ThinkPHP 3.2.3 SQL注入漏洞核心在于数组参数未校验、关键字段(table/alias/where)未过滤、表达式解析绕过转义;审计需追踪模型find/select/delete等方法入口及_parseOptions中危险键值拼接逻辑。

ThinkPHP 3.2.3 的 SQL 注入漏洞核心在于 数组型参数未校验、关键字段未过滤、表达式解析绕过转义,审计时不能只盯单引号闭合或 intval 处理,而要顺着框架数据流,重点抓 find()、select()、delete() 等模型方法的参数入口和解析逻辑。
从控制器入口定位高危调用链
先搜索项目中使用 M() 或 D() 实例化模型后直接调用 find()、select()、where()、delete() 的地方,尤其是参数来自 I('GET.xxx')、I('POST.xxx') 等用户输入的场景。典型危险写法:
$data = M('user')->find(I('GET.id'));$list = D('Article')->where(I('POST.condition'))->select();M('log')->delete(I('GET.id'));
注意:若 I() 获取的是数字型字段(如 id),虽经 intval() 转换看似安全,但一旦传入数组(如 ?id[where]=1=1),就会跳过类型转换,直接进入模型的数组解析分支。
跟踪 _parseOptions() 中的危险字段解析
漏洞主因集中在 Model->_parseOptions($options) 方法。该方法对以下键值不做任何过滤或白名单校验,直接拼入 SQL:
立即学习“PHP免费学习笔记(深入)”;
-
$options['table']:可被设为'user UNION SELECT 1,@@version,3--',后续经explode(',')分割后仍原样进入parseKey(),无任何清理 -
$options['alias']:可控字符串,直接追加在表名后,如'user AS t1 UNION SELECT --' -
$options['where']:若为字符串(非数组),则完全跳过parseWhere()中的转义逻辑,原样返回构成WHERE子句
例如传入 ?id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)#,因 $options['where'] 是字符串,不走数组分支,也不触发 parseValue() 转义,最终拼进 SQL 执行报错回显。
检查 I() 和 think_filter 的过滤盲区
I() 默认调用 think_filter(),但该函数仅用黑名单匹配几个关键字(如 EXP、IN、BETWEEN),对 updatexml、extractvalue、floor(rand(0)*2)、堆叠注入分号等完全放行。审计时应确认:
- 是否全局重写了
DEFAULT_FILTER或自定义了think_filter - 是否存在
I('param.xxx', '', 'htmlspecialchars')这类显式指定过滤器的写法——htmlspecialchars对 SQL 注入无效 - 是否在调用模型方法前手动清空或覆盖了
$options数组,掩盖了原始污染点
验证底层 SQL 构建是否依赖字符串拼接
继续跟进到 Db\Driver\Mysql->select($options),观察其如何生成最终 SQL。3.2.3 使用模板替换方式(如 'SELECT %FIELD% FROM %TABLE% %WHERE%'),再用 str_replace() 填充各占位符。关键点在于:
-
%TABLE%替换内容来自$this->parseTable($options['table']),而parseTable()内部又调用parseKey()—— 该方法对字符串参数不做任何处理,直接返回 -
%WHERE%来自parseWhere(),但仅当$where是数组时才走转义流程;字符串型$where直接返回,成为注入出口 -
parseBind()仅处理预处理绑定参数,对非绑定的字符串拼接型字段不起作用
只要发现任意一处使用 str_replace、sprintf 或直接字符串连接拼接用户控制的 table、alias、where、order、group 等字段,即可确认存在注入风险。



















