前言
到最后了,/(ㄒoㄒ)/~~
EZXSS
EZXSS(存储型 XSS)
漏洞类型: 存储型 XSS(Stored Cross-Site Scripting)
题目分析:
目标是一个技术博客网站,包含搜索框和留言板功能。经测试:
- 搜索框对输入做了 HTML 转义(
<→<),反射型 XSS 不可行 - 留言板(
/guestbook)直接将用户输入渲染到 HTML 中,无任何过滤和转义 - 页面无 CSP 头,XSS 可自由执行
/flag端点返回 404,说明 flag 在 admin 的 cookie 中- 主页有清除 cookie 的 JS 脚本,但留言板页面没有 1 攻击思路: 提交 XSS payload → admin bot 访问留言板审核 → XSS 在 admin 浏览器中执行 → 窃取 admin cookie → 回传到留言板
关键发现: <script> 标签在 innerHTML 插入的 HTML 中不会执行,必须使用 <img onerror> 等事件处理器触发 JS。
Step 1:测试搜索框(反射型 XSS 不可行)
|
|
Step 2:测试留言板(存储型 XSS 确认)
|
|
页面渲染结果:<b> 标签生效,<script> 不执行,<img onerror> 触发弹窗。
Step 3:提交 XSS Payload 窃取 Admin Cookie
使用 img onerror + fetch 将 admin cookie 回传到留言板。注意引号嵌套:onerror 属性用双引号包裹,内部 JS 字符串用单引号。
|
|
Step 4:等待 Admin Bot 访问,刷新留言板获取 Flag
Admin bot 定期访问 /guestbook 审核留言,浏览器执行 onerror 中的 JS:
- 读取
document.cookie→flag=flag{xxx} - POST 到
/guestbook→content=FLAG:flag{flag{xxx}}
刷新留言板即可看到回传的 flag。
Flag: flag{6d73dd52-37ae-4b9c-9020-c6b5dff252af}
EZXSS_1(存储型 XSS + 标签/属性过滤绕过)
漏洞类型: 存储型 XSS(Stored Cross-Site Scripting)+ 过滤绕过
题目分析:
与 EZXSS 基础题相同的网站结构,但增加了 WAF 过滤:
- 搜索框 HTML 转义,反射型 XSS 不可行
- 留言板存储型 XSS,但服务端对输入进行了关键词过滤
- 无 CSP 头
过滤测试:
经逐步测试,以下关键词/标签被过滤:
<script>标签 → 过滤<img>标签 → 过滤<body>标签 → 过滤(同时影响 fetch 的body属性名)<iframe>标签 → 过滤
以下标签可用:
<svg>✓<input>✓<details>✓
绕过思路:
- 标签绕过:用
<svg onload>替代<img onerror> - body 属性绕过:用
XMLHttpRequest.send()替代 fetch 的 body,或用属性名拼接['bo'+'dy']
Step 1:测试过滤规则
|
|
Step 2:绕过 body 属性过滤 — 方法1 XMLHttpRequest
用 XMLHttpRequest.send() 替代 fetch 的 body 属性,完全避免使用 body 关键词:
|
|
Step 3:绕过 body 属性过滤 — 方法2 属性名拼接
仍然使用 fetch,但用 ['bo'+'dy'] 拼接绕过 body 关键词检测:
|
|
Step 4:提交 payload 并获取 flag
多提交几次,等待 admin bot 访问留言板触发 XSS,刷新页面即可看到回传的 flag。
Flag: flag{36708ba7-72e9-4f53-8778-25d7933bbaa9}
EZXSS_2(存储型 XSS + 多重过滤绕过)
漏洞类型: 存储型 XSS(Stored Cross-Site Scripting)+ 多重过滤绕过
题目分析:
在 EZXSS_1 基础上增加了更严格的 WAF:
<script>、<img>、<body>标签过滤var、body、new Image等关键词过滤- 事件处理器与标签之间有空格时被过滤(如
<svg onload=...>) - HTML 中的
>会截断属性值
过滤测试:
|
|
绕过思路:
- 标签绕过:
<svg/onload>用/替代空格绕过检测 - body 属性绕过:用
XMLHttpRequest.send()替代 fetch 的 body - 特殊字符绕过:用
eval(atob('base64'))避免>截断 HTML 属性
Step 1:确认过滤规则
|
|
Step 2:构造 Payload — XMLHttpRequest + eval(atob(…))
JS 代码(避免 var、body 关键词):
|
|
Base64 编码后用 eval(atob(...)) 执行:
|
|
Step 3:提交 payload 并获取 flag
多提交几次,等待 admin bot 访问触发 XSS。
Flag: flag{26d8fef3-2dc5-45fe-ba10-902b6ea939c2}
EZXXE
EZXXE(基础 XXE 文件读取)
漏洞类型: XML 外部实体注入(XXE - XML External Entity Injection)— 本地文件读取
题目分析:
目标是一个 XML 解析服务,页面包含一个表单,用户输入 XML 内容后提交到 xml.php 进行解析,解析结果直接回显在页面上。
关键点:
- 表单提交到
xml.php,POST 方法,参数名为contract - 服务端使用 XML 解析器处理用户输入
- 解析结果直接回显,无过滤
- 未禁用外部实体加载
攻击思路: 构造 XML 外部实体 → 引用本地文件 file:///flag → 服务端解析时读取文件内容 → 回显 flag
Step 1:分析页面结构
页面有一个 textarea 表单,默认内容为 XML 声明:
|
|
提交到 xml.php,解析结果显示在 <pre id="output"> 中。
Step 2:构造 XXE Payload 读取 /flag
|
|
原理说明:
<!DOCTYPE foo [...]>定义文档类型,内部声明外部实体<!ENTITY xxe SYSTEM "file:///flag">声明一个外部实体xxe,指向本地文件/flag<root>&xxe;</root>在 XML 中引用该实体,解析器会将其替换为文件内容
Step 3:提交 Payload 获取 Flag
|
|
Flag: flag{b8107160-86f1-4998-9489-4a4fafdc97c4}
EZXXE_1(SVG 文件上传 XXE)
漏洞类型: XML 外部实体注入(XXE)— SVG 文件上传触发
题目分析:
与 EZXXE 不同,本题不是直接输入 XML,而是通过 SVG 文件上传触发 XXE:
- 页面是 SVG Vector Viewer,接受
.svg文件上传 - 表单使用
multipart/form-data编码 - 服务端解析 SVG 文件并提取元数据(尺寸、描述等)
- SVG 是基于 XML 的格式,可在其中嵌入 XXE payload
攻击思路: 构造恶意 SVG 文件 → 嵌入 XXE 外部实体 → 上传到服务端 → 服务端解析 SVG 时读取本地文件 → 返回文件内容
关键发现: 服务端解析 SVG 后提取 <desc> 标签内容作为 “Analysis Result” 显示,因此 flag 需要通过 <desc> 标签输出。
Step 1:分析上传功能
页面表单结构:
|
|
上传后服务端解析 SVG 并返回:尺寸、<desc> 标签内容等。
Step 2:构造恶意 SVG 文件
|
|
原理说明:
- SVG 是 XML 格式,支持 DOCTYPE 声明
- 声明外部实体
xxe引用file:///flag - 将实体放在
<desc>标签中,服务端解析后会显示描述内容
Step 3:上传并获取 Flag
|
|
服务端返回:
|
|
Flag: flag{f9583848-22b0-4855-95e2-c3c5a47b5c23}
EZXXE_2(XXE + WAF 关键词过滤绕过)
漏洞类型: XML 外部实体注入(XXE)+ WAF 绕过
题目分析:
与 EZXXE_1 同为 SVG 文件上传 XXE,但增加了 WAF 过滤:
file://协议被过滤flag关键词被过滤(无论出现在路径的任何位置)php://、expect://、data://等协议被过滤- 直接路径(如
/etc/passwd)可以读取,但含flag的路径被拦截
过滤测试:
|
|
绕过思路: 使用 URL 编码绕过关键词检测。WAF 在检测前未对输入进行 URL 解码,因此可以用 %67 替代 g 来绕过 flag 关键词检测。
Step 1:确认过滤规则
|
|
Step 2:URL 编码绕过
flag 中的 g 编码为 %67,路径变为 /fla%67,WAF 无法识别但服务端会解码:
|
|
Step 3:上传 SVG 获取 Flag
|
|
Flag: flag{9e08d0c8-88a7-4ec0-b3e9-592f7bda2ea8}
EZXXE_3 盲 XXE + OOB 外带
这一题页面给了非常明显的提示:
- 提交点是
POST /xml.php - 支持
DTD和外部实体 - 解析结果不会回显文件内容,只会告诉我们
OK或FAIL - 页面还直接提示了 “知道 flag 在根目录又如何”
所以这题本质上就是一道盲 XXE。因为文件内容不直接回显到页面里,常规的:
|
|
虽然能被正常解析,但前端只会显示 OK,并不能直接看到 flag。
先做一个基础验证,确认外部实体确实开启:
|
|
返回 OK,说明本地文件实体会被解析。
再构造一个不存在的文件:
|
|
这次返回 FAIL,说明页面上的 OK/FAIL 可以作为一个有效的盲测信号。随后把路径换成 /flag:
|
|
依旧返回 OK,由此确认根目录下的 /flag 确实存在。
接下来就不能再靠页面回显了,得改成 OOB 外带。这里我用 webhook.site 接收请求,先创建一个 token,例如:
|
|
会拿到一个类似下面的 token:
|
|
这一步之后,先用一个最小 payload 测试目标机器是否真的能主动向外发请求:
|
|
查询 webhook.site 的请求记录后,可以看到目标服务器确实访问了这个 URL,说明 OOB 链路是通的。
这一题还有一个很关键的点:data:// 外部 DTD 也能被解析。这样我们就不需要单独找公网主机去托管 evil.dtd,可以直接把恶意 DTD base64 后塞进 data:// 里,再由参数实体把 /flag 内容带到 webhook.site。
最终使用的 payload 如下:
|
|
这段 base64 解码后的 DTD 逻辑是:
|
|
思路就是:
- 用
php://filter/convert.base64-encode/resource=/flag读取并编码/flag - 用参数实体再拼出一个新的外部实体
%exfil - 让服务端主动请求
https://webhook.site/...?...,把 base64 形式的 flag 带出去
最后在 webhook.site 请求记录里拿到:
|
|
base64 解码后得到最终 flag:
|
|
EZSSRF
EZSSRF(基础 SSRF 内网端口探测)
漏洞类型: 服务端请求伪造(SSRF - Server-Side Request Forgery)— 内网端口探测
题目分析:
目标页面是一个"VIP 用户专属内网服务",提供一个 URL 输入框,服务端会代为请求用户输入的 URL。页面提示:
- 内部网关服务运行中,需输入正确的 IP/端口
- VIP 用户专用端口在 8888~9999 之间
关键点:
- 服务端对用户输入的 URL 发起请求,并将响应内容回显到页面上
- 可以访问
127.0.0.1(本地回环),说明存在 SSRF 漏洞 - 需要通过 SSRF 探测内网中 8888~9999 范围内的服务端口
攻击思路: 利用 SSRF 逐一探测内网端口 → 找到开放的内部服务 → 访问内部服务的敏感路径获取 flag
Step 1:确认 SSRF 可用
首先验证服务端确实会代为请求,访问 80 端口(返回页面本身):
|
|
返回页面自身内容,确认 SSRF 可用。
Step 2:探测内网端口
根据提示,端口范围在 8888~9999 之间。逐一扫描:
|
|
发现端口 9001 开放,返回 VIP 用户欢迎信息,并提示可以访问管理员 admin 面板。
Step 3:探测内部服务路径
在 9001 端口上探测 admin 相关路径:
|
|
Step 4:获取 Flag
|
|
页面响应中直接回显 flag:
|
|
Flag: flag{9581e95e-75a6-43e3-81a2-bb5668212002}
EZSSRF_1(SSRF + IP 黑名单绕过 — 十六进制编码)
漏洞类型: 服务端请求伪造(SSRF)+ IP 黑名单绕过
题目分析:
与 EZSSRF 基础题相同的网站结构,提供一个 URL 输入框让服务端代为请求,但增加了 IP 黑名单过滤:
- 直接使用
127.0.0.1→ 返回Blocked IP - 使用
localhost→ 返回Blocked host - IPv6
[::1]→ 连接失败(服务端不支持 IPv6) - 页面 HTML 注释提示:VIP 用户专用端口在 8888~9999 之间
- 服务端返回响应状态码和内容长度,可作为端口探测信号
过滤测试:
|
|
绕过思路: 服务端对 IP 黑名单的检测仅匹配点分十进制格式(127.0.0.1)和 localhost 字符串,但底层的 HTTP 客户端库(如 PHP cURL)支持多种 IP 表示方式。使用十六进制编码 0x7f000001(即 127.0.0.1 的十六进制表示)即可绕过黑名单检测。
Step 1:确认 SSRF 及 IP 黑名单存在
|
|
Step 2:十六进制编码绕过 IP 黑名单
127.0.0.1 的十六进制表示为 0x7f000001(127=0x7F, 0=0x00, 0=0x00, 1=0x01):
|
|
Step 3:探测内部服务路径
在 9001 端口上探测 admin 相关路径:
|
|
Step 4:获取 Flag
|
|
页面响应中直接回显 flag:
Flag: flag{1160e55c-f0a1-40f9-b15a-31c73d0654dc}
EZSSRF_2(SSRF + IP 多重编码绕过 — 混合十六进制表示法)
漏洞类型: 服务端请求伪造(SSRF)+ IP 黑名单多重编码绕过
题目分析:
在 EZSSRF_1 的基础上,本题进一步加强了 IP 黑名单过滤,封堵了常见的 IP 编码绕过方式:
127.0.0.1→Blocked IP0x7f000001(纯十六进制)→Hex IP not allowed0177.0.0.1(八进制)→Octal IP not allowed2130706433(十进制整数)→Integer IP not allowed127.1(短格式)→Loopback range blocked0x7f.0.0.1(混合十六进制表示法)→ 绕过成功!HTTP 200
过滤测试:
|
|
绕过思路: 服务端分别检测了纯十六进制(0x7f000001)、八进制(0177.0.0.1)、整数(2130706433)等格式,但遗漏了混合十六进制表示法 0x7f.0.0.1——只有第一个八位组用十六进制 0x7f(=127)表示,其余为正常十进制。底层 HTTP 客户端库(PHP cURL)能正确解析这种混合格式,但 WAF 的正则未覆盖此模式。
Step 1:确认多重过滤规则
|
|
Step 2:混合十六进制表示法绕过
将 127 写为 0x7f,其余保持十进制,组成 0x7f.0.0.1:
|
|
Step 3:探测内部服务路径
|
|
Step 4:获取 Flag
|
|
Flag: flag{7b23f7e3-92fb-4a65-95d5-091852f917af}
EZSSRF_3(SSRF + file:// 协议本地文件读取)
漏洞类型: 服务端请求伪造(SSRF)— file:// 协议本地文件读取
题目分析:
目标是一个 “URL Fetcher - Enterprise Edition” 页面,提供一个 URL 输入框,服务端会代为请求用户输入的 URL 并回显响应内容。
关键点:
- 表单通过 POST 提交,参数名为
url - 服务端对用户输入的 URL 发起请求,并将响应内容(HTTP 状态码、内容类型、响应大小、响应体)回显到页面上
- HTML 注释泄露提示:
<!-- flag在/flag.txt里 --> - 与 EZSSRF / EZSSRF_1 / EZSSRF_2 不同,本题没有内网端口探测的场景,也没有 IP 黑名单过滤
- 本题的核心考点是:服务端未限制请求协议,允许使用
file://协议直接读取本地文件
攻击思路: 利用 SSRF 的 file:// 协议 → 直接读取服务器本地的 /flag.txt 文件 → 回显 flag
Step 1:分析页面结构
页面包含一个 POST 表单,参数 url,HTML 注释中泄露了 flag 位置:
|
|
Step 2:确认 SSRF 可用
先用 http://127.0.0.1/ 测试,返回页面自身内容(HTTP 200, 9388 bytes),确认 SSRF 正常工作:
|
|
Step 3:使用 file:// 协议读取本地 flag
直接使用 file:///flag.txt 读取服务器本地文件:
|
|
服务端返回:
|
|
分析: 本题与前几题 EZSSRF 系列不同,没有 IP 黑名单、端口范围限制等防护,而是考察对 file:// 协议的理解。服务端使用 cURL 等 HTTP 客户端库发起请求时,支持 file://、ftp://、gopher:// 等多种协议。当未对协议类型做白名单限制时,攻击者可以直接用 file:// 读取服务器本地任意文件。
Flag: flag{b83b873d-5858-4624-8fc0-4d73dbbd33e8}
EZSSRF_4(SSRF + file:// 读源码 + gopher 协议构造 POST 请求)
漏洞类型: 服务端请求伪造(SSRF)— file:// 本地文件读取 + gopher 协议内网 POST 请求伪造
题目分析:
目标是一个 “ImageHub - Image Upload Service”,提供 URL 图片上传功能,服务端会代为请求用户输入的 URL 并将响应作为图片展示。
关键点:
- 表单通过 POST 提交,参数名为
image_url - 页面明确说明支持 HTTP、HTTPS、FTP、Gopher、File 五种协议
- 服务端使用 PHP cURL 发起请求,支持
CURLOPT_FOLLOWLOCATION(跟随重定向) - 无 IP 黑名单、无协议过滤
攻击思路: 第一步用 file:// 读取源码,发现 flag.php 有 IP 限制且需要 POST JSON;第二步用 gopher:// 协议构造原始 HTTP POST 请求绕过限制。
Step 1:确认 file:// 协议可用
|
|
返回 root:x:0:0:root:/root:/bin/bash...,确认 file:// 协议可读取本地文件。
Step 2:枚举 Web 目录文件
通过 /proc/self/cwd/ 列出 Web 根目录:
|
|
返回文件列表:styles.css、template.html、index.php、flag.php
Step 3:读取 index.php 源码
|
|
源码显示服务端直接将用户 URL 传给 cURL,无任何过滤:
|
|
Step 4:读取 flag.php 源码,分析访问条件
|
|
关键源码:
|
|
三个条件:
- IP 限制:
REMOTE_ADDR必须是127.0.0.1或localhost - 请求方法: 必须是 POST
- 请求体: JSON 格式
{"action": "get_flag"}
Step 5:HTTP SSRF 验证 IP 限制可绕过
用 HTTP 协议访问 flag.php,因为请求从服务器自身发出,REMOTE_ADDR 为 127.0.0.1,IP 检查通过:
|
|
返回 flag.php 源码(GET 请求返回源码),说明 IP 限制已通过。但 GET 请求无法获取 flag,必须发 POST。
Step 6:用 gopher 协议构造原始 HTTP POST 请求
因为 SSRF 表单只能控制 URL,无法控制 cURL 的请求方法,所以用 gopher:// 协议直接构造原始 HTTP POST 请求发给 127.0.0.1:80:
|
|
URL 解码后的原始 HTTP 请求:
|
|
⚠️ 关键踩坑点: Content-Length 必须精确等于请求体的字节数({"action": "get_flag"} = 22 字节)。如果长度不对,Apache 会等待剩余数据导致超时。
Step 7:获取 Flag
服务端返回:
|
|
分析: 本题综合考察了两个 SSRF 技巧:
- file:// 协议读源码 — 通过
/proc/self/cwd/定位 Web 目录,读取flag.php源码分析访问条件 - gopher 协议构造 POST 请求 — SSRF 表单只能让 cURL 发 GET 请求,但
gopher://协议可以发送任意原始 TCP 数据,从而构造 POST 请求绕过方法限制
Flag: flag{deebce61-d165-4842-b046-1ed4c168702b}
EZSSRF_5(SSRF + Docker API 远程命令执行)
漏洞类型: 服务端请求伪造(SSRF)— Docker API 未授权访问 + 容器内命令执行
题目分析:
目标是一个 “URL 检测” 页面,提供 URL、HTTP 方法(GET/POST)和请求体三个输入项,服务端会代为请求用户指定的地址。
关键点:
- 表单支持 GET/POST 方法和自定义请求体
- 页面提示仅支持 http/https 协议
- HTML 注释泄露关键信息:
<!-- 内网依赖:Docker API 127.0.0.1:2375(运维文档) --> - Docker API 默认监听 2375 端口,无认证即可访问
攻击思路: 通过 SSRF 访问内网 Docker API → 列出容器 → 在目标容器内创建 exec 实例 → 启动 exec 执行命令读取 flag
Step 1:发现 Docker API 注释
页面 HTML 注释泄露了内网 Docker API 地址:
|
|
Step 2:确认 SSRF 可用并探测 Docker API
|
|
返回 HTTP 200 | OK,确认 Docker API 可访问。
Step 3:列出容器信息
|
|
返回容器信息,发现:
- 容器名:
ctf_container - 容器 ID 前缀:
4f1d60ea7287 - 镜像:
ubuntu:latest - 状态:
running - IP:
172.17.0.2
Step 4:在容器内创建 exec 实例
通过 Docker API 的 /containers/{id}/exec 端点在容器内创建命令执行实例:
|
|
返回 exec ID:execc7353f7a7b01d7c9f462624f596456ce
Step 5:启动 exec 获取 flag(关键踩坑)
|
|
⚠️ 关键踩坑点: exec 的 Tty 参数决定了输出格式:
Tty: false→ Docker API 返回 multiplexed 流,每个帧有 8 字节头(4 字节流类型 + 4 字节长度),SSRF 代理只读取了前几个字节,返回乱码Tty: true→ Docker API 返回原始文本流,SSRF 代理可直接读取
使用 Tty: true 后,服务端返回:
|
|
分析: 本题考察 SSRF + Docker API 未授权访问的组合利用:
- 信息收集 — HTML 注释泄露内网 Docker API 地址
- Docker API 利用 — Docker API 默认无认证,通过 SSRF 可以列出容器、创建 exec、执行命令
- exec 流格式 — Docker exec API 的 TTY 参数影响输出格式,
Tty:true返回原始文本适合 SSRF 场景
Flag: flag{de3aff20-53b5-40d3-bdbc-88a738997ff0}
EZSSRF_6(SSRF + gopher 协议 Redis 未授权访问)
漏洞类型: 服务端请求伪造(SSRF)— gopher 协议 + Redis 未授权访问
题目分析:
目标是一个 “URL请求器” 页面,仅提供 URL 输入框,服务端代为请求用户输入的地址。
关键点:
- 表单仅一个 URL 输入框,POST 提交
- 支持 http/https/gopher 协议(
dict://等不支持) file://返回 “Invalid URL”- HTML 无注释、无明显提示
攻击思路: 通过 SSRF 探测内网服务 → 发现 Redis 存储 flag → 用 gopher 协议发送 Redis 命令获取 flag
Step 1:确认 SSRF 可用
|
|
返回 Status: 200,页面内容为自身,SSRF 正常工作。
Step 2:探测内网端口
|
|
返回 “Empty reply from server” — 端口开放但不返回 HTTP(Redis 服务)。
Step 3:读取 robots.txt 发现配置文件
|
|
返回:
|
|
Step 4:读取 config.bak 发现 Redis 存储 flag
|
|
返回:
|
|
确认 flag 存储在 Redis 的 flag 键中。
Step 5:用 gopher 协议访问 Redis(踩坑:连接超时)
Redis 使用 RESP 协议,不返回 HTTP 响应。用 gopher 发送简单命令:
|
|
返回 “Operation timed out after 4002 milliseconds with 49 bytes received” — Redis 确实返回了数据(49 字节),但连接未关闭导致超时,SSRF 脚本不显示错误时的响应内容。
Step 6:使用 RESP 协议 + 流水线 QUIT 获取 flag(关键)
问题在于 Redis 保持连接等待更多命令。解决方案:使用 RESP 协议 发送命令并用 流水线(pipeline) 方式追加 QUIT 命令,让 Redis 执行完后主动关闭连接。
|
|
URL 解码后的 RESP 数据:
|
|
服务端返回:
|
|
其中 $42 是 RESP 协议的批量字符串长度前缀,+OK 是 QUIT 命令的响应。
⚠️ 关键踩坑点:
- 连接超时问题 — Redis 默认保持连接,gopher 发送单条命令后连接不关闭,SSRF 脚本超时不显示响应内容
- 解决方法 — 在 RESP 流水线中追加
QUIT命令(*1\r\n$4\r\nQUIT\r\n),Redis 执行完 GET 后处理 QUIT 主动关闭连接 - RESP 协议格式 — 必须使用 RESP 二进制协议(
*N\r\n$Len\r\n...),不能用内联协议(GET flag\r\n),否则 Redis 可能无法正确解析
Flag: flag{0f9d1155-c030-4996-9b7e-c0d999274f44}
EZJWT
EZJWT(JWT 弱密钥破解)
漏洞类型: JWT(JSON Web Token)弱密钥 / 弱密钥签名绕过
题目分析:
目标是一个"安全审计控制台"页面,页面内容受 JWT 权限控制。首次访问时服务端会发放一个 role: guest 的 JWT Cookie,只有 role: admin 的用户才能看到 flag。
关键点:
- 首次访问
/会获得两个 Cookie:boot_id和auth_token(JWT) auth_token的 JWT 使用 HS256 算法签名,payload 中包含username和role字段- 页面检查
role值,只有admin权限才会在页面上显示 flag - JWT 签名密钥为弱密钥,可被爆破破解
- 需要同时携带
boot_id和auth_token两个 Cookie 才能正常获取页面内容
攻击思路: 获取服务端发放的 JWT → 分析 JWT 结构 → 爆破弱密钥 → 伪造 admin 角色的 JWT → 替换 Cookie 获取 flag
Step 1:获取并分析 JWT
首次访问页面,服务端通过 Set-Cookie 返回 JWT:
|
|
响应头中包含:
|
|
Step 2:解码 JWT
将 JWT 的三段用 base64 解码:
|
|
分析得出:
- 算法:HS256(HMAC-SHA256 对称签名)
- 身份:
username: guest, role: guest - 目标:将
role篡改为admin
Step 3:爆破 JWT 密钥
HS256 使用共享密钥签名,服务端若使用弱密钥则可被爆破。用常见弱密钥字典尝试:
|
|
结果:密钥为 secret(最常见的 JWT 弱密钥之一)。
Step 4:伪造 admin 角色的 JWT
用破解的密钥签发一个 role: admin 的新 JWT:
|
|
生成的 admin JWT:
|
|
Step 5:携带伪造 JWT 获取 Flag
⚠️ 关键踩坑点:必须同时携带 boot_id 和 auth_token 两个 Cookie,否则页面返回空白。
|
|
页面返回完整的"安全审计控制台",权限显示为 admin,flag 出现在页面中:
Flag: flag{fe87638b-01bb-4be0-ac4f-94159d6ebdec}
EZJWT_1(RSA Key Confusion 算法混淆攻击)
漏洞类型: JWT 算法混淆攻击(RSA Key Confusion / Algorithm Confusion)
题目分析:
目标页面与 EZJWT 相同,是一个"安全审计控制台",但本题使用了 RS256(RSA-SHA256 非对称签名)而非 HS256。页面泄露了 RSA 公钥,可通过算法混淆绕过签名验证。
关键点:
- JWT 使用 RS256 算法(
alg: RS256),payload 中role: guest - 页面暴露了 RSA 公钥路径
/pubkey.pem(实际路径/var/www/html/pubkey.pem) /private.pem存在但返回 403 禁止访问- 页面提示"采用了非对称加密技术来确保系统安全"
- 服务端在验证 JWT 时,若
alg为 HS256,会错误地使用 RSA 公钥的 PEM 文件内容作为 HMAC 密钥
攻击原理:
RSA Key Confusion 是 JWT 中经典的算法混淆漏洞。当服务端同时支持 RS256 和 HS256 时:
- RS256 使用私钥签名、公钥验证(非对称)
- HS256 使用同一个密钥签名和验证(对称)
如果攻击者将 JWT 头部的 alg 从 RS256 改为 HS256,服务端会用 RSA 公钥 作为 HMAC 密钥来验证签名。由于攻击者拥有公钥,可以自行生成合法的 HMAC 签名。
攻击思路: 获取公钥 → 将 JWT 算法改为 HS256 → 用公钥 PEM 文件内容作为 HMAC 密钥签名 → 伪造 admin 角色 JWT
Step 1:分析 JWT 结构
|
|
解码 Cookie 中的 JWT:
|
|
Step 2:获取 RSA 公钥
页面源码中泄露了公钥路径:
|
|
返回:
|
|
Step 3:RSA Key Confusion — 伪造 admin JWT
将 alg 从 RS256 改为 HS256,用公钥 PEM 文件的原始字节作为 HMAC 密钥签名:
|
|
生成的伪造 JWT:
|
|
⚠️ 关键踩坑点: 必须使用 PEM 文件的原始字节(含 -----BEGIN PUBLIC KEY----- 头尾和换行符)作为 HMAC 密钥,而不是 DER 编码的二进制数据。
Step 4:携带伪造 JWT 获取 Flag
|
|
页面返回"安全审计控制台",权限显示为 admin,flag 出现在页面中。
Flag: flag{7892ef74-1e2f-44ff-b911-b6510d8475e6}
EZJWT_2(JWT 签名密钥泄露)
漏洞类型: JWT 签名密钥泄露 / 敏感文件暴露
题目分析:
目标页面与前两题相同,是一个"安全审计控制台",使用 HS256 算法签名的 JWT。本题的核心考点是 签名密钥被泄露在可访问的文件路径中。
关键点:
- JWT 使用 HS256 算法,头部包含
kid(Key ID)字段,值为key1 kid通常用于标识签名密钥,服务端会根据kid值去查找对应的密钥/keys/目录存在但返回 403 禁止目录列表/keys/key1文件可直接访问,返回签名密钥明文
攻击思路: 发现 /keys/ 目录 → 枚举密钥文件 → 获取签名密钥 → 伪造 admin 角色 JWT
Step 1:分析 JWT 结构
|
|
解码 Cookie 中的 JWT:
|
|
注意头部多了 kid: key1 字段,这是密钥标识符。
Step 2:发现密钥文件
通过路径枚举发现 /keys/ 目录存在(返回 403),尝试直接访问密钥文件:
|
|
返回签名密钥明文:
|
|
⚠️ 漏洞本质: 服务端将 JWT 签名密钥存放在 Web 可访问的 /keys/ 目录下,且文件无访问控制,任何人都可以直接读取。
Step 3:伪造 admin JWT
用泄露的密钥签发 role: admin 的 JWT:
|
|
生成的伪造 JWT:
|
|
Step 4:携带伪造 JWT 获取 Flag
|
|
页面返回"安全审计控制台",权限显示为 admin,flag 出现在页面中。
Flag: flag{4b331b4a-92cd-4a6f-a7a4-8e71370e321a}
EZJWT_3(JWT kid 参数 SQL 注入)
漏洞类型: JWT Header kid 参数 SQL 注入 → 密钥窃取/绕过签名验证
题目分析:
目标页面与前几题相同,是一个"安全审计控制台",使用 HS256 算法签名的 JWT。但本题的签名密钥存储在 MySQL 数据库中,且 kid 参数存在 SQL 注入漏洞。
关键点:
- JWT 使用 HS256 算法,头部包含
kid: "main-key"字段 - 签名密钥存储在 MySQL 的
jwt_keys表中(kid和secret两列) index.php.bak备份文件泄露了完整源码find_secret()函数直接将kid拼接到 SQL 语句中,未做任何过滤和转义
源码关键片段(index.php.bak):
|
|
$kid 直接来自 JWT Header 中的 kid 字段,用户完全可控。
攻击思路: 通过 kid 参数注入 SQL → 用 UNION SELECT 让查询返回一个已知密钥 → 用该密钥伪造 admin 角色的 JWT → 获取 flag
Step 1:发现备份文件泄露源码
|
|
源码泄露了数据库连接信息、SQL 查询逻辑以及 flag 文件路径 /flag_bei_conquer_eat_le_bu_zai_zhe_li。
Step 2:分析 SQL 注入点
find_secret() 中的 SQL 语句:
|
|
通过控制 kid 值,可以注入 UNION SELECT 让查询返回任意字符串作为密钥。
Step 3:构造 SQL 注入 Payload
注入 kid = ' UNION SELECT 'mysecret' -- -,使最终 SQL 变为:
|
|
查询将返回 mysecret 作为签名密钥,这是一个我们已知的值。
Step 4:用注入的密钥伪造 admin JWT
|
|
Step 5:携带伪造 JWT 获取 Flag
|
|
页面返回"安全审计控制台",权限显示为 admin,flag 出现在页面中。
分析: 本题综合考察了两个关键点:
- 敏感文件泄露 —
index.php.bak泄露了完整 PHP 源码,暴露了 SQL 注入漏洞和数据库结构 - JWT kid SQL 注入 —
kid参数直接拼接到 SQL 查询中,通过UNION SELECT注入可控密钥,从而伪造任意角色的 JWT
Flag: flag{183315a0-7f7a-4ef6-85d1-cc48b4126a35}
EZPOP
EZPOP(基础 PHP 反序列化 — __destruct 魔术方法触发)
漏洞类型: PHP 对象注入(POP - PHP Object Injection)— unserialize() + __destruct 魔术方法
题目分析:
页面直接展示了 PHP 源码,核心逻辑非常简单:
|
|
关键点:
$flag变量通过file_get_contents("/flag")读取 flag 文件内容,存储在全局变量$GLOBALS['flag']中ShowFlag类的__destruct()析构方法会在对象销毁时自动调用- 当
$this->show为true时,__destruct()会echo $GLOBALS['flag']输出 flag - 用户通过
$_GET['data']传入的数据会直接传给unserialize()进行反序列化
攻击思路: 构造一个 ShowFlag 对象,将 $show 属性设为 true,序列化后通过 data 参数传入 → unserialize() 还原对象 → 脚本结束时 __destruct() 被触发 → $show 为 true → 输出 flag
Step 1:分析源码,确定利用链
反序列化利用链:
unserialize($_GET['data'])— 用户可控输入- 还原
ShowFlag对象,$show属性被设为true - 脚本执行结束,PHP 引擎销毁对象,触发
__destruct() if ($this->show)条件成立,echo $GLOBALS['flag']输出 flag
Step 2:构造序列化 Payload
在 PHP 中,序列化一个 ShowFlag 对象($show = true)的结果:
|
|
格式说明:
O:8:"ShowFlag"— 对象(Object),类名长度 81— 有 1 个属性s:4:"show"— 字符串属性名,长度 4,值为showb:1— 布尔值,true
Step 3:提交 Payload 获取 Flag
|
|
服务端 unserialize() 还原对象后,脚本结束时 __destruct() 自动执行。注意 flag 输出在 highlight_file() 生成的 HTML </code> 标签之后(因为析构在脚本最后才触发),需要查看完整 HTTP 响应才能看到。
|
|
Flag: flag{f94fa85e-7281-44de-ab33-fd8c99662f0a}1