JWT漏洞

JWT是什么?

JWT全称是JSON Web Token,时RFC 7519定义的一种紧凑、URL安全的声明传输格式。常被用于身份认证、会话管理、API鉴权和微服务之间的身份传递。

例如用户登录成功后,服务器可以生成一枚JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMDAxIiwidXNlcm5hbWUiOiJsYWluIiwicm9sZSI6InVzZXIifQ . example_signature

客户端后续访问接口时携带它

GET /api/profile HTTP/1.1 
Host: example.com 
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

服务器验证JWT的签名和相关声明,确认令牌可信后,再根据其中的用户身份和权限处理请求

JWT / Cookie

明确两个容易混淆的概念:JWT是令牌格式,不是登录协议,也不是加密算法

JWT和Cookie不是同一层级的东西,不存在说JWT比Cookie更安全。JWT是一种令牌格式,可以放在Cookie中,也可以放在Authorization请求头中,真正存在安全区别的是JWT认证和Session认证两种认证方式。

JWT的主要优势

1.无状态:服务端通常不需要保存session。JWT中可以携带用户ID、角色、权限、过期时间等信息,服务端验证签名即可确认令牌是否合法。
2.适合分布式和微服务:多个服务只要共享验证密钥,或者使用公钥验证签名,就能识别用户身份,不必都访问同一个Session数据库。
3.可以验证完整性和来源:JWT通过签名防止内容被篡改。如果攻击者修改Payload但不能生成有效签名,服务端验证时就会发现。
4.可以携带声明:JWT可以携带sub、exp、aud、iss、角色等声明,方便服务间传递身份和授权信息。

JWT的三段结构

常见的JWT实际上采用的是JWS Compact Serializatoin,由三个Base64URL字符串组成

Header.Payload.Signature

JWS用数字签名或消息认证码保护内容的完整性;如果需要对内容进行加密,则应使用JWE。JWS与JWE是不同的标准

Header:头部

头部说明令牌使用的算法及类型

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

常见字段

  • alg:签名算法,如 HS256、RS256、ES256
  • typ:令牌类型,通常为 JWT
  • kid:密钥编号,服务器用它选择验证密钥
  • jwk:直接携带 JSON Web Key
  • jku:JWK Set 的地址
  • x5u:X.509 证书链地址

其中,JWK是使用JSON表示密码学密钥的标准格式,多个JWK可以组成JWK Set

Payload:载荷

Payload保存JWT的声明,即Claims

{ 
 "iss": "https://auth.example.com",
 "sub": "1001",
 "aud": "order-api",
 "username": "lain",
 "role": "user",
 "iat": 1783910400,
 "exp": 1783911300,
 "jti": "0c33c7da" 
}

常见的声明标准

声明含义
issIssuer,签发者
subSubject,令牌代表的主体
audAudience,令牌预期接收方
expExpiration Time,过期时间
nbfNot Before,在该时间之前不可使用
iatIssued At,签发时间
jtiJWT ID,令牌唯一编号

Signature:签名

签名输入通常是:Base64URL(Header) + “.” + Base64URL(Payload),而Signature = HMAC-SHA256(签名输入, Secret)。这里也恰好说明了一点,JWT是否可信依靠的是算法对应使用的密钥,那么正常情况伪造JWT第一步就是获取对应的密钥。

Payload = 身份证上的文字
Signature = 防伪印章
Secret / 私钥 = 制作防伪印章的核心密钥

以HS256为例:HMAC-SHA256( Base64URL(Header) + “.” + Base64URL(Payload), secret )

最终JWT为Base64URL(Header) + “.” + Base64URL(Payload) + “.” + Base64URL(Signature)

签名的作用是:
1.检测Header或Payload是否被修改
2.证明令牌由掌握密钥的一方签发
3.防止攻击者直接伪造身份和权限声明

JWT的核心安全能力是签名验证:Header.Payload.Signature,服务端可以通过签名确认Header和Payload有没有被修改,Token是否过期是否由可信身份认证系统签发。

Base64URL

JWT中的Header和Payload通常只是Base64URL编码

Base64URL与普通Base64的主要区别是:+ 替换为 – ;/ 替换为 _; 通常省略末尾的 =

因此任何获得JWT的人都可以解码Payload,所以JWT Payload中不应该存放:用户密码、私钥或API密钥等敏感信息。至于签名,签名只保护了完整性,不提供保密性。

JWT工作流程

用户携带用户名和密码请求访问;

  • 服务器校验用户凭据;
  • 应用提供一个 token 给客户端;
  • 客户端存储 token,并且在随后的每一次请求中都带着它;
  • 服务器校验 token 并返回数据。

需要注意问题:

  • 每一次请求都需要 token;
  • Token 应该放在请求 header 中;
  • 需要将服务器设置为接受来自所有域的请求,用 Access-Control-Allow-Origin: *。

JWT认证/Session认证及其安全性比较

Session认证流程

用户提交账号密码
        ↓
服务端验证身份
        ↓
生成随机Session ID
        ↓
服务端保存:Session ID → 用户信息
        ↓
通过Cookie把Session ID返回给浏览器
        ↓
浏览器后续自动携带Cookie
        ↓
服务端查询Session并确认身份

JWT认证流程

用户提交账号密码
        ↓
服务端验证身份
        ↓
生成包含用户声明的JWT并签名
        ↓
客户端保存JWT
        ↓
请求时携带JWT
        ↓
服务端验证签名、有效期等声明
        ↓
认证通过

JWT认证和Session认证的安全性比较

Token被盗风险来看,Session ID和JWT通常都是Bearer Token(持有者令牌),因此两者一旦泄露,都可能导致账号被盗用。

数据泄露风险上来看,Session通常只在客户端保存一个随机ID,真实用户信息保存在服务端,而JWT的Payload默认只是Base64URL编码,没有加密,任何拿到JWT的人都能够解码Payload,如果Payload中保存敏感信息就会导致敏感信息泄露。

篡改风险上看,如果Session ID使用可预测的生成方式,攻击者确实可能预测或伪造有效Session;但规范实现的Session ID应由密码学安全随机数生成,本身不需要通过签名防篡改,因为服务端只接受已存在的随机Session ID。JWT则通过签名直接保护Token内容的完整性,在密钥安全、算法配置和验证逻辑正确的前提下,可以防止Payload被修改。

JWT攻击手法

完全没有验证签名

服务端只是解码JWT

DecodedJWT jwt = JWT.decode(token); String role = jwt.getClaim("role").asString();

没有调用真正的验证函数

这时攻击者可以把

{
 "role": "user" 
}

修改为

{
 "role": "admin" 
}

签名为空

JWT第一部分含有alg字段,该字段指定生成签名采用哪种哈希算法,如果某站使用的是HS256,可将该字段篡改为none,某些JWT的实现,一旦发现alg为none,将不再生成哈希签名,自然不存在校验签名一说,直接将payload里的username改为admin即可篡改用户信息

将头部alg的值修改为none,接着修改payload内容编码复制前两部分即可,最后一部分因为alg:none所以不存在

爆破弱密钥

将JWT内容保存至jwt.txt文档内

用kali自带的字典或者字典:https://github.com/wallarm/jwt-secrets/blob/master/jwt.secrets.list进行爆破

hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 jwt数据文件 爆破jwt的字典

爆破出密钥之后即可修改JWT进行越权等

未验证签名导致越权

直接修改userid编码即可

JWT常见漏洞利用

通过爆破出key以后,可以利用JWT进行漏洞测试

水平越权

把1234567890改为1234567891

{
"sub": "1234567890",
"name": "0xShe",
"sysadmin": "N",
"iat": 1690989833
}

垂直越权

把sysadmin的值N改为Y(NO改为YES或者false改为true)

{
"sub": "1234567890",
"name": "0xShe",
"sysadmin": "Y",
"iat": 1690989833
}

SQL注入

{
"sub": "1234567890",
"name": "0xShe",
"data": "1' UNION SELECT 'key';-- ",
"iat": 1690989833
}

命令执行

{
"sub": "1234567890",
"name": "0xShe",
"echo": "yes||whoami",
"iat": 1690989833
}

文件读取

{
"sub": "1234567890",
"name": "0xShe",
"path": "/etc/passwd",
"iat": 1690989833
}

SSRF

{
"url": "http://www.DNSlog.cn/",
"iat": 1690989833
}

靶场实战


未经验证的签名绕过JWT身份验证

https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature

进入靶场点击登录

普通账号/密码:wiener/peter

登录后用BP抓包看看,很明显的三段JWT

对JWT内容进行解密查看内容

直接修改sub值为administrator


有缺陷的签名验证绕过JWT身份验证

https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-flawed-signature-verification

将alg更改为none,sub改为administrator

弱签名密钥绕过JWT身份验证

https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-weak-signing-key

一样的登录普通用户账号然后对JWT内容进行解码查看

这里不清楚使用的密钥,若不知道对应密钥那么伪造的JWT将不会被服务器所认可,所以对密钥进行爆破

JWT审计

审计JWT的重点应该检查

1.签名算法是否安全

重点检查:

  • 是否允许alg=none;
  • 是否存在RS256与HS256算法混淆;
  • 服务端是否直接信任JWT Header中的alg;
  • 是否使用MD5、SHA-1等不安全算法;
  • 是否明确配置允许的算法白名单。

2. 密钥是否安全

检查:

  • 密钥是否硬编码在源码中;
  • 是否提交到Git历史、镜像或配置文件中;
  • 是否使用默认密钥或示例密钥;
  • HMAC密钥是否过短、可被字典爆破;
  • 测试和生产环境是否共用密钥;
  • 多个业务系统是否共用同一密钥;
  • 私钥文件权限是否过大;
  • 是否具有密钥轮换机制。

3. 标准声明是否严格校验

重点检查:

  • exp:是否设置并验证过期时间;
  • nbf:Token是否在允许时间之前被使用;
  • iat:签发时间是否合理;
  • iss:是否由可信认证服务签发;
  • aud:Token是否确实签发给当前系统;
  • sub:用户主体是否有效;
  • jti:是否可用于重放检测和撤销;
  • 是否合理限制时钟偏差。

只验证签名、不验证exp、iss和aud是不完整的。

4. Header参数是否可控

重点关注:

  • kid是否可以造成目录穿越或SQL注入;
  • jku、x5u是否允许加载攻击者控制的远程密钥;
  • 是否可能通过远程取密钥造成SSRF;
  • 是否信任攻击者提供的JWK;
  • 未找到指定密钥时,是否错误地回退到默认密钥。

服务端应该使用可信的本地映射选择密钥,不能任意访问JWT Header指定的URL或文件。

5. Payload是否包含敏感信息

普通JWT的Payload只是Base64URL编码,不是加密。

检查是否包含:

  • 密码或密码哈希;
  • 手机号、身份证号;
  • 银行卡信息;
  • 内部系统敏感字段;
  • 密钥或凭证;
  • 不应该暴露的用户隐私。

如果需要保密,应使用JWE或者只在JWT中保存用户标识,敏感数据留在服务端。

JWT 密钥硬编码修复

如果发现JWT密钥硬编码,我会将其视为可能已经泄露。首先生成新密钥并轮换旧密钥,使旧Token失效或进入短暂过渡期;还需要检查Git历史、镜像和日志中的泄露范围,并在CI中加入Secret扫描。仅仅把原来的密钥从代码移动到环境变量中并不够,因为旧密钥已经可能被复制。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇