复现不是「跑通 PoC」,而是「证明你懂了」
网上搜一个 CVE 编号,通常能直接找到别人写好的 exp,复制粘贴跑一遍,截图交差。这叫跑通,不叫复现。
真正的复现要回答四个问题:
- 这个漏洞为什么存在?(根因)
- 什么条件下能触发?(前提,比如某个模块必须开启)
- 补丁改了什么?(官方怎么修的)
- 修完之后还有没有绕过?(变体)
能答完这四问,你才具备「看到一个新 CVE 也能自己判断」的能力。本文用 CVE-2021-41773 做完整示例,把方法拆成四步。
合规前提:所有实验只在自己的隔离环境(本地 Docker、专用靶场)里做。对公网未授权目标发起任何请求都可能违法。本文示例使用公开的、已修复的漏洞,仅用于学习。
四步法总览
| 步骤 | 做什么 | 产出 |
|---|---|---|
| ① 读公告 | 确定组件、版本区间、触发条件 | 一句话的漏洞描述 |
| ② 读补丁 | diff 修复前后版本,定位改动点 | 根因假设 |
| ③ 搭环境 | 复刻受影响版本,隔离运行 | 可复现的靶机 |
| ④ 构造验证 | 最小 PoC 验证假设,再扩展影响 | 复现记录 + 报告 |
第一步:读公告,把范围锁死
优先看三个来源,按可信度排序:
- 厂商安全公告:最权威,会写明受影响版本、修复版本、是否需要特定配置
- GitHub Security Advisory:开源项目常用,通常直接附补丁链接
- NVD:结构化好,但 CVSS 分数别太当真,要结合你自己的场景看
以 CVE-2021-41773 为例
公告给出的关键信息:
| 项 | 内容 |
|---|---|
| 组件 | Apache HTTP Server |
| 受影响版本 | 2.4.49(2.4.50 修复不完整,对应 CVE-2021-42013) |
| 修复版本 | 2.4.51 |
| 类型 | 路径穿越(path traversal) |
| 前提 | 需要启用 mod_cgi,且目录配置允许 |
注意最后一行——「需要启用 mod_cgi」这个前提,决定了绝大多数默认部署其实不受影响。很多人看到 CVSS 9.8 就慌,却忽略了触发条件。这是读公告最重要的价值:判断你到底在不在影响面内。
顺手确认自己有没有中招
# 直接从响应头看版本,最快的初筛
curl -sI http://target/ | grep -i server
# 或者用 nmap 的 http-server-header 脚本
nmap -p 80,443 --script http-server-header target
如果版本是 2.4.49 或 2.4.50,再继续看配置;如果不是,直接结束,不用浪费时间。
第二步:读补丁,找出根因
这是业余和专业的最大分水岭。公告告诉你「发生了什么」,补丁告诉你「为什么」。
git clone https://github.com/apache/httpd.git
cd httpd
git log --oneline --all -- server/util.c | head -20
git diff 2.4.49 2.4.51 -- server/util.c
你会发现修复集中在路径规范化相关的函数(ap_normalize_path)。核心改动是:对编码后的路径做了更严格的校验,并且拒绝某些点的组合。
从 diff 反推漏洞原理
修复前的逻辑大致是:先把 %2e 解码成 .,再判断路径里有没有 ../。问题在于解码和校验的顺序与范围有缺口,导致 .%2e 这类组合能绕过检查,最终在文件系统层面被解释成 ..。
一句话根因:「校验用的路径」和「实际打开用的路径」不是同一个字符串。这是路径穿越类漏洞的经典模式,遇到同类问题可以直接套用这个思路去找。
为什么 2.4.50 还是不安全
2.4.50 打了补丁,但只堵了 %2e,没堵住二次编码 %%32%65。这引出了另一个编号 CVE-2021-42013。
这个细节值得记住:补丁不完整是常态。所以第四步「找变体」不是可选项,而是必做项。
第三步:搭环境,版本必须精确
最省事的方式是用 vulhub 这类预置靶场:
git clone https://github.com/vulhub/vulhub.git
cd vulhub/httpd/CVE-2021-41773
docker compose up -d
docker compose ps
起好后确认版本:
curl -sI http://127.0.0.1:8080/ | grep -i server
# 期望看到 Apache/2.4.49
三个环境纪律
- 隔离:靶机只监听
127.0.0.1或仅主机网络,绝不暴露到公网。历史上因为把漏洞环境开在公网被挖矿的案例不少。 - 版本精确:差一个小版本,漏洞可能就修了。容器镜像虽然方便,但要确认它真的是 2.4.49。
- 可销毁:
docker compose down -v能干净清掉,别在宿主机上装一堆东西。
第四步:构造最小 PoC
最小 PoC 的价值在于排除干扰。先只发一个请求,确认能读到文件,再谈别的。
curl --path-as-is "http://127.0.0.1:8080/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
两个细节决定成败:
--path-as-is:不加这个,curl 会自己把../规范化掉,你发出去的请求根本不是你想发的。这是新手最常踩的坑。- 路径必须从存在的目录开始:
/icons/是 Apache 默认存在的目录,从这里穿越才有意义。随便写个不存在的路径会先 404。
能返回 root:x:0:0:... 就说明复现成功。
从「读文件」扩展到「执行命令」
如果 mod_cgi 开启,理论上可以通过 /cgi-bin/ 下的脚本路径穿越执行命令。这一步涉及具体利用,本文不展开——重点是让你知道:同一个根因,影响面可以从「信息泄露」升级到「远程命令执行」。评估风险时要按最坏情况算。
验证完之后做什么
- 记录最小复现步骤:三条命令以内,别人能照着跑通
- 记录环境:镜像、版本、配置项(尤其
mod_cgi状态) - 拍下证据:请求原文 + 响应片段
- 写清修复建议:升级到 2.4.51+,或临时关闭
mod_cgi、限制目录权限
写一份能用的复现报告
报告不是「我复现成功了」,而是让别人能判断自己是否受影响。建议结构:
| 章节 | 写什么 |
|---|---|
| 基本信息 | 编号、组件、版本区间、CVSS、披露时间 |
| 影响判定 | 什么条件下才受影响(这是最有价值的部分) |
| 根因分析 | 结合补丁 diff 说明为什么 |
| 复现步骤 | 环境 + 命令 + 预期输出 |
| 影响评估 | 最坏情况能到什么程度 |
| 修复与缓解 | 升级版本 / 配置加固 / 临时规避 |
常见坑
| 现象 | 原因 | 处理 |
|---|---|---|
| curl 自动规范化了路径 | 缺少 --path-as-is |
加上,或用 Burp 手改原始请求 |
| 返回 404 而不是文件 | 起始目录不存在,或 mod_cgi 未开 |
换 /icons/ 等默认存在的目录 |
| 容器版本不对 | 镜像被更新过 | 用 tag 固定版本,docker inspect 确认 |
| 补丁 diff 看不出东西 | 版本 tag 拿错分支 | 用 release tag 对比,而不是 main |
| 复现成功但线上没反应 | 线上有 WAF 或前置代理做了规范化 | 说明缓解措施有效,报告里要写清 |
把方法迁移到别的漏洞
这套四步法不限于 Web。换到别的场景,只需要替换「组件」和「环境」:
- Java 反序列化:公告 → 找 gadget 链相关补丁 → 搭对应中间件版本 → 用 ysoserial 验证
- 内核提权:公告 → diff 内核补丁 → 起对应内核的虚拟机 → 编译 PoC
- Android / IoT:公告 → 找固件版本 → 模拟器或真机 → 抓日志验证
共同点永远是:先确定版本,再读补丁,最后才动手。跳过前两步直接找 exp,你永远只能复现别人公开过的漏洞。
小结
- 读公告锁范围:重点看「触发条件」,它决定了你的实际风险。
- 读补丁找根因:diff 是最诚实的老师,公告会含糊,代码不会。
- 环境要精确且隔离:版本错一个数字,结论就全错。
- 先最小 PoC,再谈影响:一条
curl能说明的事,不需要一整套框架。 - 补丁可能不完整:2.4.50 的教训,变体分析必须做。
延伸阅读:
免责声明:本文所有技术内容仅用于自有环境的安全研究、漏洞学习与防御建设。未经授权对他人系统进行漏洞验证或利用,可能触犯《中华人民共和国网络安全法》《刑法》第 285 条,后果自负。
- 1、本网站名称: 星宇安全实验室
- 2、本站永久网址:https://bk.zhaozhiqiang.pw/
- 3、本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
- 4、本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
- 5、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
- 6、本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
- 7、文章版权归作者所有,未经允许请勿转载。
















暂无评论内容