← 返回 Day 149 正文

Day 149 · 图解

Day 149 图解 — JWT 结构与安全验证流程

图 1:JWT 三段结构与编解码过程

┌──────────────────────────────────────────────────────────────────────┐
│                          JWT = aaa.bbb.ccc                            │
└──────────────────────────────────────────────────────────────────────┘

   签发(encode)                         验证(decode)
 ┌───────────────┐                     ┌───────────────┐
 │ Header  JSON  │                     │ 拆成 3 段      │
 │ {"alg","typ"} │                     └───────┬───────┘
 └───────┬───────┘                             │
         │ base64url                           ▼
         ▼                            ┌──────────────────┐
   ┌───────────┐                      │ 解析 Header 取 alg│
   │  header   │──┐                   └────────┬─────────┘
   └───────────┘  │                            │
 ┌───────────────┐│                            ▼
 │ Payload  JSON ││                   ┌──────────────────┐
 │ {"sub","exp"} ││                   │ alg 在白名单内?   │ 否 → 拒绝 ❌
 └───────┬───────┘│                   └────────┬─────────┘
         │ base64url                  是 │
         ▼         │                      ▼
   ┌───────────┐   │             ┌──────────────────┐
   │  payload  │───┼────────────►│ 用密钥重算签名     │
   └───────────┘   │             └────────┬─────────┘
                   │                      │
                   ▼                      ▼
      ┌─────────────────────────┐  ┌──────────────┐
      │ HMAC/RSA/ECDSA(秘密)     │  │ 签名一致?     │ 否 → 拒绝 ❌
      │ sign(header.payload)    │  └──────┬───────┘
      └───────────┬─────────────┘         │ 是
                  ▼                       ▼
            ┌───────────┐        ┌──────────────────┐
            │ signature │        │ 校验 exp/nbf/iss/aud│ 否 → 拒绝 ❌
            └───────────┘        └────────┬─────────┘
                                          │ 全部通过
                                          ▼
                                     信任 payload ✅

图 2:HMAC vs RSA 信任模型

── HMAC(HS256):对称,一把密钥两个人用 ──────────────────

   Auth Server ────[同一把 secret]──── API Server
        │                                  │
     签发用 secret                     验证也用 secret
        └────────► 谁有密钥谁就能伪造 ◄────────┘
        (密钥泄露 = 全线沦陷)

── RSA / ECDSA(RS256/ES256):非对称,单向能力 ────────────

   Auth Server                                API Server
   ┌──────────┐                              ┌──────────┐
   │ 私钥(保密)│                              │ 公钥(公开)│
   └────┬─────┘                              └────┬─────┘
        │ 只能签发                          只能验证 │
        └──────────► token ────────────────────────►│
                                                验签通过 ✅
   公钥泄露无害;只有私钥泄露才能伪造

图 3:alg 混淆攻击(HS/RS Confusion)

Mermaid 时序图:

sequenceDiagram participant Atk as 攻击者 participant Srv as 服务端(持 RSA 公钥) Note over Srv: 服务端配置为 RS256 验证<br/>但代码按 Header.alg 动态选算法(错误) Atk->>Atk: 取到公开的 RSA 公钥文本 Atk->>Atk: 伪造 Header {"alg":"HS256"} Atk->>Atk: 用「公钥文本」当 HMAC 密钥签名 Atk->>Srv: 发送 header.payload.sig Srv->>Srv: 读到 alg=HS256 → 用公钥当 HMAC 密钥验签 Srv-->>Atk: 验签通过 → 伪造管理员身份 ✅ Note over Srv: 正确做法:algorithms=["RS256"] 硬编码
sequenceDiagram
    participant Atk as 攻击者
    participant Srv as 服务端(持 RSA 公钥)
    Note over Srv: 服务端配置为 RS256 验证<br/>但代码按 Header.alg 动态选算法(错误)
    Atk->>Atk: 取到公开的 RSA 公钥文本
    Atk->>Atk: 伪造 Header {"alg":"HS256"}
    Atk->>Atk: 用「公钥文本」当 HMAC 密钥签名
    Atk->>Srv: 发送 header.payload.sig
    Srv->>Srv: 读到 alg=HS256 → 用公钥当 HMAC 密钥验签
    Srv-->>Atk: 验签通过 → 伪造管理员身份 ✅
    Note over Srv: 正确做法:algorithms=["RS256"] 硬编码

图 4:攻击决策树(拿到一个 JWT 之后)

拿到 JWT
   │
   ├─ base64url 解 Header,看 alg
   │     ├─ alg=none ............→ 删签名段直接试(CVE 经典)
   │     ├─ alg=HS256 ...........→ 试弱密钥爆破 (hashcat -m 16500 / jwt_tool)
   │     ├─ alg=RS256/ES256 .....→ 试 alg 混淆、jku/x5u 注入
   │     └─ 未知 alg ............→ 查库版本 CVE
   │
   ├─ 看 kid ..................→ 路径穿越 / SQLi / 命令注入
   ├─ 看 jku / x5u ............→ 密钥注入
   ├─ 解 Payload
   │     ├─ 有无 exp?超长有效期?→ 重放窗口
   │     ├─ 有无 aud?...........→ 跨服务 token 混用
   │     └─ 有无敏感字段?.......→ 信息泄露
   └─ 改 Payload 重放 .........→ 越权 / 角色提升

图 5:正确验证的检查流水线

token ──►[① 格式 3 段?]──►[② alg 白名单?]──►[③ 重算签名]──►[④ exp/nbf]──►[⑤ iss]──►[⑥ aud]──► 放行
            │否                │否               │否            │否          │否        │否
            ▼                  ▼                ▼             ▼           ▼         ▼
          401                401              401           401         401       401
     (任何一步失败都必须拒绝,且不泄露"哪一步失败")