『SSRF』服务器端请求伪造
基本概念
-
定义
SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种攻击者形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔离的内部系统)。
-
原理
SSRF通常出现在web应用提供从其他服务器应用获取数据功能(例如从指定URL获取远程资源)的时候,例如:
- 图片下载或预览
- 网站抓取器
- PDF 生成器
- Webhook 通知地址
- 文件上传后校验远程地址合法性等
如果应用在处理地址和协议的时候没有严格过滤或校验,从而使得攻击者可以利用存在缺陷的web应用作为代理,攻击其远程和本地的服务器(攻击者可以传入任意的 URL,从而控制服务器去访问),例如:
- 请求内网资源
- 端口扫描
- 云服务的元数据接口
- 敏感接口:如redis、内网API、数据库
- 任意外部地址:伪造请求或隐藏攻击来源
- 利用file协议读取本地文件
SSRF漏洞验证方式
观察是否为服务端发起请求
通过页面元素的“行为”去排查请求到底是浏览器直接发的,还是浏览器将地址交给服务器让服务器去请求(也就是 SSRF)。
例如:
页面中有一张图片:
1 | <img src="http://www.xxx.com/a.php?image=http://img.abc.com/test.jpg"> |
你用浏览器访问这个页面,按 F12
打开控制台 Network 观察:
- 如果看到直接请求的是
http://img.abc.com/test.jpg
→ 表示是浏览器直接请求,不是 SSRF。 - 如果看到请求的是
http://www.xxx.com/a.php?image=...
,且最终图片是服务器返回的 → 说明服务器可能先拿这个 URL 去下载,再返回给你,这种情况就要警惕 SSRF。
DNSLog
当 SSRF 是盲打型(无页面回显)时,你可以使用 DNSLog 生成一个唯一子域,如:
1 | http://abc.12345.dnslog.cn |
然后将它作为 SSRF 的 URL 参数:
1 | http://www.xxx.com/a.php?image=http://abc.12345.dnslog.cn |
如果服务器发起请求,会触发你的 DNSLog 收到解析记录,从而验证 SSRF 成立。
抓包分析探测
使用 Burp Suite 或 Fiddler 抓取你浏览器的请求,查看:
- 你发的是
image=http://127.0.0.1:8080
,但这个地址你客户端根本访问不了 - 页面却返回了
Tomcat
的信息 → 很可能是服务器自己请求并返回的,验证 SSRF。
一旦验证 SSRF 存在,可以尝试发起请求探测内网服务,比如:
http://127.0.0.1:6379
(Redis)http://localhost:8080
(Tomcat)http://192.168.0.1:80
(内网 Web 后台)
SSRF漏洞利用方式
SSRF漏洞利用函数
下面以 php 为例,说明 php 中可能存在 SSRF 漏洞的函数
file_get_contents()
file_get_contents
函数从用户指定的url获取图片,然后通过 file_put_content
函数把文件保存在硬盘上,并展示给用户。
1 |
|
fsockopen()
fsockopen
函数实现对用户指定url数据的获取,该函数使用socket(端口)跟服务器建立tcp连接,传输数据。变量host为主机名,port为端口,errstr表示错误信息将以字符串的信息返回,30为连接超时时间
1 |
|
intval($port)
:端口号转为整数$errno
和$errstr
:连接失败时记录错误码和错误信息
curl_exec()
cURL 是一个强大的客户端工具,常用于在程序中发起 HTTP 请求。PHP 提供了对 cURL 的原生支持,方便你在脚本中模拟浏览器行为去获取网页数据、提交表单、下载文件、调用 API 等。
1 |
|
SSRF漏洞利用协议
当我们发现SSRF漏洞后,首先要做的事情就是测试所有可用的URL伪协议
- file 协议结合目录遍历读取文件。
- gopher 协议打开端口。
- dict 协议主要用于结合 curl 攻击。
- http 协议进行内网探测。
HTTP / HTTPS
基于 HTTP/HTTPS
协议可以实现内网探测
-
访问内网 Web 服务
-
读取云服务元数据
-
探测端口(返回状态码判断)
1 | GET /fetch?url=http://127.0.0.1:8080/admin |
file协议
使用file协议实现任意文件读取
1 | curl -vvv "http://target/ssrf.php?url=file:///etc/passwd" |
dict协议
利用dict
协议,dict://ip/info
可获取本地 redis
服务配置信息。
1 | curl -vvv "http://target/ssrf.php?url=dict://127.0.0.1:6379/info" |
gopher协议
-
Gopher协议
Gopher 是一种早期的互联网文本分发协议,可以用于构造任意 TCP数据包并发送给任意主机端口,攻击内网服务如 Redis、MySQL、SMTP 等,实现 SSRF 到 RCE
-
gopher SSRF 利用格式
1
gopher://<ip>:<port>/_<payload>
-
ip:port
:目标 -
_
:分隔符,后面接的是完整 payload -
payload
:你要发送的原始 TCP 数据,需要进行 URL 编码
-
-
攻击场景
1)Redis未授权访问
- 写入计划任务、Webshell、或清除数据库
- 结合写入
cron
、authorized_keys
达到远程命令执行
2)内网端口扫描 + 协议探测
- 通过返回的错误、延迟等信息判断端口是否开放
- gopher 可以探测 TCP 服务是否在线,比 http 更底层
3)SMTP / FTP / MongoDB / MySQL 等协议攻击(基于明文通信)
- 构造符合 SMTP 协议的发信请求,发送垃圾邮件
- 构造 FTP 登录命令,测试弱口令或开放匿名登录
- 构造 MongoDB 查询命令,尝试获取数据库数据
-
**例子:Redis 写入恶意 key **
原始 Redis 命令:
1
SET evilkey evilvalue
Redis 协议格式:
1
*3\r\n$3\r\nSET\r\n$8\r\nevilkey\r\n$10\r\nevilvalue\r\n
URL 编码后:
1
%2A3%0D%0A%243%0D%0ASET%0D%0A%248%0D%0Aevilkey%0D%0A%2410%0D%0Aevilvalue%0D%0A
完整 Gopher URL:
1
gopher://127.0.0.1:6379/_%2A3%0D%0A%243%0D%0ASET%0D%0A%248%0D%0Aevilkey%0D%0A%2410%0D%0Aevilvalue%0D%0A
当 SSRF 后端发送此请求时,就等于访问 Redis 并执行了命令:
SET evilkey evilvalue
ftp协议
ftp协议是基于 TCP 的明文传输协议,一般SSRF可以利用FTP实现:
-
FTP 回显特性用于信息泄露
请求
1
GET /ssrf?url=ftp://127.0.0.1:21
通常返回的是类似下面的 banner 内容:
1
220 (vsFTPd 3.0.3)
如果后端把响应内容原样返回给用户,你就可以知道目标是否部署了 FTP 服务,并知道服务版本。
-
通过连接响应判断端口是否开放(端口扫描)
FTP 是基于 TCP 协议的 —— 如果目标端口开放,SSR 服务器
会收到连接响应;如果关闭,则超时或拒绝连接。
如果我们将目标改成非常多的内网 IP + 端口组合(结合Bp工具进行爆破),就能实现内网扫描(TCP探测)。
1
2ftp://127.0.0.1:22
ftp://192.168.0.100:3306注:这种方式只能判断“能否建立 TCP 连接”,不能判断应用层协议是否存在。
SSRF绕过方式
部分存在漏洞,或者可能产生SSRF的功能中做了白名单或者黑名单的处理,来达到阻止对内网服务和资源的攻击和访问。因此想要达到SSRF的攻击,需要对请求的参数地址做相关的绕过处理
DNS重绑定
对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就pass掉。
要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务。同时设置TTL时间为0。(防止有DNS服务器对解析结果进行缓存)
完整的攻击流程为:
1)攻击者输入自己的域名
2)服务器端获得URL参数,进行第一次DNS解析,从攻击者控制的 DNS 服务器获得了一个非内网的IP。(同时响应的TTL值为0,这样服务器不会长时间存储这个缓存结果)
3) 服务器端对获得的IP进行判断,发现为非黑名单IP,通过验证
4)服务器端对URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
@
绕过
平常我们传入的url是url=http://127.0.0.1
,如果
我们传入的url是url=http://quan9i@127.0.0.1
,它此时依旧会访问127.0.0.1
1 | url=http://notfound.ctfhub.com@127.0.0.1/flag.php |
省略0
当过滤127.0.0.1
整体时,还有一种绕过方式就是省略中间的0,这个时候也是可以访问的
进制转换
将127.0.0.1
进行转换,转换为其他进制的数从而绕过检测
1 | 0177.0.0.1 //八进制 |
特殊0
在windows中,0代表0.0.0.0
,而在linux下,0代表127.0.0.1
,如下所示
1 | url=http://0/flag.php |
SSRF 防御
-
禁用不需要的协议。禁止
gopher://
、file://
、dict://
等非 http 协议 -
内网资源隔离
-
重要内网服务(如 Redis、K8s API)只开放给可信 IP 段访问。
-
内部接口通过防火墙控制,仅允许指定服务访问。
-
-
校验返回信息。验证远程服务器对请求的响应。例如,web应用是去获取 png 格式文件。那么在把返回结果展示给用户之前先验证返回的
content-type
是否符合image
。 -
禁止服务端访问用户可控URL。如果业务上并不需要由服务器请求用户提供的地址,建议根本禁止这种设计。