php流行性注入漏洞利用,【漏洞分析】ThinkPHP 5.0版本 SQL注入漏洞分析

本文详细解析了ThinkPHP 5.0版本中V5.0.16的安全更新,重点聚焦于发现的SQL注入漏洞。通过代码分析,展示了漏洞是如何利用可控输入构造恶意SQL语句的,同时探讨了为何修复过程复杂。最后提供了利用示例和修复措施,以及思考题供读者检验理解。
摘要由CSDN通过智能技术生成

阅读:

6,902

前段时间,ThinkPHP发布了V5.0.16版本的release,该版本提到了安全更新。本篇文章以此次安全更新入手,对ThinkPHP 5.0版本 SQL注入漏洞进行了详细分析。文末还有测试小问题,看看大家get到这个漏洞的精髓了吗?

前言

Thinkphp V5.0.16版本的release说明如下:

ae175fadef56e95252880d6ef8569324.png

说明中提到了安全更新,但并没有提到是什么安全问题。

V5.0.16的commits记录如下,可以看到在3月26日出现了一个关于安全性的提交,但26日似乎没有一次性改好,在27日又对这个inc/dec查询改动了一次

632683912afebdf5154d8897705ab9b2.png

接下来看下这个inc/dec查询到底有什么问题,需要一改再改。

漏洞分析

先看下26日改了什么

0d8f7e99985b70194073951dfdfe5050.png

再看看27日改了什么

852439547110d57a042590d044296a51.png

改动都在Builder.php这个文件的相同位置,而且反反复复的折腾的,就是$val[1]这个变量。

接下来看看完整的函数部分,看看$val[1]到底怎么了。

漏洞部分在parseData函数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] . '+' . floatval($val[2]);

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;

}

可以看出这个方法是用来传入的字典类型$data数据的,具体传入的$data是什么,还需要进一步分析。

先不管调用关系,单单看这个方法,在处理$data的value时,会分情况处理,

c145f092c639d1d218a55c04c6cd4424.png

是否是空,是否是数组,是否是常量,而漏洞恰恰出在了是否是数组这个elseif上了。

接下来看看谁调用了parseData,传入的$data又是什么。

向上跟踪到Builder.php中的insert方法

53e0890274dbf77b10b93601220c6199.png

这个insert也不是最上层,但我们就先看看这个insert做了什么。

假如最上层入口的$data我们可控,$data这个字典中的value值还是个数组,那个经过parseData方法后,最终的返回值就可控,原因如下:

2e43a6f0351bb5a6dd07ddd0b5de3be0.png

只要$val[0]的值是exp/inc或者是dec,那么我们就能将$val[1]恶意构造的值传入$result[$item]中,这个$result值最终会返回给insert方法中的$data变量,看下图:

376b15442c2788eeeef8193302480975.png

并且,最终$fields的值会是$item的值;$values的值会是$result[$item]的值,即为

2447387a98b2aebbc6eaf899474665fc.png

不要担心这个parseKey方法会破坏我们构造的$val[1],因为。。。。

800d98231aa120a996c41d1e8185ddc1.png

到目前为止,我们的推断是,只要$data的值可控,那么我们就能将恶意构造的值传入$values这个参数。接下来,看看$values这个可控参数又如何造成sql注入的。

706bbebdbf6ee5228db44087e7d4d78e.png

很明显,这里是要拼接sql语句了,最终的$values会被拼接到$sql变量中

eed08ae3364d297e77bdf81df417b47e.png

抛开恶意构造的部分,有经验的朋友一看就能想到这个$sql变量是要做什么的。对,这个$sql变量是要用作参数化查询的sql指令部分。

为了验证我们的猜想,继续往上层跟踪。

在\library\think\db\Query.php中,调用了我们刚才分析的builder中的insert方法

85c0df4ed52a657d70428f47eb553c9f.png

刚才的$sql变量,这次又传递给了Query.php中的$sql变量了,这里的$bind,实际上就是用来取出真是value值的。

然后在上图红圈中,使用execute进行参数化查询。由于$sql变量可控,里面可以包含我们传入的恶意字符串,因此,即使用了参数化查询,也没法避免sql注入的产生。

这个漏洞怎么利用呢?这关系的Query.php中的这个insert方法,看看thinkphp中关于这个方法的使用说明

d80cbcfc7399e7f8c6943968b5acb7c3.png

我这里给个demo,帮助大家理解下应用场景。

d4ad51ad1daf3c7384ca5d5d5a3ca877.png

总结

理论上来说,利用参数化查询,将要执行的sql语句和参数分开传入,的确可以防止sql注入的产生。但是像这个案例,要执行的sql语句中的内容竟然可控,那就比较尴尬了。

思考

漏洞分析虽然告一段落了,这里我给大家提出几个问题,看看大家有没有真的弄明白这个漏洞。如果我直接通过get方法传入一个字符串,这个漏洞会利用成功吗?

最终的修补如下图

cb20e3f185aec660df5bfb5956f0b1d3.png

当$val[0]=exp的时候,$val[1]仍然可控,并且也传入了$result[$item]里了,这里是否还是有漏洞呢?为什么thinkphp不修这里呢?

答案get传入的值如果是一个字符串,最终会到如下这里

03f4606a4e68eeb2196535f986c02541.png

最终的$result[$item]中的$key值是不可控的,

对于我给出的那个例子

ed6658efa8991b2c22bb206fe05ea18b.png

$key就是红圈中的内容

因此最终执行时,$sql不可控,还利用了参数化查询,完全没有注入的可能,如下图

0f032290d74d0e17acce11ed7f9e32c3.png

答案2

这个问题也困扰到我了,找到这个问题的时间简直比分析漏洞的时间还长。

通过Input方法传入的变量,会中途经过\library\think\Request.php中的input方法进行处理,然而在这个方法中,有一个过滤器。。。

52ed128ecb1e1736bc752bb792449f4c.png

如果$data是数组形式的,就利用$this->filterValue进行处理

87f101c30865c4aab29a0915e4d4fa76.png

这个过滤器还没对我们的$data下手,注意看红框处,filterExp

c95042a9bb2cc728f53c94637771ad2e.png

在这里,filterExp如果匹配到了exp,则会给它后面加一个空格,这就导致了我们通过get/post

提交进来的数组中,如果有exp,则会被处理为“exp ”,因此无法进入”exp”这个case

efc1e009b44d9e92a56ba0f7b218ae01.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值