写 WP 不是写结果
写 WP 不是写结果
本文适合
已经能做出部分题目,但 WP 经常只剩 payload、脚本和 flag 的学习者。学完你能:把一道题写成可复现的证据链,让队友能按你的步骤重新判断、验证、利用和排查失败。
一句话判断
WP 不是 flag 展示页,而是"别人能复现你如何从现象走到结论"的解题证据链。
一篇糟糕的 WP 通常长这样:
打开题目,发现是 SQL 注入。
跑脚本,得到 flag。这等于没有写。别人不知道你为什么判断 SQL 注入,也不知道脚本为什么能跑,更不知道失败时该怎么排查。
题目中常见信号
写 WP 时最容易漏掉的不是最终步骤,而是判断依据。遇到这些信号,必须记录:
WP 里要保留什么:原始请求、修改后的请求、状态码、关键响应片段
WP 里要保留什么:完整报错、触发输入、报错前后的差异
WP 里要保留什么:输入长度、崩溃地址、寄存器或栈上的可控痕迹
WP 里要保留什么:file、xxd、strings、binwalk 的关键输出
WP 里要保留什么:已知参数、缺失参数、重复值、数学关系
WP 里要保留什么:输入 prompt、系统响应、工具调用或检索内容变化
判断一句话能不能写进 WP,有一个标准:它能不能被证据支持。
不能写:
一眼看出是 SQL 注入。应该写:
`id=1'` 返回 SQL syntax error,`id=1 and 1=1` 与 `id=1 and 1=2` 响应不同,因此判断 id 参数进入了 SQL 条件表达式。核心概念
WP 的核心是复现性,而复现性来自三件事:
- 观察可追溯:别人知道你第一眼看到了什么。
- 验证可重复:别人能复制你的命令、请求、脚本和输入。
- 结论可解释:别人知道每一步输出为什么支持下一步。
一个完整 WP 至少写清四段:
目的:交代入口和目标
常见内容:方向、附件、URL、目标、环境
目的:说明最初线索
常见内容:页面、文件类型、程序行为、明显字符串
目的:建立证据链
常见内容:改了什么、结果怎么变、说明什么
目的:稳定复现结果
常见内容:完整步骤、关键 payload、脚本、flag 获取方式
失败尝试不是垃圾信息。只要它帮你排除了一个假设,或者暴露了一个环境问题,就值得写进 WP。
最小分析流程
写 WP 时按这个流程整理:
- 先补题目信息:题目名、方向、材料、目标。
- 写第一手观察:不要直接跳到漏洞名。
- 按时间顺序列验证步骤:每一步都写"输入 -> 输出 -> 结论"。
- 把最终利用独立成稳定步骤:让别人不看中间尝试也能复现。
- 保留关键命令和输出:命令放代码块,输出只截关键行。
- 写失败排查:至少说明一个容易踩坑的点。
- 写复盘:把本题转化成下一次可执行动作。
推荐最小模板:
# 题目名
## 题目信息
- 方向:
- 材料:
- 目标:
- 环境:
## 初步观察
我先看到了:
这说明:
## 验证过程
| 步骤 | 输入/命令 | 结果 | 结论 |
|---|---|---|---|
| 1 | | | |
## 最终步骤
1. ...
2. ...
3. ...
## 失败排查
- 如果 ...,先检查 ...
## 复盘
- 本题关键点:
- 我卡住的原因:
- 下次先检查:最小验证示例
以最常见的 Web 参数题为例。
差的写法:
SQL 注入,union select 拿到 flag。S 级写法:
## 初步观察
页面 `/news.php?id=1` 返回新闻详情。`id=1` 和 `id=2` 返回不同文章,说明 `id` 参与后端查询。
## 验证过程
```bash
curl -i 'http://target/news.php?id=1'
curl -i 'http://target/news.php?id=2'
curl -i "http://target/news.php?id=1'"
curl -i 'http://target/news.php?id=1 and 1=1'
curl -i 'http://target/news.php?id=1 and 1=2'
```
`id=1'` 返回 SQL 语法报错,`1=1` 和 `1=2` 的页面内容不同,说明参数进入了 SQL 条件表达式。
接着使用 `order by` 判断列数:
```bash
curl -i 'http://target/news.php?id=1 order by 3'
curl -i 'http://target/news.php?id=1 order by 4'
```
`order by 3` 正常,`order by 4` 报错,说明查询结果有 3 列。最后用 `id=-1 union select 1,2,3` 找回显位,再读取表名、字段名和 flag。这段 WP 不只是告诉别人"用了 union",还告诉别人每一步为什么成立。
常见利用 / 解题路线
路线总览:
不同方向的 WP 重点不同:
必须保留的证据:原始请求、payload 请求、响应差异、关键 Header/Cookie
必须保留的证据:文件类型、字符串位置、交叉引用、比较逻辑、逆推脚本
必须保留的证据:checksec、崩溃输入、offset、泄露值、利用链脚本
必须保留的证据:参数、数学关系、验证脚本、解密结果
必须保留的证据:file、十六进制片段、提取命令、解码链
必须保留的证据:prompt、上下文来源、模型输出、边界绕过证据
WP 不是越长越好。它应该把关键链条写清楚,把无关截图、情绪和重复尝试压缩掉。
常见失败原因
原因:忽略了判断过程
排查动作:补"为什么怀疑这个方向"
原因:跳过原理解释
排查动作:补 payload 中每个关键字段的作用
原因:缺命令、环境或参数
排查动作:补完整命令、版本、路径、目标地址
原因:关键输出没有转成文字
排查动作:提取状态码、报错、地址、密钥等文字证据
原因:没有结论归纳
排查动作:每一步后加一句"这说明"
原因:看不出排查过程
排查动作:保留有信息量的失败和排除结论
原因:没转化成动作
排查动作:写"下次先检查 X",不要只写"多学习"
迷你案例
题目给了一个 ELF 程序,运行后要求输入 password。
差的 WP:
IDA 打开,找到密码,输入得到 flag。S 级 WP 应该这样写:
- 题目信息:附件是
login,目标是输入正确 password。 - 初步观察:
file login
strings -a login | grep -E 'Correct|Wrong|flag'- 验证过程:
strings发现Correct!和Wrong!,在 IDA/Ghidra 中查看Correct!的交叉引用,定位到比较逻辑。 - 关键结论:程序读取输入后逐字节异或
0x23,再与内置数组比较。 - 逆推脚本:
target = [0x4a, 0x56, 0x50, 0x50]
print(bytes(x ^ 0x23 for x in target))- 失败排查:如果脚本输出长度不对,先检查数组是否复制完整;如果本地正确远程错误,检查换行符是否被算入输入。
- 复盘:逆向题不是"找密码",而是"找输入在哪里被判断,以及判断前做了什么变换"。
这样的 WP 三个月后还能帮你快速找回同类题的入口。