[红日安全]代码审计Day4 - strpos使用不当引发漏洞
字数 1261 2025-08-29 08:32:24

PHP代码审计:strpos使用不当引发的安全漏洞分析

1. 漏洞背景

本文分析PHP代码审计中因strpos()函数使用不当导致的安全漏洞,并结合实际案例(DeDecms V5.7SP2)展示类似逻辑缺陷引发的安全问题。

2. 示例漏洞分析

2.1 原始漏洞代码

// 示例代码片段
class Login {
    public function __construct($user, $pass) {
        $this->loginViaXml($user, $pass);
    }
    
    public function loginViaXml($user, $pass) {
        if (strpos($user, '<') !== false || strpos($pass, '<') !== false) {
            die('非法字符');
        }
        if (strpos($user, '>') !== false || strpos($pass, '>') !== false) {
            die('非法字符');
        }
        
        $format = '<user><username>%s</username><password>%s</password></user>';
        $xml = sprintf($format, $user, $pass);
        // 使用XML进行登录验证...
    }
}

new Login($_POST['user'], $_POST['pass']);

2.2 漏洞原理

  1. strpos函数特性

    • 返回匹配子字符串的位置(0表示开头匹配)
    • 未找到时返回false
    • false == 0在PHP中为true
  2. 错误防护逻辑

    if (strpos($user, '<') !== false) // 正确写法
    if (!strpos($user, '<'))         // 错误写法(漏洞点)
    
  3. 漏洞利用

    • 当输入以<开头时,strpos()返回0
    • !0在PHP中等于true,导致防护被绕过
    • 可构造XML注入payload:
      user=<"><injected-tag%20property="&pass=<injected-tag>
      

3. 实际案例分析:DeDecms V5.7SP2密码重置漏洞

3.1 漏洞位置

member/resetpassword.php文件中的安全问答验证逻辑

3.2 漏洞代码

// 安全问答验证
if($dopost == "safequestion") {
    $row = $db->GetOne("SELECT safequestion,safeanswer,userid,email FROM #@__member WHERE mid = '$mid'");
    if(empty($safequestion)) $safequestion = '';
    if(empty($safeanswer)) $safeanswer = '';
    
    // 漏洞点:使用弱类型比较
    if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer) {
        sn($mid, $row['userid'], $row['email'], 'N');
        exit();
    }
}

3.3 漏洞原理

  1. 弱类型比较问题

    • 未设置安全问答时,数据库值为$row['safequestion']="0"$row['safeanswer']=null
    • 使用==比较时:
      • "0" == "" → false
      • null == "" → true
    • 可绕过payload:0.00.0e1
  2. 利用链

    1. 访问resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=[目标ID]
    2. 获取返回的key值
    3. 访问修改密码链接:resetpassword.php?dopost=getpasswd&id=[目标ID]&key=[获取的key]
    

3.4 漏洞修复

将弱类型比较==改为严格比较===

if($row['safequestion'] === $safequestion && $row['safeanswer'] === $safeanswer)

4. 防御建议

  1. strpos正确用法

    // 正确方式
    if (strpos($input, '<') !== false) {
        // 包含<字符
    }
    
    // 错误方式(易被绕过)
    if (!strpos($input, '<')) {
        // 误判0为false
    }
    
  2. 类型安全比较

    • 始终使用===!==进行严格比较
    • 特别注意:0"0"""nullfalse的区别
  3. 输入过滤

    • 对XML/HTML/SQL等特殊上下文使用专用过滤函数
    • htmlspecialchars()mysqli_real_escape_string()
  4. 安全编码实践

    • 使用白名单而非黑名单验证
    • 对边界条件进行全面测试(如空值、0值、特殊字符等)

5. 总结

  1. PHP的弱类型系统和特定函数行为可能引入安全隐患
  2. strpos()等函数需注意返回值0与false的区别
  3. 比较操作应优先使用严格模式(===/!==)
  4. 安全验证逻辑需要覆盖所有可能的输入情况
  5. 实际漏洞常由多个小问题组合触发,需全面审计

6. 扩展学习

  1. PHP类型比较表:

    值1 值2 == ===
    0 "" true false
    0 "0" true false
    null "" true false
    false "" true false
  2. 相关函数注意事项:

    • stripos():不区分大小写版本,有相同问题
    • strstr()/stristr():返回子字符串或false
    • preg_match():返回匹配次数(0或1),需用===1判断
  3. 推荐审计工具:

    • PHPStan
    • Psalm
    • RIPS静态分析工具
PHP代码审计:strpos使用不当引发的安全漏洞分析 1. 漏洞背景 本文分析PHP代码审计中因 strpos() 函数使用不当导致的安全漏洞,并结合实际案例(DeDecms V5.7SP2)展示类似逻辑缺陷引发的安全问题。 2. 示例漏洞分析 2.1 原始漏洞代码 2.2 漏洞原理 strpos函数特性 : 返回匹配子字符串的位置(0表示开头匹配) 未找到时返回false false == 0 在PHP中为true 错误防护逻辑 : 漏洞利用 : 当输入以 < 开头时, strpos() 返回0 !0 在PHP中等于true,导致防护被绕过 可构造XML注入payload: 3. 实际案例分析:DeDecms V5.7SP2密码重置漏洞 3.1 漏洞位置 member/resetpassword.php 文件中的安全问答验证逻辑 3.2 漏洞代码 3.3 漏洞原理 弱类型比较问题 : 未设置安全问答时,数据库值为 $row['safequestion']="0" , $row['safeanswer']=null 使用 == 比较时: "0" == "" → false null == "" → true 可绕过payload: 0.0 、 0. 、 0e1 利用链 : 3.4 漏洞修复 将弱类型比较 == 改为严格比较 === : 4. 防御建议 strpos正确用法 : 类型安全比较 : 始终使用 === 和 !== 进行严格比较 特别注意: 0 、 "0" 、 "" 、 null 、 false 的区别 输入过滤 : 对XML/HTML/SQL等特殊上下文使用专用过滤函数 如 htmlspecialchars() 、 mysqli_real_escape_string() 等 安全编码实践 : 使用白名单而非黑名单验证 对边界条件进行全面测试(如空值、0值、特殊字符等) 5. 总结 PHP的弱类型系统和特定函数行为可能引入安全隐患 strpos() 等函数需注意返回值0与false的区别 比较操作应优先使用严格模式( === / !== ) 安全验证逻辑需要覆盖所有可能的输入情况 实际漏洞常由多个小问题组合触发,需全面审计 6. 扩展学习 PHP类型比较表: | 值1 | 值2 | == | === | |-----|-----|-----|-----| | 0 | "" | true | false | | 0 | "0" | true | false | | null | "" | true | false | | false | "" | true | false | 相关函数注意事项: stripos() :不区分大小写版本,有相同问题 strstr() / stristr() :返回子字符串或false preg_match() :返回匹配次数(0或1),需用 ===1 判断 推荐审计工具: PHPStan Psalm RIPS静态分析工具