ThinkPHP 3.2.0 的过滤机制存在严重缺陷:全局 REQUEST_VARS_FILTER 默认开启但 think_filter() 仅尾部加空格,无法阻止EXP注入;且路由参数、I()函数、$_SERVER等多入口未覆盖,EXP表达式可直接拼接SQL触发远程命令执行。

ThinkPHP 3.2.0 的框架底层过滤机制,核心在于“全局变量过滤”和“输入解析层拦截”两道防线。审计时不能只看有没有过滤函数,关键要看过滤是否生效、是否覆盖所有入口、是否可被绕过。
看全局 REQUEST_VARS_FILTER 是否开启并执行
在 App.class.php 的 init() 方法中,存在如下逻辑:
- 若配置
C('REQUEST_VARS_FILTER') === true(默认为true),则对$_GET、$_POST、$_REQUEST递归调用think_filter() - 该调用发生在框架路由分发前,属于最早期的统一入口处理
- 需确认项目配置文件(如
Conf/config.php)未显式关闭此选项:'REQUEST_VARS_FILTER' => false
审 think_filter 函数的真实过滤效果
think_filter() 定义在 Common/functions.php,其原始逻辑是:
- 仅匹配 SQL 查询关键词(如
EXP、IN、LIKE、BETWEEN等),且不区分大小写 - 匹配成功后执行
$value .= ' '—— 这不是清空或转义,而是尾部追加空格 - 该操作无法阻止注入:例如
id[0]=exp&id[1]=1 and updatexml(1,1,1)中的exp被加空格变成exp,但 ThinkPHP 解析器仍识别为 EXP 表达式关键字 - 函数未处理引号闭合、注释符(
--、#)、括号嵌套、编码绕过(如%20、+)等常见绕过手法
查框架各输入入口是否都受控
ThinkPHP 3.2.0 存在多个用户可控输入通道,think_filter 并不覆盖全部:
立即学习“PHP免费学习笔记(深入)”;
-
I()函数虽常用于取参,但它内部会再次判断请求方法并读取对应超全局变量,若REQUEST_VARS_FILTER关闭,I()就无过滤 - URL 路由参数(如
/index.php/User/read/id/1)走的是PATH_INFO解析,不经过$_GET,因此逃逸think_filter -
$_SERVER、$_COOKIE、$_FILES默认不参与array_walk_recursive,除非手动指定method为server或cookie - Model 层的
where()、find()、delete()若接收数组参数(如['id' => ['exp', '1 and 1=1']]),直接触发 EXP 表达式解析,完全绕过字符串级过滤
验 EXP 表达式是否构成注入链路
EXP 是 ThinkPHP 3.2 的高危设计缺陷,不是过滤能解决的:
- EXP 允许将任意 SQL 片段拼入查询,如
$map['id'] = ['exp', '1 and (select user())'] - 框架在
Driver.class.php的parseThinkWhere()中直接拼接,无二次校验 - 只要攻击者能控制数组键值(如通过 URL 传
?id[0]=exp&id[1]=...),即可触发 - 典型 PoC:
?id[0]=exp&id[1]=updatexml(1,concat(0x7e,user(),0x7e),1)



















