CTF-04:HeapDump 泄露与内存取证
第四道题。服务器把堆快照暴露出来了。 关联:上一题 ← CTF-03_SSTI与Jinja2模板注入.md | 下一题 → CTF-05_Cookie里的神秘配方.md
题目
一个博客网站。响应头暴露了 X-Powered-By: Express,是 Node.js。
首页没什么特别的,但 /api-docs 有 Swagger API 文档。翻了一下,发现一个接口:
GET /heapdump
Description: Diagnosing the memory allocation.
没有鉴权,直接点 Execute 就会下载一个堆快照文件。flag 就在里面。
解题过程
1. 发现技术栈
拿到响应头:
HTTP/1.1 200 OK
X-Powered-By: Express
ETag: W/"acd455-19e7e04134a"
Express.js,Node 系框架。W/ 开头的 ETag 是弱校验(weak validator)—— 服务器只保证语义等价,不保证字节级一致。静态文件默认会带 ETag,这里没什么问题,但暴露了文件 hash,可以作为信息补充。
2. 翻 API 文档
访问 /api-docs,Swagger UI 列出了所有接口,包括一个 /heapdump。
描述写的是 “Diagnosing the memory allocation."——直白到不能再直白,就是让你诊断内存用的。
没有鉴权、没有 token、没有 IP 白名单,直接 Execute 就能下载。
3. 下载 heapdump
GET /heapdump → 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="heapdump-1780230655952.heapsnapshot"
Content-Length: 11326549
大约 11MB 的 V8 堆快照文件。
4. 提取 flag
直接 grep,搜 pico(因为知道题目是 picoCTF 的,flag 格式是 picoCTF{...}):
grep -a "pico" heapdump-1780230655952.heapsnapshot
也可以用 Chrome DevTools:
- F12 → Memory 面板 → Load → 选择快照文件
- Ctrl+F 搜
pico
flag: picoCTF{Pat!3nt_15_Th3_K3y_8df117c1}
原理
HeapDump 是什么
V8 引擎的堆快照,记录某个时刻所有 JavaScript 对象在内存中的状态。包含:
- 所有变量值(字符串、对象、闭包)
- 调用栈
- 函数定义 Express 应用运行中,所有加载到内存的变量都在里面——包括 flag。
为什么会泄露
Node.js 有个常用的调试/诊断包叫 heapdump,可以主动生成快照用于排查内存泄漏。开发时很有用,但线上暴露出去就是事故。
正常的做法:
- 绑定到内网/localhost,不对外暴露
- 加鉴权,比如 header 校验或 token
- 只在 debug 模式下开启 这题的反面就是:没做任何保护,裸奔。
延伸:Node.js 是什么
这题出现了 X-Powered-By: Express 和 heapdump。要理解这题,得先搞清楚 Node.js 在整个 Web 里扮演什么角色。
浏览器里的 JS vs Node.js
浏览器(前端 JS): 操作 DOM、改页面样式、发请求
Node.js(后端 JS): 读数据库、写文件、处理 HTTP 请求、管理内存
语言一样(都是 JavaScript),舞台不同。Node.js = 在服务器上跑 JavaScript 的运行时。
Node.js 在后端的定位
Node.js 是业界主流后端技术之一。Netflix(SSR 层)、LinkedIn(API 网关)、Uber(核心行程匹配)、阿里/腾讯(BFF 层)都在用。
和后端 Java 生态的常见分工:
Java(Spring)—— 核心业务层
订单、支付、账户、风控,需要强类型、高稳定性
Node.js(Express)—— 边界和胶水层
SSR / BFF / API 聚合 / 实时通信 / CLI 工具
常见实践场景
| 场景 | 说明 |
|---|---|
| BFF | 前端和 Java 微服务之间的聚合层,拼数据格式 |
| API 网关 | 请求路由、限流、鉴权(类似你实习用的 Shepherd + Ocean) |
| SSR | 服务端渲染页面,Netflix、Airbnb 在用 |
| 实时通信 | WebSocket、SSE(你毕设里写过 SSE) |
| CLI 工具 | Webpack、Copilot CLI、VS Code 扩展——都是 Node.js |
| 轻量 CRUD | 快速搭 API,像这题的 Express 博客,十几行代码起一个服务 |
为什么会有 heapdump
Node.js 服务跑久了可能内存泄漏。开发者需要看内存里有什么,于是 Node.js 提供了 heapdump 机制——给内存拍一张快照,分析哪些对象没被释放。
正常链路:
发现内存泄漏 → 打 heapdump → 分析对象分布 → 修代码 → 部署
CTF 里的问题:这个 debug 接口没关,还暴露在 Swagger 文档里,等于把钥匙插门上还贴了张纸条。
理解了这些再看这题
你访问 Swagger 页面(/api-docs)
↓ 看到了 /heapdump 接口
↓ 点 Execute
Node.js 服务器收到请求
↓ 拍了内存快照(heapdump)
↓ 返回了 11MB 的文件,里面存着 flag
你搜到 flag
没有 X-Powered-By: Express,你不会知道这是 Node.js。
不知道 Node.js 是什么,就不明白 heapdump 是什么文件。
不知道 heapdump 存了什么,就不会想到搜 picoCTF。
每一条信息都是链条上的一环。
防御
| 做法 | 说明 |
|---|---|
| 不暴露诊断接口 | /heapdump、/debug 等路径不该出现在线上 |
| Nginx 层拦截 | 特定路径只允许内网 IP |
禁用 X-Powered-By |
app.disable('x-powered-by'),减少信息泄露 |
| 生产环境最小权限 | debug 工具只在开发/预发环境可用 |
与本仓库的关联
| 关联到 | 原因 |
|---|---|
| 软件运维 | 排查第一招"先定界”——看到 X-Powered-By 就知道是 Express 的活 |
| cleanCode-规范 | 防御性编程:不该暴露的东西别暴露 |
| 技术栈全景 | Node.js BFF 和你实习用的 Java 微服务 + 中间件,本质是同一套架构思路——服务拆分、API 网关、配置管理 |
| ../../AI/毕设原理速通.md | 毕设的 Express + FastAPI 架构 = Node.js BFF + Python AI 服务,这题里的 Express 就是 Node.js 后端 |