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 null (not a string) when passed an array, and null === null, so passing arrays for both fields bypasses the check.
Setup
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.
curl -o login.php.bak http://verbal-sleep.picoctf.net:50313/impossibleLogin.php~# Intercept the login POST with Burp Suite and change the body to:# username[]=a&pwd[]=bSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Recover the backupObservationI noticed the challenge hinted at an 'impossible' login with hidden PHP source, which suggested the server-side code might be recoverable via an editor backup artifact such as an Emacs tilde file appended to the PHP filename.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.bakor.oldsuffixes 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.
Step 2
Exploit PHP type juggling with arraysObservationI noticed the decoded PHP source used sha1($username) === sha1($pwd) with a strict equality check, which suggested that if sha1() could be made to return null for both inputs simultaneously, the check would trivially pass; PHP does exactly this when arrays are passed instead of strings.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 null (with a warning). Since null === null, 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/passwordbash# 2. In the intercepted request change:bash# username=a&pwd=bbash# to:bash# username[]=a&pwd[]=bbash# 3. Forward the requestbashbash# Or with curl directly:bashcurl -X POST -d 'username[]=a&pwd[]=b' http://verbal-sleep.picoctf.net:50313/impossibleLogin.phpExpected output
picoCTF{w3Ll_d3sErV3d_Ch4mp_5b26...}What didn't work first
Tried: Trying to break the check with loose equality magic hashes - e.g. supplying '0e...' style strings that PHP coerces to zero under ==
The code uses strict equality (===), not loose equality (==). Magic hash strings only work against loose == comparisons where PHP coerces both sides to the integer 0. Under ===, PHP also checks type, so two string '0e...' values would only match if they are the exact same string - they will never satisfy sha1($username) === sha1($pwd) when $username !== $pwd as strings. The array-to-null technique works here precisely because === null === null is true regardless of string content.
Tried: Sending the curl request with a regular string that is already the SHA1 of itself (a fixed-point), hoping sha1(sha1(x)) === sha1(x) creates a collision
No known SHA1 fixed point exists where sha1(x) === x as a raw string, and even if it did, both sides of the check receive the original input value, not its hash. The server computes sha1($username) and sha1($pwd) separately, so finding a string equal to its own hash would only produce sha1(same) === sha1(same), which still fails the $username !== $pwd gate. The array approach short-circuits both sha1() calls to null simultaneously, bypassing the need for any cryptographic property.
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 returnsnull. When the code doessha1($username) === sha1($pwd)and both calls returnnull, the strict equality===check passes - null equals null. 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.
Interactive tools
- Reverse Shell GeneratorGenerate reverse shell payloads (bash, nc, python, perl, ruby, php, node, powershell) and matching listeners. Set host and port once, copy any variant.
Flag
Reveal flag
picoCTF{w3Ll_d3sErV3d_Ch4mp_5b26...}
Passing arrays instead of strings causes sha1() to return null for both, making null === null evaluate to true.