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 关键问题点

  1. 输入处理流程

    • 用户通过HTTP请求传入恶意参数(如?test[user^updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
    • 框架的input()函数在Request.php中对请求参数进行初步处理
    • 参数未经充分过滤直接传递到数据库查询构建阶段
  2. 关键函数调用链

    helper#db → Query#where → Query#order → Query#find → Builder#select → Builder#parseOrder → Mysql#parseKey
    
  3. 漏洞触发点

    • 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 请求处理流程

  1. 输入接收

    • input('test/a')接收GET参数并转换为数组
    • 恶意参数存储在$test变量中
  2. 数据库查询构建

    • where()方法处理条件
    • order()方法将恶意参数直接存储到$this->options['order']
  3. 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 临时解决方案

  1. 输入过滤

    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);
    }
    
  2. 重写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. 漏洞防护建议

  1. 开发阶段

    • 对所有用户输入进行严格过滤和验证
    • 使用框架提供的参数绑定机制
    • 避免直接拼接用户输入到SQL语句中
  2. 运维阶段

    • 定期更新框架到最新版本
    • 部署WAF防护SQL注入攻击
    • 限制数据库账户权限
  3. 代码审计

    • 检查所有使用order、where等方法的代码
    • 特别注意直接使用用户输入作为查询条件的场景

7. 总结

该漏洞暴露了ThinkPHP框架在早期版本中对SQL查询构建过程中对用户输入过滤不足的问题。通过精心构造的恶意参数,攻击者可利用order子句实现SQL注入,获取数据库敏感信息或进行其他恶意操作。开发者应重视所有用户输入点的安全过滤,并保持框架版本的及时更新。

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中对请求参数进行初步处理 参数未经充分过滤直接传递到数据库查询构建阶段 关键函数调用链 : 漏洞触发点 : 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 中配置数据库连接信息: 3.3 漏洞复现代码 在控制器中添加测试代码: 3.4 触发漏洞 访问URL: 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方法 : parseOrder函数问题 : parseKey函数缺陷 : 4.3 最终生成的恶意SQL 原始输入: 生成的SQL语句: 5. 修复方案 5.1 官方修复 升级到ThinkPHP 5.1.23或更高版本,官方在后续版本中加强了对order字段的过滤。 5.2 临时解决方案 输入过滤 : 重写parseKey方法 : 在自定义的Builder类中重写parseKey方法: 6. 漏洞防护建议 开发阶段 : 对所有用户输入进行严格过滤和验证 使用框架提供的参数绑定机制 避免直接拼接用户输入到SQL语句中 运维阶段 : 定期更新框架到最新版本 部署WAF防护SQL注入攻击 限制数据库账户权限 代码审计 : 检查所有使用order、where等方法的代码 特别注意直接使用用户输入作为查询条件的场景 7. 总结 该漏洞暴露了ThinkPHP框架在早期版本中对SQL查询构建过程中对用户输入过滤不足的问题。通过精心构造的恶意参数,攻击者可利用order子句实现SQL注入,获取数据库敏感信息或进行其他恶意操作。开发者应重视所有用户输入点的安全过滤,并保持框架版本的及时更新。