前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThinkPHP 3.X / 5.X order by 注入

ThinkPHP 3.X / 5.X order by 注入

作者头像
wywwzjj
发布2023-05-09 14:24:55
6820
发布2023-05-09 14:24:55
举报
文章被收录于专栏:wywwzjj 的技术博客

概述

在 ThinkPHP 5.1.23 之前的版本中存在 SQL 注入漏洞,该漏洞是由于程序在处理 order by 后的参数时,未正确过滤处理数组的 key 值所造成。如果该参数用户可控,且当传递的数据为数组时,会导致漏洞的产生。

ThinkPHP 5.1 中的更新日志也可看到:V5.1.23(2018-8-23)改进order方法的数组方式解析,增强安全性。

同时受到影响的还有 3.2.3 及以下的版本。

环境搭建

代码语言:javascript
复制
composer create-project --prefer-dist topthink/think=5.1.22 thinkphp5.1.22

ThinkPHP 3.2.3 版本环境建议按官方文档操作,直接下载: https://www.kancloud.cn/manual/thinkphp/1680

配好数据库后,在 index.php 中加入测试代码。

代码语言:javascript
复制
class Index {
    public function index() {
        $order = input('order');
        $res = db('user')->order($order)->find();
        dump($res);
    }
}

user 是随便创建的表,看到该页面说明环境没问题。

image.png
image.png

复现

image.png
image.png

分析

TP 5.1.x

我们先来看一下正常的 SQL 查询流程。

ThinkPHP 提供了大量封装数据库操作的函数给开发者使用,但终究是要落实到生成 SQL 语句的。

Builder.php 中可看到这些实现细节,以 select 查询为例,TP 弄了一个查询模板,每次查询时替换成具体的值。

代码语言:javascript
复制
protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%';

select 语句替换操作,在这里生成 SQL 语句。既然 parseOrder() 有注入,其他的同样可能出现问题。

代码语言:javascript
复制
public function select(Query $query) {
    $options = $query->getOptions();

    return str_replace(
        ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
        [
            $this->parseTable($query, $options['table']),
            $this->parseDistinct($query, $options['distinct']),
            $this->parseField($query, $options['field']),
            $this->parseJoin($query, $options['join']),
            $this->parseWhere($query, $options['where']),
            $this->parseGroup($query, $options['group']),
            $this->parseHaving($query, $options['having']),
            $this->parseOrder($query, $options['order']),
            $this->parseLimit($query, $options['limit']),
            $this->parseUnion($query, $options['union']),
            $this->parseLock($query, $options['lock']),
            $this->parseComment($query, $options['comment']),
            $this->parseForce($query, $options['force']),
        ],
        $this->selectSql);
}

最终在 Connection.php 中用 PDO 执行。

代码语言:javascript
复制
$this->PDOStatement = $this->linkID->prepare($sql);
$this->PDOStatement->execute();
return $this->getResult($pdo, $procedure);

回到本文的重点,order by 处理,可看到 order by$array 拼接一下就返回了。

代码语言:javascript
复制
protected function parseOrder(Query $query, $order) {
    if (empty($order)) {
        return '';
    }

    $array = [];

    foreach ($order as $key => $val) {
        if ($val instanceof Expression) {
            $array[] = $val->getValue();
        } elseif (is_array($val)) {
            // 有些分析是从这进去的,没这个必要,反而使 payload 复杂化
            $array[] = $this->parseOrderField($query, $key, $val);
        } elseif ('[rand]' == $val) {
            $array[] = $this->parseRand($query);
        } else {
            if (is_numeric($key)) {
                list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
            } else {
                $sort = $val;
            }

            $sort    = strtoupper($sort);
            $sort    = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
            $array[] = $this->parseKey($query, $key, true) . $sort;
        }
    }

    return ' ORDER BY ' . implode(',', $array);
}

如果 $val 非 ASC、DESC,将被直接清空。继续跟进 parseKey(),这个方法会随使用的数据库驱动类型变化。

代码语言:javascript
复制
// Mysql.php
public function parseKey(Query $query, $key, $strict = false) {
    if (is_numeric($key)) {
        return $key;
    } elseif ($key instanceof Expression) {
        return $key->getValue();
    }
    
    $key = trim($key);
    
    // ... 处理 json 字段和 table_name.filed 这种形式
    
    if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
        $key = '`' . $key . '`';
    }
    
    // ...
    
    return $key;
}

简单来说,经过这一步操作,由于strict = true,key 将被包一层反引号。

现在的问题就变成了:

代码语言:javascript
复制
select xxx from xxx order by `$key` limit 1;  -- limit 1 是自动拼接上的

联合注入时,经常使用 order by 4 来判断字段数,当 4 被反引号包裹时还能起到同样的效果吗?没了。

也就是说至少得知道一个字段名,否则 order by 这里就会报错了。不能堆叠注入,待继续突破!

看下 V5.1.23 的补丁,order by 后的 ) 没了,还能继续绕吗?

代码语言:javascript
复制
if (false === strpos($key, ')') && false === strpos($key, '#')) {
    $sort    = strtoupper($sort);
    $sort    = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
    $array[] = $this->parseKey($query, $key, true) . $sort;
}

TP 3.2.3

这个版本就更简单了,相比 5 系列,连反引号都没有了。

代码语言:javascript
复制
// Driver.class.php
protected function parseOrder($order) {
    if (is_array($order)) {
        $array = array();
        foreach ($order as $key => $val) {
            if (is_numeric($key)) {
                $array[] = $this->parseKey($val);
            } else {
                $array[] = $this->parseKey($key) . ' ' . $val;
            }
        }
        $order = implode(',', $array);
    }
    return !empty($order) ? ' ORDER BY ' . $order : '';
}

组成的 SQL 语句是这样的:

代码语言:javascript
复制
select xxx from xxx order by $order limit 1;  -- limit 1 是自动拼接上的

拿出 order by 的常规套路即可,有报错就报错注入,没报错就盲注。

总结

TP 3 这个注入还是挺实用的,TP 5 还需要继续研究下,如果不能获取到列名,很难利用。

参考

https://mp.weixin.qq.com/s/jDvOif0OByWkUNLv0CAs7w

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/01/30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 环境搭建
  • 复现
  • 分析
    • TP 5.1.x
      • TP 3.2.3
      • 总结
      • 参考
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档