内容来源:[安全智库](https://mp.weixin.qq.com/s?__biz=MzI0NjQxODg0Ng==&mid=2247483798&idx=1&sn=65cdf852dffd63b9d4ec41c31d9a5365&scene=1&srcid=0617Fv53mYLaSNhYGZtceh0w&key=f8ab7b995657050b32dcb64db737c8b7135dce7d6e83622a3d6360c53999a5ae78d5147519fbe484e19adb249c744800&ascene=0&uin=MTE4NDAxNTgyMQ%3D%3D&devicetype=iMac+MacBookPro11%2C1+OSX+OSX+10.11.5+build(15F34)
### 0X01 准备工作
jannock发的Discuz有条件远程命令执行,很多大站受影响,网上还没公布细节,在某安全公众号上看到jannock简单的说了一下原理,是ssrf+redis/memcache的问题,一般的站也用不着redis/memcache这种缓解网站压力的应用,所以基本是大站才会受影响,本着学习的态度,顺着这个思路,还原了一下整个漏洞。
> http://www.wooyun.org/bugs/wooyun-2016-0214429
其中redis和memcache的原理是一样的,drops上有一篇利用memcache攻击的文章。附链接:
> http://drops.wooyun.org/papers/8261
我在这里就还原利用redis的攻击方法。
讲原理之前先来说说redis/memcache是干嘛的呢?其实可以把它们理解成一个高性能的数据库,当网站的缓存很大的时候,利用redis/memcache来处理缓存可以提高网站的性能,在dz的后台也可以看到是否在使用redis/memcache。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reuz3y1BqfH4Re2tyCNb42ovgwbzoCvQfsbQQvrWcD6pvbfggczkJicicA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
首先你要自己在服务器上安装redis,然后php默认没有redis扩展,还需要安装phpredis扩展,顺便说一下,phpredis的2.1版本兼容性不太好,安装上去后无法访问网站,用2.28版本就可以了,配置这个环境是花了我一下午的时间,其中很多曲折,都不是重点,安装之后在phpinfo()中能看到redis。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reVZx08mAefWWhticbRx8XtVkoZCTB1r8iaqicYyNnficl6A6APdgIIsEKIQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
在dz的配置文件config_global.php中填入redis的地址就可以了,端口都是默认的6379,prefix是dz中随机生成的,默认无密码。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re5mj3kgkJ2IPAib50uA4Uf5ldRRIaibjdYlFIYayiaINicWTv9HToE8gBRw/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
都完成之后在dz后台能看到运行情况。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reQ8gkd3gCk1s1PSYhkdM5FjxibcEIwrRNATI4jichkGB3ezITIu0t74RA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
简单说一下redis的使用吧,启动之后在本机的6379端口会运行起来redis的服务,redis可以理解为一个数据库,数据以key=>value这样的形式储存,其实更像一个数组,使用如下。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re1oot8wBOwJfs9M6p8svplwXRCmTXsvDaK8KwLUR0BDlvoiclbYn9X1g/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
Redis还支持eval “lua命令”这样来执行,我们在后面会用到,简单的例子如下:
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reWK1BcyGT8Nic4wBmsGlURpH7TP84LV1JCW5xPzZcxnN68qlZw6FfssA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
### 0X02 漏洞原理与复现
简单的说,漏洞就是通过ssrf来操作redis,更改了全局变量的值,导致任意代码执行。
当dz设置使用缓存后,初始化时会把缓存内容加入全局变量$_G
Source/class/discuz/discuz_application.php中设置。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7renHMWYF17fnN2buztOo1DDmGQFlRMJceAWQ48d2bzepGliaSOHtZRGUg/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
而在调用缓存的地方source/fucntion/function_core.php中。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re5IPj5JwOfCg8H8Pzl38PGiadicPiavQf79rH8CYOn6IauxUedyuYHp7PQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
其中,关键点的两个变量我们都可以通过redis修改,来getshell为了方便测试,可改为:
```
$_G['setting']['output']['preg']['search']=”/.*/e”;
$_G['setting']['output']['preg']['replace']=”phpinfo();”;
```
我们来看看哪些地方可以触发漏洞,搜索一下output_replace()发现只在function output和function output_ajax中用到了,两个函数中应该都可以触发,挑output_ajax来看看。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re593EskD4sBEZQOAqSS65W7gZJL6c07JQm5jibyOk2ysNhfzjbaTWckg/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
一开始我准备在划线的if前面print_r一下if判断中的全局变量。
```
$_G['setting']['rewritestatus']
```
但是什么也打印不出来,最后发现是前面的ob_get_contents()这个函数的问题,这个函数会把输出的值存到缓存中,并不会打印到浏览器页面上,那怎么看这个变量的值呢?还可以把这个变量写到txt文件中,再来查看。发现正常运行情况下,if判断是不满足的,其中:
```
$_G['setting']['rewritestatus']=0
```
所以我们在修改缓存值的时候还需要把它改为1,接着看看output_ajax()在哪些地方用到了。
在/data/template/1_1_common_footer_ajax.tpl.php中:
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7rezxScJOJ6xXUqtr35vgYCyibicRkL3ia7TwRry6v1xcKJV8RsT2icGtBFZw/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
这个文件用到的地方很多,比如在forum_ajax中:
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reVodFM6xCibDgUaOm0ahDxCaFrkfmsJmyW8g39xml6ZKI23EN8fNv0gA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
这个地方对应的地址是:
```
/forum.php?mod=ajax&action=getthreadtypes&inajax=yes
```
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7relRCMAHpyUaqnpVIPfk5Rj67o5dMwrib59DVrpIckhicBa2CYyk9dfz2w/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
然后我们看下如何通过redis修改缓存值,dz使用redis时,全局变量$_G[‘setting’]放在xxxx_setting中的,其中前缀就是前面config_global.php中的prefix的值,那我们这里的全局变量是放在5Z13gm_setting中的,我们来看看。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re2ibLWEOMI1G847icGSwZcicsUtZgAhszkcSNsCKpNiakuYUMiaHBTRgEevg/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
里面数据很多,我们可以看到这里是序列化之后再放进去的,格式是$a['output']['preg']['search']['plugins']这样的,那么我们写个脚本操作。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7rejQQW57o5vUraaBrE7NbY17Mv6FFL1ibeqibJqTdzpyKLQWVaafEFlSHw/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
这里我们的redis是没有设置密码的,一般网站会设置密码(通过dz的ssrf的话就不考虑密码的问题,在config_global.php中别人都帮我们设置过了),运行脚本之后,我们再访问:
> /discuz/forum.php?mod=ajax&inajax=yes&action=getthreadtypes
成功getshell。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7remD9iacc2xjvt4cPWrgrbQ1K7yvvs93bY8ASCaWRKKOdk8zbia5BZvJGg/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
有的同学会注意到,前缀我们是不知道的啊,那不就不能修改数据了吗?但是redis是支持模糊查询的,可以通过keys方法,举个例子。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reKZaQgMicKcia701UBEYMcJUXqhdg1vPz2KWlsico56GxmiaTbPQUzYFYXA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
我们是要通过ssrf来操作redis,怎么通过一个链接来操作呢?dz的ssrf是默认调用curl请求,因此会支持gopher或者dict协议,这对我们已经足够了,我们写个脚本发送利用curl发送gopher请求。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7remhTeD58E4BRbrtZKk8uGPkSiaqgMoI42CiakQpNaz9n7Wia0P2B9icvHAg/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
%0D%0A是换行符,相当于执行下一条指令,这里set前面加了一个x是因为/后面的第一个字符不会被当做指令,我们执行的指令其实是set name cheng。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7rewIljBBhg0ntXQEJyIl3TV5vQIJKlPB3mb9DbZskK7oEI9zZiazUHLeQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
这里返回ok就是表示我们执行成功,这样就相当于在命令行中执行redis的指令,有个问题,还是前缀的问题,我们的前缀是可以通过指令keys *_setting获取到,但是ssrf的时候看不到返回值的,我们没有办法获取到返回值,那怎么办?这里我们就要用到lua脚本,前面提到了,redis是可以通过eval来执行lua脚本的,我们看看我们的lua脚本。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re2I9Nicg6CVM1BVa8F3TOXD5Og4b1P99Cyft30iaVR5kDQVVpCsnYDHYA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
通过keys方法把xxx_setting保存到v中,然后set v xxxx,这样就不会有前缀未知的问题了,gopher的链接就是:
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reIcjRrJ5SvZcTBAicxiczXXvXE6TYs1sRakU7gAwWrmElqn1XVg62lISA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
执行结果:
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reoWwPVCCacZtD1CBwsib639NIwDZUBzicn5JDxjJhFORrBpdvD4SQMjkA/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
最后return 1了,说明执行成功,刷新页。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re0n97RNoG3u4YhjZKn2zRLjGdwTakKNCSDPnF0hV4pdPsrQCvVqaXfw/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
成功getshell,因为我们破坏了5Z13gm_setting中的数据,会导致网站访问异常。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7reNzZjnPY89Dd7F3qpcWyicEHjhfTLlYKohZwsOzzFLz8weCpBMiadwdTg/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
利用flushdb命令重置redis中的数据,再刷新才能恢复访问。
### 0X03 遗憾与感想
讲到这里整个漏洞基本复现完了,剩下结合ssrf的复现,我在脚本里已经是使用curl来发送请求的,所以通过ssrf来实现应该也不是问题,遗憾的是我手上并没有合适的ssrf。
为什么说ssrf也要合适,因为我们看到我们的链接是:
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7remungSycE4mkj0MWppezoMtCv0R7YNIoVGx6k7J2iaevUQ3G7oOZa0cQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
当中包含了很多特殊字符,而且脚本在里面也不方便去构造满足条件的ssrf,比如dz最新版倒是有一个ssrf,是jannock之前发的一个:
> http://www.wooyun.org/bugs/wooyun-2015-0151179
这个漏洞还能利用。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7rehib54ia3HZjYpN1paFW4XwLdFGYRsD3szvSQSRvRyECiavNFpBoos4NCQ/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
但是这个利用起来就有条件,比如链接必须是图片链接,最后要是.jpg,协议必须是http。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re4wMy1Vw6hibSJOZu5yItO0Z9AP1P0jWjwdMLl9egl0G1Z7ibODO5Bc1A/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
因为是在链接中,还不能包含特殊字符,不然会被xss_check拦截。
![](http://mmbiz.qpic.cn/mmbiz/3aPicPDUDhKH5Ep73aKkEOsfsXs78C7re0qOfgtrGichj85znnjiaoP6m5ib8NFPndEYb35nTsOU7rkJHgl2oPHE5g/640?wx_fmt=png&wxfrom=5&wx_lazy=1)
如果能有合适的ssrf,就能够前台getshell了。
认真想了想,这个漏洞本质上是通过gopher协议操作redis修改全局变量,也不好去补,把全局变量不放到redis中处理?这是一个方法,但是又会降低使用redis后网站效率,dz能做的就是处理好ssrf的地方,不要提供跳板让攻击者能够getshell,以后dz出一个ssrf对于这些使用redis的大站来说都是一个getshell的威胁,这也挺难受的。最后提一点,redis使用的时候一定要设置密码和访问权限,redis出现未授权访问可以很轻易的拿到root权限,具体可以看wooyun实例
> http://www.wooyun.org/bugs/wooyun-2015-0152710
才疏学浅,有纰漏错误之处还往指出,多谢。
全部评论 (2)