JSON Web Token 攻击面
学习笔记,来源于前人文章的整合。
0×00 什么是JWT
JWT ( JSON Web Token
的缩写)是一串带有声明信息的字符串,由服务端用加密算法对信息签名来保证其完整性和不可伪造。Token里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息,JWT可用于身份认证、会话状态维持、信息交换等。
JWT 由三部分构成,分别称为 header
、payload
和 signature
,各部分用.
相连构成一个完整的Token,形如xxxxx.yyyyy.zzzzz
。
分别看下各个部分:
header :
使用一个JSON格式字符串声明token的类型和签名用的算法等,形如{"alg": "HS256", "typ": "JWT"}
。该字符串经过Base64Url编码后形成JWT的第一部分xxxxx
。
Base64Url编码可以用这段代码直观理解:
from base64 import *
def base64URLen(s):
t0=b64encode(s)
t1=t0.strip('=').replace('+','-').replace('/','_')
return t1
def base64URLde(s):
t0=s.replace('-','+').replace('_','/')
t1=t0+'='*(4-len(t0)%4)%4
return b64decode(t1)
payload :
使用一个JSON格式字符串描述所要声明的信息,分为 registered
、public
、 和 private
三类,形如{"name": "John Doe", "admin": true}
。
同样的,该字符串经过Base64Url编码形成JWT的第二部分yyyyy
。
signature :
将 xxxxx.yyyyy
使用alg
指定的算法加密,然后再Base64Url编码得到JWT的第三部分zzzzz
。所支持的算法 类型取决于实现,但HS256
和 none
是强制要求实现的。
0×01 JWT 攻击手段
参考网站:https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries//。
1. 敏感信息泄露
JWT中的header
和 payload
虽然看起来不可读,但实际上都只经过简单编码,开发者可能误将敏感信息存储在里面。使用上述工具可以方便地解码JWT中前两部分的信息。
当服务端的秘钥泄密的时候,JWT的伪造就变得非常简单容易。对此,服务端应该妥善保管好私钥,以免被他人窃取。
2. 指定算法为none
上面提到算法 none
是JWT规范中强制要求实现的,但有些实现JWT的库直接将使用none
算法的token视为已经过校验。这样攻击者就可以设置alg
为none
,使signature
部分为空,然后构造包含任意payload
的JWT来欺骗服务端。
签名算法确保恶意用户在传输过程中不会修改JWT。但是标题中的alg字段可以更改为none。一些JWT库支持无算法,即没有签名算法。当alg为none时,后端将不执行签名验证。将alg更改为none后,从JWT中删除签名数据(仅标题+’.'+ payload +’.')并将其提交给服务器。
解决对策:
不允许出现 none 的方法;
将开启 alg : none 作为一种额外的配置选项。
3. 将算法RS256修改为HS256(非对称密码算法=>对称密码算法)
HS256使用密钥来签名和验证每个消息。而RS256使用私钥对消息进行签名并使用公钥进行认证。
如果将算法从RS256更改为HS256,则后端代码使用公钥作为密钥,然后使用HS256算法验证签名。由于攻击者有时可以获取公钥,因此攻击者可以将标头中的算法修改为HS256,然后使用RSA公钥对数据进行签名。
此时,后端代码就会使用RSA公钥+HS256算法进行签名验证,从而让验证通过。
解决对策:
不允许 HS256等对称加密 算法读取秘钥。jwtpy就是限制了这种方法。当读取到 类似于 “— xxx key —” 的参数的时候应抛出错误;
将秘钥与验证算法相互匹配。
4. 爆破密钥
JWT的安全性依赖于密钥的保密性,任何拥有密钥的人都可以构造任何内容的合法token。
当一个JSON Web Token 被分发出去,如果密钥不够强壮就存在被爆破的风险,而且整个爆破过程可以离线进行。
已经有人写了一些工具,推荐如下:
5. 错误的堆叠加密+签名验证假设
错误的堆叠加密
这种攻击发生在单个的或者嵌套的JWE中,我们想象一个JWE如下所示:
JWT RAW
header : ...
payload: "admin" : false
"uid" : 123
"umail" : 123@126.com
...
JWE Main
protected / unprotected
recipients:
en_key : key1
en_key : key2
cipher : xxx
在攻击者不修改秘钥的情况下,对于ciphertext进行修改。往往会导致解密的失败。但是,即使是失败,很多JWT的解密也是会有输出的,在没有附加认证数据(ADD)的情况下更是如此。攻击者对于ciphertext的内容进行修改,可能会让其他的数据无法解密,但是只要最后输出的payload中,有“admin”:true。 其目的就已经达到了。
解决对策:
对于JWE而言,应当解密所有数据,而非从解密的结果中提取单个需要的数据。另外,利用附加认证数据ADD,也是非常好的选择。
6. 签名假设验证
这种攻击发生嵌套的JWS中。我们想象一个嵌套的JWS,其包括了两层的部分,其结构如下:
JWT Main
JWT Sub1
payload
Signature2
Signature
现在,攻击者通过一定的方式,能够让外层的验证通过的时候,此时,系统还应该检查内层的签名数据,如果不检查,攻击者就可以随意篡改payload的数据,来达到越权的目的。
解决对策:
因此对于嵌套JWS而言,应当验证所有层面的签名是否正确,而非验证最外层的签名是否正确就足够。
6. 无效椭圆曲线攻击
椭圆曲线加密是一种非常安全的方式,甚至从某种程度上而言,比RSA更加安全。关于椭圆曲线的算法,在此不展开。
在椭圆曲线加密中,公钥是椭圆曲线上的一个点,而私钥只是一个位于特殊但非常大的范围内的数字。 如果未验证对这些操作的输入,那攻击者就可以进行设计,从而恢复私钥。
而这种攻击已在过去中得到证实。这类攻击被称为无效曲线攻击。这种攻击比较复杂,也设计到很多的数学知识。详细可以参考文档:critical-vulnerability-uncovered-in-json-encryption。
解决对策:
检查传递给任何公共函数的所有输入是否有效是解决这类攻击的关键点。验证内容包括公钥是所选曲线的有效椭圆曲线点,以及私钥位于有效值范围内。
7. 替换攻击
在这种攻击中,攻击者需要至少获得两种不同的JWT,然后攻击者可以将令牌中的一个或者两个用在其他的地方。
在JWT中,替换共叽有两种方式,我们称他们为相同接收方攻击(跨越式JWT)和不同接收方攻击。
不同接收方攻击
我们可以设想一个业务逻辑如下:
Auth 机构,有着自己的私钥,并且给 App1 和 App2 发放了两个公钥,用于验证签名;
Attacker 利用自己的秘钥登录了 App1。
此时 Auth 机构给 Attacker 下发了一个 附带签名的JWT,其payload内容为:
{
'uname':'Attacker'
'role' :'admin'
}
此时,如果 Attacker 知道 App1 和 App2 的公钥是同一个Auth 签发的话,他可以利用这个JWT去登录 App2,从而获取Admin权限。
解决方法:
在jwt中带上 aud 声明,比如 aud : App1 这样。来限定该jwt只能用于App1。
相同接收方攻击/跨越式JWT same recipient/Cross JWT
我们可以设想一个业务逻辑如下:
在同一站点下,有两个应用程序,wordpress和phpmyadmin,他们都利用了相同的秘钥对和算法来验证JWT签名;
站点管理员知道 Different Recipient 的问题,所以给 wordpress 的应用增加了 aud 验证,但是 phpmyadmin 的用户人数较少,没有增加 aud 的验证;
Attacker 利用自己的秘钥登录了 wordpress。
此时 站点 给 Attacker 下发了一个 附带签名的JWT,其payload内容为:
{
'uname':'Attacker'
'role' :'writer'
'aud' :'shaobaobaoer.cn/wordpress'
'iss' :'shaobaobaoer.cn'
}
这个JWT看似非常安全,但这仅仅是对于 wordpress 的应用程序而言,。从而Attacker 可以以 writer 的身份登录 phpmyadmin。
解决方案:
为所有子应用程序增加 aud 的验证
8. 其他假想的攻击方式
JWT + SQL 注入
参考链接:https://github.com/greunion/ctf-write-ups/tree/master/2018-nullcon/web/400-web6。
当解密JWT的秘钥很多的时候,往往需要通过kid来确定使用哪个秘钥,而keyid参数通过b64加密来保存,可以被篡改。当keyid要通过数据库API拿取得时候,往往就会联想到sql 注入。
我们可以想象一下的攻击情况:
$keyID = $token-> getKeyID();
$keyContent = sqlAPI -> fromKeyidGetKeyContent($keyID)
###
class sqlAPI():
function fromKeyidGetKeyContent($keyID){
$result= Query("select key_content from keyTable where key_id = '$keyID'");
return $result['key_content']
}
###
if($token-> verify($JWA,$keyContetn)){
echo $flag;
}
在下列比较简易的代码中,通过让数据库返回值为我们自定义的key_content。就可以达到破解JWT的目的。
通过注入:
' union select 'easy' limit 1,1--+
即可让秘钥改成easy。
0x02 CTF中的应用
利用JWT进行身份验证的缺陷性,进行利用密钥伪造身份构造token或利用算法签名缺陷伪造进行JWT攻击。
通过对secret的获取是token伪造的一个关键点,一般secret在题目中常隐藏较深,通常作为判断JWT之后的首个突破点,如果对加密算法爆破失败通常需要利用题目其他信息进行发现,常见的有文件泄露,文件包含漏洞,利用其他页面信息获取等。其次多对JWT的利用多从其加密算法入手,通过对加密算法的修改,进而突破。
常用工具
加解密验证:https://jwt.io/
暴力破解:
https://github.com/brendan-rius/c-jwt-cracker
https://github.com/jmaxxz/jwtbrute
https://github.com/ticarpi/jwt_tool
案例记录
- Juice Shop JWT issue 1 :修改alg为none
- 网鼎杯i_am_admin : 通过低权用户登陆取得SECRET后伪造admin突破身份验证
- 网鼎杯mmmmy : 爆破secret
- b00t2root : 修改私有声明
- CTF论剑场 : 文件泄露+修改标准中注册的声明
- 2020 虎符网络安全赛道 : easy_login
0×03 安全建议
- 验证函数应忽略JWT中的
algo
字段,预先就明确JWT使用的算法,如果需要使用多种算法,可以在header
中使用表示”key ID” 的kid
字段,查询每个kid
对应的算法。 - JWT/JWS 标准应该移除
header
中的algo
字段。JWT的许多安全缺陷都来自于开发者依赖这一客户端可控的字段。 - 开发者应升级相应库到最新版本,因为旧版本可能存在致命缺陷。