jwt学习笔记

JWT学习笔记

什么是JWT

JSON网络令牌(JWT)是用于在系统之间发送加密签名的JSON数据的标准化格式。理论上,它们可以包含任何类型的数据,但最常用于发送关于用户的信息(“声明”),作为身份验证、会话处理和访问控制机制的一部分。

与传统的会话令牌不同,服务器需要的所有数据都存储在JWT本身的客户端。这使得JWT成为高度分布式网站的热门选择,在这些网站中,用户需要与多个后端服务器无缝交互。

JWT格式

JWT由3部分组成:报头、有效载荷和签名。这些都由一个点分隔,如以下示例所示:

eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA

JWT的报头和有效负载部分只是base64url编码的JSON对象。报头包含关于令牌本身的元数据,而有效载荷包含关于用户的实际“声明”。例如,我们可以从上面的令牌解码有效载荷,以揭示以下声明:

{
    "iss": "portswigger",
    "exp": 1648037164,
    "name": "Carlos Montoya",
    "sub": "carlos",
    "role": "blog_author",
    "email": "carlos@carlos-montoya.net",
    "iat": 1516239022
}

在大多数情况下,任何人都可以轻松地读取或修改这些数据。因此,任何基于JWT的机制的安全性严重依赖于密码签名。

JWT签名

发出令牌的服务器通常通过散列报头和有效载荷来生成签名。在某些情况下,它们还加密产生的哈希。无论哪种方式,该过程都涉及密钥签名密钥。此机制为服务器提供了一种方法来验证令牌中的数据自发布以来没有被篡改:

  • 由于签名是直接从令牌的其余部分导出的,因此更改报头或有效载荷的单个字节会导致签名不匹配。
  • 如果不知道服务器的密钥签名密钥,就不可能为给定的头或有效载荷生成正确的签名。

JWT vs JWS vs JWE

JWT规范实际上是非常有限的。它只定义了一种将信息(“声明”)表示为JSON对象的格式,该对象可以在双方之间传输。实际上,JWT并不是真正用作独立实体。JWT规范由JSON Web签名(JWS)和JSON Web加密(JWE)规范扩展,它们定义了实际实现JWT的具体方法。

JWT-jws-jwe

换句话说,JWT通常是JWS或JWE令牌。当人们使用术语“JWT”时,他们几乎总是指JWS令牌。JWE非常相似,只是令牌的实际内容是加密的,而不仅仅是编码。

JWT攻击

JWT攻击涉及用户向服务器发送修改过的JWT以达到恶意目的。通常,此目标是通过模拟另一个已经通过身份验证的用户来绕过身份验证和访问控制。

JWT攻击的漏洞是如何产生的?

JWT漏洞通常是由于应用程序本身中有缺陷的JWT处理引起的。与JWT相关的各种规范在设计上相对灵活,允许网站开发人员自行决定许多实现细节。这可能导致他们意外引入漏洞,即使在使用战斗强化库时也是如此。

这些实现缺陷通常意味着JWT的签名没有得到正确的验证。这使得攻击者能够篡改通过令牌的有效载荷传递给应用程序的值。即使签名被鲁棒地验证,它是否可以真正被信任在很大程度上取决于服务器的密钥密钥是否保持密钥。如果该密钥以某种方式泄露,或者可以被猜测或暴力强制,攻击者可以为任何任意令牌生成有效签名,从而危及整个机制。

常见漏洞

利用有缺陷的JWT签名验证

根据设计,服务器通常不存储关于它们发布的JWT的任何信息。相反,每个令牌都是一个完全独立的实体。这有几个优点,但也引入了一个基本问题–服务器实际上不知道令牌的原始内容,甚至不知道原始签名是什么。因此,如果服务器没有正确验证签名,那么攻击者就无法对令牌的其余部分进行任意更改。

例如,考虑包含以下声明的JWT:

{
    "username": "Tom",
    "isAdmin": false
}

如果服务器基于此username标识会话,则修改其值可能使攻击者能够模拟其他登录用户。类似地,如果isAdmin值用于访问控制,这可以为权限提升提供一个简单的向量。

在前几个实验中,我们将看到一些示例,说明这些漏洞在实际应用程序中的外观。

接受任意签名

JWT库通常提供一种方法来验证令牌,而另一种方法只是对令牌进行解码。例如,Node.js库jsonwebtokenverify()decode()

有时,开发人员会混淆这两个方法,只将传入的令牌传递给decode()方法。这实际上意味着应用程序根本不验证签名。

接受没有签名的令牌

JWT头包含一个alg参数。这将告诉服务器使用哪种算法对令牌进行签名,因此也告诉服务器在验证签名时需要使用哪种算法。

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

这是固有的缺陷,因为服务器别无选择,只能隐式信任令牌的用户可控输入,而令牌此时根本没有经过验证。换句话说,攻击者可以直接影响服务器如何检查令牌是否可信。

JWT可以使用一系列不同的算法进行签名,但也可以保持无签名。在这种情况下,alg参数被设置为none,其指示所谓的“不安全JWT”。由于这种情况的明显危险,服务器通常拒绝没有签名的令牌。但是,由于这种过滤依赖于字符串解析,因此有时可以使用经典的混淆技术(例如混合大写和意外编码)绕过这些过滤器。

即使令牌是无签名的,有效负载部分仍然必须以尾随点结束。

暴力破解密钥

一些签名算法,如HS 256(HMAC + SHA-256),使用任意的独立字符串作为密钥。就像密码一样,关键是这个密钥不能被攻击者轻易猜测或强行破解。否则,他们可能能够创建具有他们喜欢的任何报头和有效载荷值的JWT,然后使用密钥以有效签名重新签名令牌。

在实现JWT应用程序时,开发人员有时会犯一些错误,比如忘记更改默认值或占位符机密。他们甚至可能复制并粘贴他们在网上找到的代码片段,然后忘记更改作为示例提供的硬编码密钥。在这种情况下,攻击者使用众所周知的密钥的单词列表来暴力破解服务器的密钥是十分简单的。

使用hashcat的暴力强制密钥

我们只需要一个来自目标服务器的有效的、签名的JWT和一个众所周知的密钥的单词列表。然后,我们可以运行以下命令,将JWT和wordlist作为参数传入:

hashcat -a 0 -m 16500 <JWT> <wordlist>

Hashcat使用单词列表中的每个密钥对来自JWT的头部和有效载荷进行签名,然后将得到的签名与来自服务器的原始签名进行比较。如果任何签名匹配,hashcat将以以下格式输出识别的密钥,沿着各种其他细节:

<JWT>:<identified-secret>

如果多次运行该命令,则需要包含--show标志以输出结果。

由于hashcat在我们的机器上本地运行,并且不依赖于向服务器发送请求,因此此过程非常快,即使使用巨大的单词列表。

一旦我们确定了密钥,我们就可以使用它为我们喜欢的任何JWT头和有效负载生成有效的签名。

使用JWT_tool进行爆破
python3 JWT_tool.py JWT -C -d dict.txt

JWT头参数注入

据JWS规范,只有alg header参数是强制性的。然而,实际上,JWT头文件(也称为JOSE头文件)通常包含几个其他参数。以下是攻击者特别感兴趣的。

  • jwk(JSON Web Key)-提供一个嵌入的JSON对象,表示该密钥。
  • jku(JSON Web Key Set URL)-提供一个URL,服务器可以从中获取包含正确密钥的一组密钥。
  • kid(密钥ID)-提供一个ID,服务器可以在有多个密钥可供选择的情况下使用该ID来识别正确的密钥。根据密钥的格式,这可能具有匹配的kid参数。

这些用户可控制的参数每个都告诉接收方服务器在验证签名时使用哪个密钥。我们将学习如何利用这些方法来注入使用我们自己的任意密钥而不是服务器的密钥签名的修改过的JWT。

通过jwk参数注入自签名JWT

JSON Web Signature(JWS)规范描述了一个可选的jwk头参数,服务器可以使用该参数以JWK格式将其公钥直接嵌入令牌本身。

下面的JWT头文件例子:

{
    "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
    "typ": "JWT",
    "alg": "RS256",
    "jwk": {
        "kty": "RSA",
        "e": "AQAB",
        "kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
        "n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
    }
}

理想情况下,服务器应该只使用有限的公钥白名单来验证JWT签名。但是,错误配置的服务器有时会使用嵌入在jwk参数中的任何密钥。

我们可以通过使用我们自己的RSA私钥对修改后的JWT进行签名,然后将匹配的公钥嵌入到jwk头中来利用这种行为。

尽管我们可以手动添加或修改Burp中的jwk参数,但bp的JWT Editor扩展提供了一个有用的功能来帮助我们测试此漏洞:

  1. 加载扩展后,在Burp的主选项卡栏中,转到JWT编辑器密钥选项卡。
  2. 生成新的RSA密钥。
  3. 向Burp Repeater发送包含JWT的请求。
  4. 在消息编辑器中,切换到扩展生成的JSONWebToken选项卡,并根据需要修改令牌的有效负载。
  5. 单击Attack,然后选择Embedded JWK。出现提示时,选择新生成的RSA密钥。
  6. 发送请求以测试服务器如何响应。

我们也可以通过自己添加jwk头来手动执行此攻击。但是,我们可能还需要更新JWT的kid头参数,以匹配嵌入键的kid。扩展的内置攻击会为我们处理这一步。

通过jku参数注入自签名JWT

一些服务器允许我们使用jwk(JWK Set URL)头参数引用包含密钥的JWK Set,而不是直接使用jku头参数嵌入公钥。当验证签名时,服务器从该URL获取相关密钥。

JWK Set

JWK Set是一个JSON对象,包含一个表示不同键的JWK数组。你可以在下面看到一个例子。

{
    "keys": [
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "75d0ef47-af89-47a9-9061-7c02a610d5ab",
            "n": "o-yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw-fhvsWQ"
        },
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "d8fDFo-fS9-faS14a9-ASf99sa-7c1Ad5abA",
            "n": "fc3f-yy1wpYmffgXBxhAUJzHql79gNNQ_cb33HocCuJolwDqmk6GPM4Y_qTVX67WhsN3JvaFYw-dfg6DH-asAScw"
        }
    ]
}

像这样的JWK集有时会通过标准端点公开,例如/.well-known/jwks.json

更安全的网站只会从受信任的域中获取密钥,但有时我们可以利用URL解析差异来绕过这种过滤。

通过kid参数注入自签名JWT

服务器可以使用几个加密密钥来签署不同类型的数据,而不仅仅是JWT。出于这个原因,JWT的报头可能包含kid(密钥ID)参数,这有助于服务器在验证签名时识别使用哪个密钥。

验证密钥通常存储为JWK集。在这种情况下,服务器可以简单地寻找具有与令牌相同的kid的JWK。然而,JWS规范并没有为这个ID定义具体的结构–它只是开发人员选择的任意字符串。例如,它们可能使用kid参数来指向数据库中的特定条目,甚至是文件名。

如果此参数也容易受到目录遍历的攻击,攻击者可能会强迫服务器使用其文件系统中的任意文件作为验证密钥。

{
    "kid": "../../path/to/file",
    "typ": "JWT",
    "alg": "HS256",
    "k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}

如果服务器还支持使用对称算法签名的JWT,则这尤其危险。在这种情况下,攻击者可能会将kid参数指向一个可预测的静态文件,然后使用与该文件内容匹配的secret对JWT进行签名。

理论上,我们可以使用任何文件来执行此操作,但最简单的方法之一是使用/dev/null,这在大多数Linux系统中都存在。由于这是一个空文件,阅读它将返回一个空字符串。因此,使用空字符串签名令牌将产生有效签名。

如果我们使用的是JWTEditor扩展,请注意,这不允许我们使用空字符串对令牌进行签名。但是,由于扩展中的一个bug,我们可以通过使用Base64编码的空字节来解决这个问题。

如果服务器将其验证密钥存储在数据库中,则kid头参数也是SQL注入攻击的潜在向量。

其他有趣的JWT头参数

攻击者可能还感兴趣以下标头参数:

  • cty(内容类型)-有时用于声明JWT有效负载中的内容的媒体类型。这通常从头文件中省略,但底层解析库可能仍然支持它。如果你找到了绕过签名验证的方法,你可以尝试注入一个cty标头来将内容类型更改为text/xmlapplication/x-java-serialized-object,这可能会为XXE反序列化攻击启用新的向量。
  • x5c(X.509证书链)-有时用于传递用于数字签名JWT的密钥的X.509公钥证书或证书链。此头参数可用于注入自签名证书,类似于上面讨论的jwk头注入攻击。由于X.509格式及其扩展的复杂性,解析这些证书也可能引入漏洞。这些攻击的细节超出了这些材料的范围,但更多细节,请查看CVE-2017-2800CVE-2018-2633

JWT算法混淆

即使服务器使用了我们无法暴力破解的复杂密钥,我们仍然可以通过使用开发人员没有预料到的算法对令牌进行签名来伪造有效的JWT。这被称为算法混淆攻击。

这种攻击发生在攻击者能够强制服务器使用与网站开发人员预期不同的算法来验证JSON Web令牌(JWT)的签名时。如果这种情况处理不当,攻击者可能会伪造包含任意值的有效JWT,而不需要知道服务器的签名密钥。

攻击步骤

获取服务器的公钥

将公钥转换为合适的格式

创建一个恶意的JWT,修改有效负载,并将alg头设置为HS256

使用公钥对JWT进行签名。

靶场

https://authlab.digi.ninja/

https://portswigger.net/web-security/all-labs#jwt

https://hub.docker.com/r/webgoat/webgoat-8.0/

https://www.root-me.org/fr/Challenges/Web-Serveur/

参考链接

https://www.cnblogs.com/Webkio/p/16444506.html

https://www.freebuf.com/articles/web/292793.html

http://www.hackdig.com/04/hack-321742.htm

https://portswigger.net/web-security/jwt

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值