JWT 全称 JSON Web Token,是一种标准化格式,用于在系统之间发送加密签名的 JSON 数据。
原始的 Token 只是一个 uuid,没有任何意义。
JWT的结构由三部分组成,分别是Header、Payload和Signature,下面是每一部分的详细介绍和示例:
在 JWT 中 Header 部分存储的是 Token 类型和加密算法,通常使用JSON对象表示并使用Base64编码,其中包含两个字段:alg和typ
下面是一个示例Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload包含了JWT的主要信息,通常使用JSON对象表示并使用Base64编码,Payload中包含三个类型的字段:注册声明、公共声明和私有声明
下面是一个示例Payload:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
其中sub表示主题,name表示名称,iat表示JWT的签发时间
Signature是使用指定算法对Header和Payload进行签名生成的,用于验证JWT的完整性和真实性
下面是一个示例Signature
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
其中HMACSHA256是使用HMAC SHA256算法进行签名,header和payload是经过Base64编码的Header和Payload,secret是用于签名和验证的密钥,最终将Header、Payload和Signature连接起来用句点(.)分隔就形成了一个完整的JWT
第一部分是Header,第二部分是Payload,第三部分是Signature,它们之间由三个 .
分隔,注意JWT 中的每一部分都是经过Base64编码的,但并不是加密的,因此JWT中的信息是可以被解密的
下面是一个示例JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
下面是一个JWT在线构造和解构的平台:
JWT的工作流程如下:
JWT库会通常提供一种验证令牌的方法和一种解码令牌的方法,比如:Node.js库jsonwebtoken有verify()和decode(),有时开发人员会混淆这两种方法,只将传入的令牌传递给decode()方法,这意味着应用程序根本不验证签名
下边我们通过portswigger靶场来演示一下这个漏洞案例:
靶场地址:https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature
(1)首先看看通关要求:修改您的会话令牌以访问管理面板/admin
,然后删除用户carlos
(2)前文我们说到,JWT 需要开发者提供一个 Signature(签名),如果我们不对签名进行验证,极有可能产生如下的越权情况。
(3)打开靶场,登录,访问/admin
(4)因为我们使用的jwt,所以权限相关的设置肯定在jwt中。我们抓个包拿到jwt解密看看
(5)把wiener
修改成administrator
把第二部分payload拿出来base64解密,然后修改,修改万再拼接会jwt中
(6)把修改后的jwt替换原本的jwt
(7)访问/admin
,删除carlos
用户即可通关
在JWT的Header中alg的值用于告诉服务器使用哪种算法对令牌进行签名,从而告诉服务器在验证签名时需要使用哪种算法,目前可以选择HS256,即HMAC和SHA256,JWT同时也支持将算法设定为”None”,如果”alg”字段设为”None”,则标识不签名,这样一来任何token都是有效的,设定该功能的最初目的是为了方便调试,但是若不在生产环境中关闭该功能,攻击者可以通过将alg字段设置为”None”来伪造他们想要的任何token,接着便可以使用伪造的token冒充任意用户登陆网站
下边我们通过portswigger靶场来演示一下这个漏洞案例:
这关与上边的漏洞原理不同,但最终的效果都是可以伪造token
,攻击手法与上一关卡相同,唯一不同的是这次是需要把header中的alg参数的值改为none
即可!
不在演示!
在实现JWT应用程序时,开发人员有时会犯一些错误,比如:忘记更改默认密码或占位符密码,他们甚至可能复制并粘贴他们在网上找到的代码片段然后忘记更改作为示例提供的硬编码秘密,在这种情况下攻击者使用众所周知的秘钥来暴力破解服务器的秘钥是很容易的
下边我们通过portswigger靶场来演示一下这个漏洞案例:
靶场地址:https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-weak-signing-key
(1)打开靶场 —> 登录 —> 抓包 —> 拿到JWT
eyJraWQiOiIwZWMxNmY3ZS0yOGU0LTQxMjUtYjUxMS0yZDc1ZmRjZjRiM2QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTcwMzI2MDMwOH0.t5QY8-kd-bzIqp0PyXg2EUUxg1jPl6-NYKBI4BTw8P0
(2)JWT字典:https://github.com/wallarm/jwt-secrets
(3)我们使用jwt_tool
来爆破JWT:https://github.com/ticarpi/jwt_tool
使用教程:https://www.cnblogs.com/xiaozi/p/12005929.html
(4)拿到爆破出来的秘钥secret1
,我们篡改jwt
(5)修改本地或者抓包修改jwt都可以,然后访问/admin,删除carlos
用户
(1)再来回顾一下jwk是什么吧!
JWK(JSON Web Key):JWK是指用于JWT的密钥。它可以是对称加密密钥(例如密码),也可以是非对称加密密钥(例如公钥/私钥对)
(2)漏洞原理
在理想情况下,服务器应该是只使用公钥白名单来验证JWT签名的,但对于一些相关配置错误的服务器会用JWK参数中嵌入的任何密钥进行验证,攻击者就可以利用这一行为,用自己的RSA私钥对修改过的JWT进行签名,然后在JWK头部中嵌入对应的公钥进行越权操作
(3) RSA加密算法
https://www.cnblogs.com/pcheng/p/9629621.html
这位师傅的文章总结的非常好,公钥加密、私钥解密、私钥签名、公钥验签。
下边我们通过portswigger靶场来演示一下这个漏洞案例:
靶场地址:https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-jwk-header-injection
(1)这个案例我们使用burpsuite
,首先先去安装一个插件jwt enditor
(2)安装好了后我们使用它生成一个新的RSA密钥
(3)打开靶场 —> 登录 —> bp抓包 —> 发送到Repeat
模块
(4)将sub内容修改为administrator
(5)点击”Attack”,然后选择”Embedded JWK”,
(6)出现提示时选择您新生成的RSA密钥
(7)复制新生成的jwt
(8)替换本地的jwt
(9)然后访问/admin,删除carlos
用户
(1)先回顾什么是jku?
JKU(JSON Web Key Set URL):JKU是JWT Header中的一个字段,该字段包含一个URI,用于指定用于验证令牌密钥的服务器。当需要获取公钥或密钥集合时,可以使用JKU字段指定的URI来获取相关的JWK信息。
(2)看看他长什么样?
下边我们通过portswigger靶场来演示一下这个漏洞案例:
靶场地址:https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-jku-header-injection
(1)分析一下
漏电点都是仅通过公钥判断数据是否被篡改,但公钥在header头中,用户可控!
唯一的区别就是这关中的公钥使用服务器获取的。
(2)那还是之前的流程
jwt enditor
生成一个新的RSA密钥,使用之前的也可以Repeat
模块(3)复制公钥,拿到漏洞利用服务器(在题目中选择”Go eo exploit server”,然后加上key头并保存到exploit的body中)
切记加key
头
{
"keys": [
]
}
(4)在bp中需要改动三处
(5)点击下面的sign,选择Don’t modify header模式
(6)之后的流程
替换本地jwt —> 删除carlos
用户
JWS 规范没有针对 kid 进行严格设置,比如必须是 uuid 的格式或者是其他的,它只是开发人员选择的任意字符串。
那么我们可以通过将 kid 指向数据库中的特定条目,或者是指向文件名,作为验证密钥。
例如:
{
"kid": "../../path/to/file",
"typ": "JWT",
"alg": "HS256",
"k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}
当 JWT 使用的是对称加密算法的时候,极有可能存在目录遍历的漏洞,我们能够强制服务器使用其文件系统中的任意文件作为验证密钥。
我们可以先尝试读取dev/null
这一文件,dev/null
这一文件默认为空文件,返回为 null,我们可以在 Symmetric Key 中,将 k 值修改为AA==
也就是 null,进行攻击。
下边我们通过portswigger靶场来演示一下这个漏洞案例:
靶场地址:https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-kid-header-path-traversal
(1)使用bp插件jwt enditor
生成一个 Symmetric Key,也就是对称密钥,并将 k 的值修改为AA==
(2)打开靶场 —> 登录 —> bp抓包 —> 发送到Repeat
模块
(3)接着,我们在抓到的包中修改 kid 值,尝试用目录遍历读取dev/null
此文件。并将 sub 修改为 administrator
(4)点击下面的 Sign,使用 OCT8 的密钥攻击。
(5)接着就是:替换本地jwt —> 删除carlos
用户
因为是目录遍历,所以多尝试。
可以使用一系列不同的算法对 JWT 进行签名。其中一些,如HS256(HMAC + SHA-256)使用“对称”密钥。这意味着服务器使用单个密钥对 Token 进行签名和验证。显然,这需要保密,就像密码一样。
其他算法,例如 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 密钥。这意味着攻击者可以使用 HS256 和公钥对令牌进行签名,并且服务器将使用相同的公钥来验证签名。
上边是抄官方的话,下边我们用大白话来解释一下:
RS256
这非对称加密算法生成的jwt。公钥
,因为上边说过公钥通常是共享的HS256
算法伪造一个jwt,用这个公钥
作为签名的密钥。程序会使用verify()
这个方法来验证jwt有没有被篡改。但是这个库设计的有问题(问题:他是通过你jwt头中alg
来判断是使用那种算法来进行签名的。所以我们可以篡改他的算法),这块就会使用RS256
生成的公钥作为HS256
的秘钥来验证攻击者伪造的jwt。这个公钥攻击者可控,所以伪造的jwt就会通过验证。
算法混淆攻击通常涉及以下高级步骤:
alg
标头设置为HS256
.下边我们通过portswigger靶场来演示一下这个漏洞案例:
(1)获取服务器的公钥
服务器有时通过映射到/jwks.json或/.well-known/jwks.json的端点将它们的公钥公开为JSON Web Key(JWK)对象,比如大家熟知的/jwks.json,这些可能被存储在一个称为密钥的jwk数组中,这就是众所周知的JWK集合
(2)将公钥转换为合适的格式
在Burpsuite的JWT Editor
中点击”New RSA Key”,用上边获取到泄露的JWK而生成一个新的RSA Key
选中”Copy Public Key as PEM”,同时将其进行base64编码操作,保存一下得到的字符串(备注:上下的一串——-END PUBLIC KEY——-不要删掉)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj+eOXtjDkD6BYr0ftlLo
rnU+xsXB2btxi4REHYghwP4YCiZjX7UsPvEYRWyt8FJzyQap+zUoueiFTWBt/Ngt
qOCQWPUDMv9BQ3Kjpos6yC/PM8TEmJLsg0F2b1OcIoDuPgo9v0JWmSmpS+THqUwH
xgizbwFBbZxS+aGPV9vv0KyULDV2CLjWjbyYh+2sJZFW7DFq1EHWedtqmTcY3/Gt
Sv3CBNdv9Hn/J5d5P9gorrbuKrPnc2qD967poetwrmI/9TxQdCVSEjqLdBqEIzBg
IbLRST2J0DNHX54ESyjcutmfRG833wEm1c8S98bhG3eGx+HpqX5/hkPPlwdZTPTy
9QIDAQAB
-----END PUBLIC KEY-----
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFqK2VPWHRqRGtENkJZcjBmdGxMbwpyblUreHNYQjJidHhpNFJFSFlnaHdQNFlDaVpqWDdVc1B2RVlSV3l0OEZKenlRYXArelVvdWVpRlRXQnQvTmd0CnFPQ1FXUFVETXY5QlEzS2pwb3M2eUMvUE04VEVtSkxzZzBGMmIxT2NJb0R1UGdvOXYwSldtU21wUytUSHFVd0gKeGdpemJ3RkJiWnhTK2FHUFY5dnYwS3lVTERWMkNMaldqYnlZaCsyc0paRlc3REZxMUVIV2VkdHFtVGNZMy9HdApTdjNDQk5kdjlIbi9KNWQ1UDlnb3JyYnVLclBuYzJxRDk2N3BvZXR3cm1JLzlUeFFkQ1ZTRWpxTGRCcUVJekJnCkliTFJTVDJKMEROSFg1NEVTeWpjdXRtZlJHODMzd0VtMWM4Uzk4YmhHM2VHeCtIcHFYNS9oa1BQbHdkWlRQVHkKOVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
(3)生成一个对称加密的key,把k替换成我们刚修改完格式的公钥
(4)篡改jwt。
(5)使用这个公钥签名
(6)替换本地jwt —> 删除carlos
用户
在公钥不可用的情况下您仍然可以通过使用jwt _ forgery.py之类的工具从一对现有的JWT中获取密钥来测试算法混淆,您可以在rsa_sign2n GitHub存储库中找到几个有用的脚本
https://github.com/silentsignal/rsa_sign2n
下边我们通过portswigger靶场来演示一下这个漏洞案例:
(1)打开靶场,首先我们先正常登录、退出,拿到两个jwt
eyJraWQiOiI2YTFkYjRlYS1hNGJmLTQ0NzQtYjQxMC0zYzk5NTc3YzJhMWMiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTcwMzMyOTc4OX0.BCzT_VhEaeqfxPoRBYwduIju0AXpmPqJ8HzM-7iqYquyNx2NgPRiJNVvYemspXIPQ8_-sGr67Qn6lVSHgj51xoNd_jTJfYO8AlQWF4oAiz2Hfjfng6DN7VoiuJ7vQCMh9VSWnzLzG30leaEzyRjzHGnbFE9EUZ5Hbu7tXOFJU5IwHE35TuU5Xcnv2DXpRDxTsJpHvk5gKQWPx4XLNOY--8LJncBRUoDXD7jXCW0hdY19DPkIDI_xNKYi27sGkShv8_zf3G5oSVdChCVgSfdGyCivTtuQ3pAeTl1AwmJll9wy7v4MunWSMgXD_-SyiCakBNgPaMm_gWfTlATrrPPT_g
eyJraWQiOiI2YTFkYjRlYS1hNGJmLTQ0NzQtYjQxMC0zYzk5NTc3YzJhMWMiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTcwMzMyOTgxM30.IeXISe59u7Ju0k-NU2RUXORZlY4uKOOpDQC01TSVq35asYsRSUnh7yKS4bwooI6L5wSiZduaYbFNQeg2Na0RDvJx6-wIYR8LlEVQ9U2n7_8Z7pPkF9QVQDJGF6mUhQAEOS35gBFUOwvYsR2WaayKaOH_nSOJbFiQjOzF8EykR0LEz5vk2NhMYYMDbbO1LGJ5i2QBtIB5SfTwlZPiy7lK9d_Une2a0FwmeaoNA_4dIsiVo4hD3Av-DT_voCN9pSN-AuoofKqJYwolxHatfUGP4ONuVRwcVScJmvaH2UAh4YI1deRCk62nChBhmBt6TTclGn9xzJX7TeGfqsn6wmWQhA
(2)随后将其放到靶场提供的docker工具里面运行,运行的命令如下:
docker run --rm -it portswigger/sig2n <token1> <token2>
jwt _ forgery.py脚本会输出一系列token的存在情况值
这里我们尝试每一个Tempered JWT,不过靶场这里给了提示说是X.509 形式的,所以我们只需要将X.509形式的JWT进行验证即可
(3)剩下的步骤结合上一关的一样了,创建恶意的jwt —> 签名 —> 替换本地jwt —> 删除carlos
用户
就拿我之前碰到过的两个漏洞来说明!严格意义上不能说是jwt的问题,应该是程序设计的问题,但是跟jwt沾了一点边!
访问管理后台,/adplanet/PlanetUser页面下有用户管理功能!
我们访问该页面并抓包,发现api:/square/GetAllSquareUser,用于获取用户信息,包括用户名、密码、邮箱地址等敏感信息!
我们把这个数据包放入webfuzz模块中,删除他的JWT token,然后发送数据包,我们可以发现我们仍然可以获取到用户信息!
和上边类似,只是另一个不同的功能带点。
还是抓包后删jwt,但是发现返回401
此时,我们去前台注册一个普通用户获取他的jwt。携带这个jwt发包!
成功垂直越权!
https://paper.seebug.org/3057/#_1
https://www.freebuf.com/articles/web/337347.html
https://www.cnblogs.com/pcheng/p/9629621.html
https://www.cnblogs.com/xiaozi/p/12005929.html
https://portswigger.net/web-security/jwt/algorithm-confusion
转载自公众号:安服仔Yu9
原文地址:https://mp.weixin.qq.com/s/3rraOTO3Z-n9GzxFco5FOA
9 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!