Apriti sesamo picoCTF 2025 Solution

Published: April 2, 2025

Description

ABC Bank's "impossible" login hides a PHP backup. The source shows it hashes username and password with SHA1 and returns the flag when the hashes match but the raw values differ. PHP's sha1() returns false (not a string) when passed an array, and false === false, so passing arrays for both fields bypasses the check.

Append ~ to impossibleLogin.php (Emacs backup convention) to recover the actual PHP source.

Decode the Base64 constants in the source to learn the POST parameter names (username, pwd) and the SHA1-equality check.

Use Burp Suite (or curl) to intercept the login POST and change username and pwd from strings to arrays.

bash
curl -o login.php.bak http://verbal-sleep.picoctf.net:50313/impossibleLogin.php~
bash
# Intercept the login POST with Burp Suite and change the body to:
bash
# username[]=a&pwd[]=b
  1. Step 1Recover the backup
    Emacs creates backup files by appending a tilde to the filename. Browse impossibleLogin.php~ to download the real PHP source. Decode the Base64 constants inside: they reveal the POST field names username and pwd, and the comparison sha1($username) === sha1($pwd) && $username !== $pwd.
    Learn more

    Emacs editor backup files are created automatically when Emacs opens a file for editing. The backup gets the same filename with a trailing tilde (~). When developers edit server-side files directly using Emacs in a web-accessible directory and forget to delete the backups, they become publicly downloadable - exposing source code that was never meant to be served.

    Common variants include Vim swap files (.swp, .swo), nano backup files (.save), and .bak or .old suffixes left by IDEs. Web servers do not strip these by default, so they must be explicitly blocked in server configuration.

    The Base64 encoding inside the PHP source is a thin attempt to obscure hardcoded constants. Once the source is obtained, decoding takes a single command. Encoding rather than encrypting gives only the illusion of security.

  2. Step 2Exploit PHP type juggling with arrays
    Use Burp Suite to intercept the login POST request. Change the body so that both username and pwd are arrays: username[]=a&pwd[]=b. In PHP, sha1() of an array returns false (with a warning). Since false === false, the hashes match. The raw values differ (both are arrays but were passed different dummy strings), so the != check passes too. The server returns the flag.
    bash
    # 1. Open Burp Suite > turn on Intercept > log in with any username/password
    bash
    # 2. In the intercepted request change:
    bash
    #    username=a&pwd=b
    bash
    # to:
    bash
    #    username[]=a&pwd[]=b
    bash
    # 3. Forward the request
    bash
    bash
    # Or with curl directly:
    bash
    curl -X POST -d 'username[]=a&pwd[]=b' http://verbal-sleep.picoctf.net:50313/impossibleLogin.php
    Learn more

    PHP type juggling arises from PHP's loose type system. Many built-in functions accept any type and silently convert or return a fallback when the input is unexpected. Passing an array to sha1() triggers a warning and returns false. When the code does sha1($username) === sha1($pwd) and both calls return false, the strict equality === check passes - false equals false. At the same time, the arrays themselves are not equal as values, so the !== check between the raw inputs also passes. The login condition is satisfied without ever providing matching credentials.

    This technique is well-known in PHP security research. Real-world variants include comparing MD5 hashes of arrays (same behavior), using loose equality == with certain hash strings that PHP coerces to zero (magic hash / type confusion), and passing NULL or booleans to functions expecting strings. The underlying root cause is that PHP's type system was designed for convenience, not security, and functions that accept any type can produce unexpected results in a security-sensitive context.

    Burp Suite is the standard web proxy for intercepting and modifying HTTP traffic. The Proxy tab captures requests before they leave the browser, the Repeater tab lets you resend a modified request any number of times, and the Decoder tab converts between Base64, URL encoding, and other formats. See the Burp Suite for picoCTF guide for the full setup workflow.

Flag

picoCTF{w3Ll_d3sErV3d_Ch4mp_5b26...}

Passing arrays instead of strings causes sha1() to return false for both, making false === false evaluate to true.

Want more picoCTF 2025 writeups?

Tools used in this challenge

Related reading

What to try next