Web安全——JWT

JWT 安全研究:从本质到攻防

一、引言

在现代 Web 应用的身份认证体系中,JWT(JSON Web Token)几乎成为事实上的标准。前端工程师喜欢它的"无状态",后端工程师喜欢它的"轻便",架构师喜欢它的"分布式友好"。然而,作为安全研究员,我们更需要问一个问题:JWT 本质上是什么,它真的安全吗?

从本质上说,JWT 不是加密技术,而是 一种自包含的、基于签名的身份凭证格式。这一点决定了它的安全边界和潜在风险。

二、JWT 的本质

要理解 JWT 的安全问题,首先必须抓住它的本质。JWT = Header + Payload + Signature

  1. Header(头部)
    描述 Token 的类型和所使用的签名算法,例如:
    { "alg": "HS256", "typ": "JWT" }
  2. Payload(载荷)
    携带用户信息和声明(claims),如用户 ID、角色、过期时间等:
    { "sub": "alice", "role": "admin", "exp": 1693918080 }
  3. Signature(签名)
    用密钥和算法(如 HMAC-SHA256)对 Header 和 Payload 的组合进行签名,用来防止数据篡改。

这三个部分经过 Base64URL 编码后拼接为:header.payload.signature

1. 签名 ≠ 加密

一个常见的误区是"JWT 里的数据是安全的"。事实上,JWT 的 Payload 只是 Base64URL 编码,任何人都能解码查看。签名仅保证数据完整性,不能保证数据保密性。

2. 自包含的设计

JWT 的设计理念是"无状态"。服务端不再保存 Session,而是直接把身份和权限信息放进 Token。这样,任何一个服务只要能验证签名,就能确认用户身份。

这意味着:

  • JWT 本质上是一个"不可更改的身份证"
  • 一旦有人能伪造 Token,就等于拿到了系统的万能钥匙。

三、JWT 与传统 Session 的对比

理解 JWT 的本质,还需要把它与传统 Session 机制做对比。

|--------|--------------------|----------------------------|
| 特性 | Session | JWT |
| 存储位置 | 服务端存储(Redis/DB/内存) | 客户端存储(Cookie/LocalStorage) |
| 状态管理 | 有状态,需查 Session ID | 无状态,直接验证签名 |
| 分布式支持 | 需共享 Session 数据 | 天然支持分布式 |
| 撤销机制 | 随时删除 Session ID | 难以撤销,必须等过期 |
| 泄露风险 | Session ID 被窃取 | JWT 泄露或伪造 |

可以看到:

  • Session 把安全性寄托在"服务端掌控一切"上;
  • JWT 把安全性寄托在"签名正确性"上。

换句话说,JWT 的安全核心在于:签名密钥必须安全、签名算法必须正确实现、验证逻辑必须严格。

四、JWT 的安全弱点(本质层面)

从安全研究视角,JWT 的"方便"与"灵活"恰恰也是它的攻击面。以下是本质上的安全弱点:

1. Payload 明文可见

JWT 的 Payload 可被任何人解码。例如:

eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiYWxpY2UiLCAicm9sZSI6ICJhZG1pbiJ9.abc123

解码后立刻暴露用户身份和角色。如果开发者错误地把敏感数据(如密码、银行卡号)放进去,直接就是信息泄露。

2. 不可撤销性

JWT 一旦签发,服务端无法单独作废。

  • 如果 Token 有效期为 1 小时,被窃取后攻击者可以在这一小时内随意冒充用户。
  • 除非引入黑名单机制(破坏了"无状态"),否则无法解决。

3. 依赖签名的单点安全

JWT 的所有安全性都依赖签名:

  • 如果 secret 太弱,攻击者可字典爆破。
  • 如果验证逻辑有误,攻击者可能绕过签名。
  • 如果支持多算法,攻击者可进行算法混淆攻击。

4. 算法灵活导致攻击面

JWT 设计时支持多种签名算法,结果导致以下漏洞:

  • none 算法漏洞:如果服务端允许 alg=none,攻击者可伪造 Token。
  • RS/HS 混淆攻击:如果服务端错误地把公钥当作 HMAC secret 使用,就可能导致伪造签名。

5. 过期机制脆弱

JWT 的 exp、nbf、iat 等字段只是数字。

  • 一些开发者只验证签名,而忽略时间字段。
  • 这导致攻击者能长期使用过期 Token。

五、JWT 常见攻击与案例

作为安全研究员,在渗透测试和代码审计中,常见的 JWT 攻击手法包括:

1. none 算法攻击

早期一些库支持 alg=none,意味着不进行签名验证。攻击者只要把 Header 改成:

{ "alg": "none", "typ": "JWT" }

并删除 Signature,就能伪造任意用户身份。

2. 弱密钥爆破

如果服务端使用简单的密钥(如 secret、123456),攻击者可以使用工具(如 jwt-cracker)进行字典爆破,快速找到签名密钥,从而伪造 Token。

3. RS/HS 混淆攻击

如果系统支持 RS256(非对称加密),攻击者可以把 alg 改成 HS256,并用公钥作为 HMAC 密钥计算签名。由于服务端错误实现,可能导致验证通过。

4. KID 注入

一些 JWT 实现支持 kid(Key ID)字段,用于指定哪个密钥验证 Token。攻击者可能通过目录遍历、SQL 注入等方式在 kid 上做文章,进而绕过签名验证。

5. 重放攻击

JWT 缺乏内置的防重放机制。如果攻击者截获了合法 Token,可以在其过期前无限使用,造成会话劫持。

六、JWT 的安全设计原则

从安全研究角度,我们可以总结出以下设计与防御原则:

  1. 强密钥
    • 使用至少 256 位的随机密钥,避免字典爆破。
    • 定期更换密钥。
  2. 禁用 none 算法
    • 在服务端明确指定只接受特定算法(如 HS256 或 RS256)。
  3. 避免算法混淆
    • 如果使用非对称加密(RS256),严格区分公钥和私钥,不要让公钥参与 HMAC。
  4. 验证时间字段
    • 必须验证 exp、nbf、iat,避免过期或提前使用。
  5. 最小化 Payload 信息
    • 不要在 JWT 中放敏感数据(密码、银行卡、邮箱验证状态等)。
    • 仅放必要的 ID、角色等。
  6. 引入撤销机制
    • 可以维护黑名单(虽然牺牲"无状态"),或者缩短 JWT 的过期时间并配合 Refresh Token 机制。
  7. 传输安全
    • 必须使用 HTTPS 传输,防止中间人窃取 Token。
  8. 存储安全
    • 避免把 Token 存在 LocalStorage(容易被 XSS 窃取)。
    • 更推荐存放在 HttpOnly Cookie 中。

七、JWT 安全研究的本质认识

经过以上分析,我们可以回到 JWT 的"本质":

  • JWT 并不是一种加密机制,而是一个 被签名的 JSON 数据结构
  • 它的安全性依赖:
    1. 签名算法的正确实现
    2. 密钥的强度和保密性
    3. 开发者是否正确校验时间、算法等

换句话说:

  • JWT 自身并不保证安全,它只是一个便利的载体。
  • 真正决定安全性的,是开发者的实现与使用方式。
  • 在攻击者眼里,JWT 的价值在于:它是"单点突破"的目标,一旦拿到密钥或找到逻辑缺陷,整个系统将被完全接管。

八、结语

JWT 作为现代 Web 应用广泛使用的认证方式,其便利性无可替代。但从安全研究的视角来看,它的本质------"自包含的签名 JSON"------注定让它背负诸多风险。

它不像 Session 那样可控,而是把安全问题转移到"签名与密钥"上。

它的灵活性让开发者高效,但同时也给攻击者留下了丰富的攻击面。

因此,JWT 不应被视为一种"安全技术",而应被视为一种"便捷机制"。安全研究员需要始终提醒开发者:

  • 不要过度信任 JWT;
  • 不要把敏感数据放进 JWT;
  • 不要忽略签名验证的每一个细节。

最终,JWT 安全的核心并不是"JWT 本身",而是"开发者能否正确理解并使用它"。

PHP 的轻量级 JWT 演示项目

项目功能设计

  1. 登录页面
    • 用户输入用户名 + 密码
    • 验证成功后,生成一个 JWT 返回给用户
  2. 访问受保护页面
    • 用户访问时必须带 JWT
    • 服务端验证 JWT 的签名 + 过期时间
    • 如果合法,显示用户信息,否则拒绝

项目结构

/jwt_demo

├── index.php # 登录表单

├── login.php # 登录处理 & JWT 生成

├── protected.php # 需要 JWT 才能访问的页面

├── jwt.php # JWT 工具函数(生成 & 验证)

代码实现

1. jwt.php ------ JWT 工具函数

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?php // jwt.php // Secret key(生产环境要设置复杂的随机值) secret = "my_secret_key"; // 生成 JWT function generate_jwt(payload, secret) { header = json_encode(['alg' => 'HS256', 'typ' => 'JWT']); base64UrlHeader = str_replace(\['+', '/', '='\], \['-', '_', ''\], base64_encode(header)); base64UrlPayload = str_replace(\['+', '/', '='\], \['-', '_', ''\], base64_encode(json_encode(payload))); signature = hash_hmac('sha256', base64UrlHeader . "." . base64UrlPayload, secret, true); base64UrlSignature = str_replace(\['+', '/', '='\], \['-', '_', ''\], base64_encode(signature)); return base64UrlHeader . "." . base64UrlPayload . "." . base64UrlSignature; } // 验证 JWT function verify_jwt(jwt, secret) { tokenParts = explode('.', jwt); if (count(tokenParts) !== 3) return false; header = base64_decode(strtr(tokenParts[0], '-', '+/')); payload = base64_decode(strtr(tokenParts[1], '-', '+/')); signatureProvided = tokenParts[2]; // 重新计算签名 signature = hash_hmac('sha256', tokenParts[0] . "." . tokenParts\[1\], secret, true); base64UrlSignature = str_replace(\['+', '/', '='\], \['-', '_', ''\], base64_encode(signature)); // 检查签名是否匹配 if (base64UrlSignature !== signatureProvided) { return false; } // 检查过期时间 payloadArray = json_decode(payload, true); if (isset(payloadArray\['exp'\]) \&\& payloadArray['exp'] < time()) { return false; // Token 过期 } return $payloadArray; } ?> |

2. index.php ------ 登录页面

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JWT 登录</title> </head> <body> <h2>用户登录</h2> <form method="POST" action="login.php"> 用户名: <input type="text" name="username"><br> 密码: <input type="password" name="password"><br> <button type="submit">登录</button> </form> </body> </html> |

3. login.php ------ 登录处理 & JWT 签发

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?php include 'jwt.php'; // 模拟用户数据库 users = \[ "alice" =\> "123456", "bob" =\> "password" \]; username = _POST\['username'\]; password = _POST\['password'\]; if (isset(users[username\]) \&\& users[username\] === password) { // 登录成功 -> 生成 JWT payload = \[ "sub" =\> username, "role" => (username === "alice" ? "admin" : "user"), "exp" =\> time() + 60\*5 // 5分钟过期 \]; jwt = generate_jwt(payload, secret); echo "登录成功!你的 JWT:<br><br>"; echo "<textarea rows='5' cols='80'>jwt\\\"; echo "\

4. protected.php ------ 受保护页面

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <?php include 'jwt.php'; // 获取 token(通过 GET 或 Header) jwt = _GET['token'] ?? null; if (!jwt) { die("未提供 Token!"); } payload = verify_jwt(jwt, secret); if (payload) { echo "\欢迎你," . payload['sub'] . "!</h2>"; echo "你的角色是: " . payload\['role'\] . "\"; echo "Token 将在 " . date("Y-m-d H:i:s", payload['exp']) . " 过期<br>"; } else { echo "Token 无效或已过期!"; } ?> |

运行流程

  1. 访问 http://127.0.0.1/jwt_demo/index.php
  2. 输入用户名 alice,密码 123456
  3. 登录成功 → 获得 JWT
  4. 点击链接访问 protected.php?token=xxxx
  5. 如果 token 正确且未过期 → 显示用户信息,否则报错

JWT 安全问题与攻击技术

1. none 算法漏洞

安全问题

JWT Header 的 alg 字段被设置为 none 时,服务端可能跳过签名验证,Payload 可被任意篡改。风险包括任意权限提升和绕过认证机制。

攻击方式

  • 抓取合法 JWT Token
  • 修改 Header 为 {"alg":"none","typ":"JWT"}
  • 修改 Payload(如 "role":"admin")
  • 删除 Signature 后发送给受保护接口

样本

原 Token:

eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJ1c2VyIjogImFs

aWNlIiwgInJvbGUiOiAidXNlciJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

修改后的 Token:

eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyIjogImFs

aWNlIiwgInJvbGUiOiAiYWRtaW4ifQ.

示例

使用 Burp Suite 拦截请求,替换 Token 并删除 Signature,即可直接访问 /admin 接口。

防御策略

  • 服务端明确禁止 none 算法
  • 只接受指定算法(如 HS256 或 RS256)
  • 使用安全库进行 JWT 验证,避免自实现签名逻辑

2. 弱密钥 / 字典爆破

安全问题

JWT 使用简单或默认密钥(如 secret123),容易被攻击者通过字典或暴力破解伪造 Token。

攻击方式

  • 收集 JWT Token
  • 使用字典或工具如 jwt-cracker 尝试生成与原 Token 相同 Signature
  • 破解后生成任意 Payload Token

样本

假设原 Token 使用密钥 secret:

Header: {"alg":"HS256","typ":"JWT"}

Payload: {"user":"bob","role":"user"}

攻击者用字典工具生成密钥:

密钥: secret

生成 Token: eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJ1c2Vy

IjogImJvYiIsICJyb2xlIjogImFkbWluIn0.DR7QmNbd6Ux5vXqC-6XgDbk5xvE

示例

使用 jwt-cracker -t <token> -d wordlist.txt 破解,成功后发送生成的 Token 即可访问管理员接口。

防御策略

  • 使用高强度随机密钥(至少 256 位)
  • 定期更换密钥
  • 避免使用默认或常用字符串作为 JWT secret

3. 算法混淆攻击(RS/HS 混用)

安全问题

服务端使用 RS256 验证,但逻辑错误允许攻击者将 Header alg 修改为 HS256 并使用公钥生成签名。

攻击方式

  • 抓取 RS256 Token
  • 修改 Header 为 HS256
  • 使用公钥生成 HMAC 签名
  • 发送请求到接口

样本

原 Token:

Header: {"alg":"RS256","typ":"JWT"}

Payload: {"user":"alice","role":"user"}

Signature: <RS256签名>

攻击者修改:

Header: {"alg":"HS256","typ":"JWT"}

Payload: {"user":"alice","role":"admin"}

Signature: <HMAC公钥生成的签名>

示例

Python 演示:

import jwt

public_key = open('public.pem').read()

payload = {"user":"alice","role":"admin"}

token = jwt.encode(payload, public_key, algorithm="HS256")

print(token)

防御策略

  • 不允许客户端指定算法
  • 严格区分对称和非对称密钥
  • 使用成熟库校验 JWT

4. KID 注入漏洞

安全问题

JWT Header 中的 kid(Key ID)用于指定验证密钥,如果服务端未严格校验,攻击者可指定任意密钥,伪造合法 Token。

风险:攻击者可冒充任意用户,包括管理员。

攻击方式

  • 修改 Header 中 kid 为攻击者控制的密钥路径或值
  • 生成对应 Payload + Signature
  • 发送给服务端验证

样本

原 Token:

Header: {"alg":"HS256","typ":"JWT","kid":"key1"}

Payload: {"user":"bob","role":"user"}

Signature: <HS256签名>

攻击者修改:

Header: {"alg":"HS256","typ":"JWT","kid":"attacker_key"}

Payload: {"user":"bob","role":"admin"}

Signature: <使用attacker_key生成的签名>

示例

Python 示例:

import jwt

attacker_key = "my_fake_key"

payload = {"user":"bob","role":"admin"}

header = {"alg":"HS256","typ":"JWT","kid":"attacker_key"}

token = jwt.encode(payload, attacker_key, algorithm="HS256", headers=header)

print(token)

防御策略

  • 严格验证 kid 是否在允许列表
  • 不使用可控文件路径或用户输入作为密钥
  • 对每个密钥使用独立管理,限制访问

5. 过期时间绕过

安全问题

JWT 的 exp(过期时间)、nbf(生效时间)字段未严格验证,攻击者可使用过期或未生效 Token。

风险:会话劫持,长期访问受保护资源。

攻击方式

  • 修改 Payload 中 exp 或 nbf
  • 使用抓取的 Token 重放请求

样本

原 Payload:

{"user":"alice","role":"user","exp":1693700000}

攻击者修改:

{"user":"alice","role":"admin","exp":1893700000}

示例

Python 演示:

import jwt

secret = "mysecret"

payload = {"user":"alice","role":"admin","exp":1893700000}

token = jwt.encode(payload, secret, algorithm="HS256")

print(token)

防御策略

  • 服务端必须验证 exp、nbf
  • 使用短期 Token + Refresh Token
  • 结合黑名单机制,支持 Token 撤销

6. Payload 明文信息泄露

安全问题

JWT Payload 仅 Base64URL 编码,不加密,攻击者可直接查看敏感信息。

风险:泄露用户角色、ID、邮箱等,辅助其他攻击(横向渗透、社工)。

攻击方式

  • 抓取 Token
  • Base64URL 解码 Payload

样本

Token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYm9iIiwicm9sZSI6ImFkbWluIiwiZW1haWwiOiJib2JAbWFpbC5jb20ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

解码 Payload:

{"user":"bob","role":"admin","email":"bob@mail.com"}

示例

Linux 命令:

echo 'eyJ1c2VyIjoiYm9iIiwicm9sZSI6ImFkbWluIn0' | base64 -d

防御策略

  • 最小化 Payload,避免存储敏感信息
  • 对敏感信息使用加密或服务器端存储
  • 使用 HTTPS 传输,防止抓包泄露

7. Token 重放

安全问题

JWT 无内置防重放机制,攻击者可重复使用抓取的 Token。

风险:冒充用户访问接口,造成会话劫持。

攻击方式

  • 使用抓包工具获取合法 Token
  • 重放 HTTP 请求至受保护接口

样本

Token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UifQ.<Signature>

攻击者直接在请求 Header 中替换 Authorization 即可访问接口。

示例

使用 curl 重放:

curl -H "Authorization: Bearer <token>" https://target.com/api/admin

防御策略

  • 引入 jti(JWT ID)或 nonce,实现一次性 Token
  • 对短期 Token 使用黑名单
  • 强化 HTTPS,防止抓包

8. Token 劫持(存储或传输不安全)

安全问题

JWT 存储在 LocalStorage 或通过 HTTP 明文传输,容易被 XSS 或 MITM 攻击者窃取。

风险:攻击者可获取用户身份,执行非法操作。

攻击方式

  • 利用浏览器 XSS 漏洞获取 LocalStorage 中的 Token
  • 或抓取 HTTP 明文 Token

样本

// 获取 LocalStorage 中 Token

let token = localStorage.getItem("jwt_token");

console.log(token);

示例

攻击者在浏览器控制台或通过恶意脚本获取 Token,并发送请求:

curl -H "Authorization: Bearer <stolen_token>" https://target.com/api

防御策略

  • 使用 HttpOnly + Secure Cookie 存储 Token
  • 全站启用 HTTPS
  • 防止 XSS,严格内容安全策略(CSP)

9. 签名算法降级

安全问题

允许客户端指定算法,攻击者可指定已知弱算法(如 MD5、SHA1)进行伪造。

风险:绕过强算法验证,实现权限提升。

攻击方式

  • 修改 Header 中 alg 为弱算法
  • 使用该算法生成签名 Token

样本

Header:

{"alg":"HS1","typ":"JWT"}

Payload:

{"user":"alice","role":"admin"}

示例

Python:

import jwt

secret = "weaksecret"

payload = {"user":"alice","role":"admin"}

token = jwt.encode(payload, secret, algorithm="HS1")

print(token)

防御策略

  • 服务端只接受强算法(HS256, RS256)
  • 不允许客户端指定算法
  • 使用成熟库进行签名验证

10. 过度依赖客户端验证

安全问题

服务端未独立验证 Token,权限逻辑放在客户端,攻击者可修改 Payload 绕过前端判断。

风险:可访问受保护资源,造成权限提升。

攻击方式

  • 修改 Payload 中权限字段
  • 直接调用 API 接口,无需前端限制

样本

Payload:

{"user":"bob","role":"admin"}

示例

Python:

import requests

token = "<modified_token>"

headers = {"Authorization": f"Bearer {token}"}

response = requests.get("https://target.com/api/admin", headers=headers)

print(response.text)

防御策略

  • 服务端独立验证 Token 和权限
  • 不依赖前端逻辑
  • 结合角色白名单、最小权限原则
相关推荐
We....9 小时前
多线程同步安全机制
java·jvm·安全
日志易9 小时前
66AI Agent重构SOC:下一代智能安全运营平台的能力跃迁
安全·ai·soc·日志易
wanhengidc10 小时前
云手机中的三大核心技术主要是指什么?
运维·科技·安全·游戏·智能手机
岛屿旅人12 小时前
涉私数据安全与可控匿名化利用机制研究(上)
网络·人工智能·安全·web安全
wanhengidc12 小时前
使用云手机进行游戏搬砖划算吗?
运维·服务器·网络·安全·游戏·智能手机
m0_7381207212 小时前
CTFshow系列——命令执行web73-77(完结篇)
前端·安全·web安全·网络安全·ctfshow
岛屿旅人13 小时前
涉私数据安全与可控匿名化利用机制研究(下)
网络·人工智能·安全·web安全·生成对抗网络
卡奥斯开源社区官方14 小时前
华为卫星对星引导技术深度解析:原理、实现与开源替代方案
安全
-曾牛15 小时前
Upload-Labs靶场全20关通关攻略(含原理+实操+环境配置)
网络安全·渗透测试·靶场·文件上传漏洞·攻略·靶场教程·漏洞练习