SECCON13

SEECON 13

复现学习一下SEECON 13 的题目

self-ssrf

拿到题目里的源码:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import express from "express";

const PORT = 3000;
const LOCALHOST = new URL(`http://localhost:${PORT}`);
const FLAG = Bun.env.FLAG!!;

const app = express();

app.use("/", (req, res, next) => {
if (req.query.flag === undefined) {
const path = "/flag?flag=guess_the_flag";
res.send(`Go to <a href="${path}">${path}</a>`);
} else next();
});

app.get("/flag", (req, res) => {
res.send(
req.query.flag === FLAG // Guess the flag
//如果请求中带的flag参数等于flag
? `Congratz! The flag is '${FLAG}'.` //输出flag
: `<marquee>🚩🚩🚩</marquee>`
);
});

app.get("/ssrf", async (req, res) => {
try {
const url = new URL(req.url, LOCALHOST);

if (url.hostname !== LOCALHOST.hostname) {
res.send("Try harder 1");
return;
}
if (url.protocol !== LOCALHOST.protocol) {
res.send("Try harder 2");
return;
}

url.pathname = "/flag";
url.searchParams.append("flag", FLAG);
res.send(await fetch(url).then((r) => r.text()));
} catch {
res.status(500).send(":(");
}
});

app.listen(PORT);

看不懂完全没思路,去看了某大佬的wp,直接分析到express框架的源码了,等等的逻辑,直接被吓死。。。。
自己打也许不能分析出来,但还是学习一下思路:

通过/ssrf访问flag时会在末尾加上参数flag,并给予参数正确flag值,
我的问题是,那直接不带任何参数去访问/ssrf不就能只有ssrf函数加上的正确的flag参数和值了吗?

然而还是怪我不了解node.js 的 express 框架。

app.use(“/“, …) 的作用范围:
这个中间件匹配所有路径(因为 / 是所有路径的前缀),所以它会在任何路由(如 /ssrf, /flag)之前执行。这是一个常见的“全局前置检查”。
所以简单来说,想要访问/ssrf就得带上flag参数,这样的话就无法实现空参数访问/ssrf了。

wp中,
解决办法就是通过express对url的处理来解题
express在处理相同的参数时会用逗号拼接:

1
/?flag=1&flag=flag  ->  req.query.flag = '1,flag'

这样会导致flag值与正确值不匹配

但是若是传入:ssrf/?flag[=]=asd

会导致两次解析不一致,其中express的qs解析一次,url解析一次:

url把flag[=]= 拆分为:
参数:flag[=
值: ]=asd

qs把 flag[=]= 解析为对象 { flag: { “=”: “asd” } }
此时 req.query.flag 是一个对象 { ‘=’: ‘asd’ },不是 undefined

第一次解析完成后,会导致qs认为flag是存在的,从而通过了’/‘中间件,进入到’/ssrf’,
之后经过’/ssrf’的处理,会变成:

ssrf/?flag[=]=asd&flag=(正确flag值)

然后解析:

url:
参数1:flag[
值:]=asd
参数2:flag
值: (正确flag)

qs就不会把url里两个不同的参数进行合并,所以就存在flag=正确值

感觉若是有node.js的开发经验,那这种题目应该算简单?可惜我学信安并没有很多开发经验。要是考到不了解的框架那不知道怎办了。

来自qwen:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
你的感受**非常真实,也非常普遍**,几乎所有从安全入门、没有深厚开发背景的人都会经历这个阶段。但请放心,你的思考方向完全正确,而且你已经迈出了最关键的一步——**理解了漏洞的本质,而不仅仅是“套用技巧”**

我们来深入聊聊你的担忧,并给你一些实用的建议。

---

### 1. “有开发经验就简单?” —— 部分正确,但不绝对

**有开发经验的优势**
- 熟悉框架的“正常行为”,更容易识别“异常行为”。
- 知道 `app.use()` 是中间件,`req.query` 是什么,`URLSearchParams` 怎么工作。
- 对 URL 编码、参数解析、HTTP 流程有直觉。

**但不代表“简单”**
- 这道题利用的是 `qs` 库在**嵌套参数 + 重复键名**下的**非预期行为**,即使是经验丰富的 Node.js 开发者,如果不专门研究过 `qs` 的源码或边界情况,也未必能立刻想到。
- CTF 题目的设计目标就是**挖掘框架/库的边缘 case 和逻辑矛盾**,而不是考察“日常开发能力”。

所以,这道题对开发者来说可能“更容易理解”,但要说“简单”,那也不一定。它更像是一个“**知道就知道,不知道就很难猜**”的知识盲点题。

---

### 2. “没开发经验怎么办?” —— 别怕,有方法!

你现在的状态是:**通过学习 WP,你补上了知识盲点,并且用自己的逻辑重新推导了一遍**。这比“凭空想出”更有价值!因为:

> **CTF 不是考试,而是研究。真正的安全研究者都是站在巨人的肩膀上。**

#### 🛠️ 如何弥补开发经验的不足?

| 方法 | 具体做法 | 效果 |
| :--- | :--- | :--- |
| **1. 学习基础 Web 开发概念** | 不需要精通一门语言,但要懂:<br>- HTTP 请求/响应结构(GET/POST, Headers, Query, Body)<br>- Cookie/Session<br>- 常见框架模式(路由、中间件、模板渲染)<br>- 数据库基础(SQL/NoSQL) | 建立“系统观”,不再把代码当黑盒 |
| **2. 主动查文档和源码** | 看到 `express`, `qs`, `flask`, `django` 等关键字:<br>- 立刻去搜官方文档或 GitHub。<br>- 重点看:**参数解析、路由匹配、安全机制**<br>- 例如:`qs` 的 README 明确写了它支持嵌套对象和数组。 | 把“未知”变成“已知” |
| **3. 搭建实验环境** | 本地写几行代码验证你的猜想:<br>```js<br>const qs = require('qs');<br>console.log(qs.parse('flag[=]=asd&flag=123'));<br>// 输出?<br>``` | 实践出真知,记忆更深刻 |
| **4. 关注“框架特性”而非“语言”** | Node.js/Python/PHP 只是载体,真正重要的是:<br>- Express 的中间件机制<br>- Flask 的 template injection<br>- Django 的 ORM 注入<br>掌握一种,触类旁通。 | 提升学习效率 |

---

### 3. “考到不了解的框架怎办?” —— 核心策略

当遇到完全陌生的框架时,别慌,按以下步骤走:

#### 🔍 步骤 1:快速识别技术栈
- 看文件名:`app.py` → Python/Flask? `server.js` → Node.js?
- 看依赖:`package.json` → Node.js, `requirements.txt` → Python
- 看错误信息:500 错误可能暴露框架名

#### 🧩 步骤 2:找“关键函数”和“数据流”
- 找输入点:`req.query`, `req.body`, `request.GET`, `request.POST`
- 找输出点:`res.send()`, `return render(...)`
- 找危险操作:`eval`, `exec`, `os.system`, `数据库查询`, `文件读写`, **SSRF 中的 `fetch`/`requests.get`**

#### 📚 步骤 3:查该函数的文档
- 搜索:`express url.searchParams.append``flask request.args.to_dict`
- 重点看:**如何处理重复参数?如何处理特殊字符?是否有已知 CVE?**

#### 💡 步骤 4:联想类似漏洞
- SSRF?→ 想办法绕过 host/protocol 检查
- SQLi?→ 想办法闭合引号或注释
- SSTI?→ 尝试 `{%}`, `{{}}`, `<%= %>`
- 参数污染?→ 试试 `?a=1&a=2`

---

### 🌟 最后一句话

> **“开发经验”不是门槛,而是你可以逐步积累的工具箱。**
>
> **而你现在已经拥有了最宝贵的资产——分析问题的能力。**

继续加油!你完全有能力攻克任何题目。💪

SECCON13
https://aidemofashi.github.io/2025/09/10/SECCON13/
作者
aidemofashi
发布于
2025年9月10日
许可协议