JWT安全漏洞之签名验证缺失攻击详解
字数 3201 2025-12-15 18:11:59

JWT安全漏洞之签名验证缺失攻击详解

一、 题目/知识点描述

JWT签名验证缺失攻击,是指JSON Web Token的接收方(通常是服务器端)在验证JWT令牌时,没有正确验证其签名的完整性,导致攻击者可以篡改令牌的有效载荷(Payload)部分(例如,用户ID、角色权限等),而验证方会无条件信任这些被篡改的数据。这是一种高危漏洞,可导致越权访问、权限提升等安全问题。

简单比喻:这就像收到一封盖了“已检验”印章的官方文件,我们只看印章就相信了文件内容。但如果这个文件本身可以被任意涂改,而印章的“检验”功能(即签名验证)被我们忽略掉了,那么伪造一份高权限的文件就变得轻而易举。

二、 解题过程/知识讲解

我们将从JWT的基本结构入手,逐步分析漏洞的成因、利用方法和防御措施。

步骤1:回顾JWT的基本结构

JWT(JSON Web Token)是一种开放标准,用于在各方之间安全地传输信息。一个完整的JWT由三部分组成,以点号分隔:

  • 头部:包含令牌类型和签名算法,如{"alg":"HS256","typ":"JWT"},然后进行Base64Url编码。
  • 载荷:存放实际需要传递的声明(Claims),如{"user":"alice","role":"user","exp":1672500000},然后进行Base64Url编码。
  • 签名:对编码后的头部和载荷,使用一个密钥和头部指定的算法(如HS256)进行签名,以确保令牌的完整性和真实性。

一个典型的JWT如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UiLCJyb2xlIjoidXNlciIsImV4cCI6MTY3MjUwMDAwMH0.xxxxxx

步骤2:理解JWT验证的常规流程

当服务器收到一个JWT时,应该执行以下步骤进行验证:

  1. 格式验证:检查令牌是否由三部分组成,用点号分隔。
  2. 头部解析:解码头部,获取alg字段,确认签名算法。
  3. 载荷解析:解码载荷,检查有效期、颁发者等声明。
  4. 核心步骤:签名验证:使用与颁发方约定的正确密钥和算法,重新计算前两部分(Header.Payload)的签名。将计算出的新签名与JWT提供的第三部分(签名)进行比较。两者必须完全一致,才证明令牌未被篡改。
  5. 业务验证:签名验证通过后,再使用载荷中的信息(如user、role)进行业务逻辑处理。

步骤3:漏洞成因分析

“签名验证缺失”漏洞的发生,核心在于第4步被完全跳过或错误实现。以下是几种常见的错误实现场景:

  1. 完全跳过验证:服务器解码JWT的载荷后,直接使用其中的数据,没有进行任何签名校验。攻击者可以任意构造JWT,修改载荷内容。
  2. 使用错误的验证库/配置:某些JWT库(如Node.js的jsonwebtoken的早期版本或某些用法)默认不验证签名,需要开发者显式调用verify函数。如果开发者错误地使用了仅解码的decode函数,就会导致漏洞。
  3. 支持none算法:JWT规范允许签名算法为none,表示不进行签名。如果服务器配置不当,没有在允许的算法列表中显式排除none,攻击者可以将头部改为{"alg":"none","typ":"JWT"},并将签名部分置空,服务器可能会接受此无签名的令牌。注意:现代标准库通常已禁止此算法,但仍需警惕。
  4. 密钥混淆/弱密钥:虽然这属于另一种攻击(Key Confusion),但有时会与验证缺失混淆。这里的“缺失”特指验证步骤本身不存在

步骤4:漏洞利用实战演示

假设一个应用存在此漏洞,其登录后返回的JWT格式如下,用于标识用户身份和角色:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UiLCJyb2xlIjoidXNlciIsImV4cCI6MTY3MjUwMDAwMH0.xxxxxx

攻击者(Attacker)的目标是:将自己伪装成管理员用户(admin)。

攻击步骤:

  1. 截获或获取一个有效JWT:攻击者可以通过注册普通账号,或通过其他信息泄露方式获得一个合法JWT。
  2. 解码并分析JWT
    • 将第一部分解码,得到头部:{"alg":"HS256","typ":"JWT"}
    • 将第二部分解码,得到载荷:{"user":"alice","role":"user","exp":1672500000}
  3. 篡改载荷
    • 将载荷中的"user"字段修改为"admin",将"role"字段修改为"administrator"。同时,为了避免令牌过期,可以修改"exp"为一个未来的时间戳。
    • 修改后的载荷变为:{"user":"admin","role":"administrator","exp":1900000000}
  4. 重新编码:将原始头部(无需修改alg字段,因为服务器不验证)和篡改后的载荷分别进行Base64Url编码。
  5. 构造恶意JWT:由于服务器不验证签名,攻击者不需要知道签名密钥,也不需要生成有效的签名。他可以简单地拼接编码后的头部、载荷和一个空的或任意的第三部分。
    • 恶意JWT示例:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW5pc3RyYXRvciIsImV4cCI6MTkwMDAwMDAwMH0.(注意最后签名部分为空)。
    • 或者保留原始签名,但服务器不验证,所以任何签名都可以。
  6. 发送请求:将构造好的恶意JWT放入HTTP请求的Authorization头部(例如:Bearer <恶意JWT>),发送给受保护的API端点。
  7. 结果:漏洞服务器解码JWT,看到载荷中有"user":"admin""role":"administrator",由于跳过了签名验证,它直接信任了这些数据,从而授予攻击者管理员权限。

步骤5:漏洞防御措施

防御的核心是强制、正确地执行签名验证

  1. 使用安全的标准库:使用成熟、维护活跃的JWT库(如Java的jjwt, Python的PyJWT, Node.js的jsonwebtoken, Go的github.com/golang-jwt/jwt),并遵循其安全最佳实践。
  2. 始终调用验证函数
    • 错误做法payload = jwt.decode(token, options={“verify_signature”: False}) (这是显式关闭验证)。
    • 正确做法payload = jwt.verify(token, ‘your-256-bit-secret’, algorithms=[“HS256”]) (必须提供密钥和允许的算法列表)。
  3. 显式指定允许的算法:在验证时,明确指定服务器所接受的算法列表(如algorithms=[“HS256”]),绝不使用允许所有算法的通配符(如algorithms=None*),这能有效阻止none算法攻击。
  4. 使用强密钥并安全管理:使用足够长度和随机性的密钥(如HS256至少256位随机字节),并将密钥安全地存储在环境变量或密钥管理服务中,而非硬编码在代码里。
  5. 验证所有必要声明:签名验证通过后,仍需验证令牌的有效期、颁发者、受众等业务声明。
  6. 依赖框架中间件:在使用Spring Security、Passport.js等框架时,使用官方或社区认可的、经过安全审计的JWT中间件,并仔细配置。

总结:JWT签名验证缺失攻击的本质是服务器逻辑上省略了最重要的安全步骤。防御的关键在于在代码中强制、显式地进行签名验证,并使用安全的库和配置。在任何涉及JWT的身份验证和授权逻辑中,签名验证都必须是第一个且不可绕过的检查点。

JWT安全漏洞之签名验证缺失攻击详解 一、 题目/知识点描述 JWT签名验证缺失攻击,是指JSON Web Token的接收方(通常是服务器端)在验证JWT令牌时,没有正确验证其签名的完整性,导致攻击者可以篡改令牌的有效载荷(Payload)部分(例如,用户ID、角色权限等),而验证方会无条件信任这些被篡改的数据。这是一种高危漏洞,可导致越权访问、权限提升等安全问题。 简单比喻:这就像收到一封盖了“已检验”印章的官方文件,我们只看印章就相信了文件内容。但如果这个文件本身可以被任意涂改,而印章的“检验”功能(即签名验证)被我们忽略掉了,那么伪造一份高权限的文件就变得轻而易举。 二、 解题过程/知识讲解 我们将从JWT的基本结构入手,逐步分析漏洞的成因、利用方法和防御措施。 步骤1:回顾JWT的基本结构 JWT(JSON Web Token)是一种开放标准,用于在各方之间安全地传输信息。一个完整的JWT由三部分组成,以点号分隔: 头部 :包含令牌类型和签名算法,如 {"alg":"HS256","typ":"JWT"} ,然后进行Base64Url编码。 载荷 :存放实际需要传递的声明(Claims),如 {"user":"alice","role":"user","exp":1672500000} ,然后进行Base64Url编码。 签名 :对编码后的头部和载荷,使用一个密钥和头部指定的算法(如HS256)进行签名,以确保令牌的完整性和真实性。 一个典型的JWT如下: 步骤2:理解JWT验证的常规流程 当服务器收到一个JWT时,应该执行以下步骤进行验证: 格式验证 :检查令牌是否由三部分组成,用点号分隔。 头部解析 :解码头部,获取 alg 字段,确认签名算法。 载荷解析 :解码载荷,检查有效期、颁发者等声明。 核心步骤:签名验证 :使用与颁发方约定的 正确密钥 和算法,重新计算前两部分(Header.Payload)的签名。将计算出的新签名与JWT提供的第三部分(签名)进行比较。 两者必须完全一致,才证明令牌未被篡改。 业务验证 :签名验证通过后,再使用载荷中的信息(如user、role)进行业务逻辑处理。 步骤3:漏洞成因分析 “签名验证缺失”漏洞的发生,核心在于 第4步被完全跳过或错误实现 。以下是几种常见的错误实现场景: 完全跳过验证 :服务器解码JWT的载荷后, 直接 使用其中的数据,没有进行任何签名校验。攻击者可以任意构造JWT,修改载荷内容。 使用错误的验证库/配置 :某些JWT库(如Node.js的 jsonwebtoken 的早期版本或某些用法)默认不验证签名,需要开发者显式调用 verify 函数。如果开发者错误地使用了仅解码的 decode 函数,就会导致漏洞。 支持 none 算法 :JWT规范允许签名算法为 none ,表示不进行签名。如果服务器配置不当,没有在允许的算法列表中显式排除 none ,攻击者可以将头部改为 {"alg":"none","typ":"JWT"} ,并将签名部分置空,服务器可能会接受此无签名的令牌。 注意 :现代标准库通常已禁止此算法,但仍需警惕。 密钥混淆/弱密钥 :虽然这属于另一种攻击(Key Confusion),但有时会与验证缺失混淆。这里的“缺失”特指 验证步骤本身不存在 。 步骤4:漏洞利用实战演示 假设一个应用存在此漏洞,其登录后返回的JWT格式如下,用于标识用户身份和角色: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UiLCJyb2xlIjoidXNlciIsImV4cCI6MTY3MjUwMDAwMH0.xxxxxx 攻击者(Attacker)的目标是:将自己伪装成管理员用户(admin)。 攻击步骤: 截获或获取一个有效JWT :攻击者可以通过注册普通账号,或通过其他信息泄露方式获得一个合法JWT。 解码并分析JWT : 将第一部分解码,得到头部: {"alg":"HS256","typ":"JWT"} 。 将第二部分解码,得到载荷: {"user":"alice","role":"user","exp":1672500000} 。 篡改载荷 : 将载荷中的 "user" 字段修改为 "admin" ,将 "role" 字段修改为 "administrator" 。同时,为了避免令牌过期,可以修改 "exp" 为一个未来的时间戳。 修改后的载荷变为: {"user":"admin","role":"administrator","exp":1900000000} 。 重新编码 :将原始头部(无需修改 alg 字段,因为服务器不验证)和篡改后的载荷分别进行Base64Url编码。 构造恶意JWT :由于服务器不验证签名,攻击者 不需要 知道签名密钥,也 不需要 生成有效的签名。他可以简单地拼接编码后的头部、载荷和一个空的或任意的第三部分。 恶意JWT示例: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW5pc3RyYXRvciIsImV4cCI6MTkwMDAwMDAwMH0. (注意最后签名部分为空)。 或者保留原始签名,但服务器不验证,所以任何签名都可以。 发送请求 :将构造好的恶意JWT放入HTTP请求的 Authorization 头部(例如: Bearer <恶意JWT> ),发送给受保护的API端点。 结果 :漏洞服务器解码JWT,看到载荷中有 "user":"admin" 和 "role":"administrator" ,由于跳过了签名验证,它 直接信任了这些数据 ,从而授予攻击者管理员权限。 步骤5:漏洞防御措施 防御的核心是 强制、正确地执行签名验证 。 使用安全的标准库 :使用成熟、维护活跃的JWT库(如Java的 jjwt , Python的 PyJWT , Node.js的 jsonwebtoken , Go的 github.com/golang-jwt/jwt ),并遵循其安全最佳实践。 始终调用验证函数 : 错误做法 : payload = jwt.decode(token, options={“verify_signature”: False}) (这是显式关闭验证)。 正确做法 : payload = jwt.verify(token, ‘your-256-bit-secret’, algorithms=[“HS256”]) (必须提供密钥和允许的算法列表)。 显式指定允许的算法 :在验证时,明确指定服务器所接受的算法列表(如 algorithms=[“HS256”] ), 绝不 使用允许所有算法的通配符(如 algorithms=None 或 * ),这能有效阻止 none 算法攻击。 使用强密钥并安全管理 :使用足够长度和随机性的密钥(如HS256至少256位随机字节),并将密钥安全地存储在环境变量或密钥管理服务中,而非硬编码在代码里。 验证所有必要声明 :签名验证通过后,仍需验证令牌的有效期、颁发者、受众等业务声明。 依赖框架中间件 :在使用Spring Security、Passport.js等框架时,使用官方或社区认可的、经过安全审计的JWT中间件,并仔细配置。 总结 :JWT签名验证缺失攻击的本质是服务器 逻辑上省略了最重要的安全步骤 。防御的关键在于 在代码中强制、显式地进行签名验证 ,并使用安全的库和配置。在任何涉及JWT的身份验证和授权逻辑中,签名验证都必须是第一个且不可绕过的检查点。