01 jwt简介
jwt全称为json web token,将json对象作为载体来传输信息,一般被用在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的业务逻辑所必须声明信息,该token可被直接用于认证,也可用作加密。
json web令牌(jwt)是一种标准化的格式,用于在系统之间发送经过加密签名的json数据。理论上jwt可以包含任何类型的数据,但最常用于进行身份认证、会话处理和访问控制。与传统的会话令牌不同,服务器需要的所有数据都存储在jwt本身的客户端。这使得jwt成为高度分布式网站的热门选择,在这些网站中,用户需要与多个后端服务器进行无缝交互。
1.1 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机制的安全性都严重依赖于密码签名。
1.2 jwt签名
颁发令牌的服务器通常通过对头部和载荷计算哈希值来生成签名。有时会对产生的哈希值进行加密处理。但是无论哪种方式,这个过程都涉及一个秘密密钥。如果密钥未知,就无法为给定的头部和载荷生成有效的签名。这实际上就为服务器提供了一种验证令牌颁发以来数据是否被篡改的机制,因为任何对头部或载荷部分的修改都会使签名不再匹配。
02 jwt、jws与jwe
jwt规范的约束实际上是非常有限的。因为它只定义了将信息(“声明”)表示为可以在双方之间传输的json对象的格式。在实际使用中,jwt并没有真正作为一个独立的实体使用。jwt规范由json web签名(jws)和json web加密(jwe)规范组成,共同定义了实际实现jwt的具体方法。
也就是说,jwt通常是指jws或jwe令牌,jwe同理,只是令牌的实际内容是经过加密的。
2.1 jwt攻击
jwt攻击是指用户向服务器发送修改过的jwt,执行恶意操作。通常情况下,比较常见的是冒充已经通过身份认证的用户,绕过认证和访问控制。如果攻击者能够用任意值创建自己的有效令牌,他们就能够提升自己的权限或冒充其他用户,从而完全接管这些用户的账户。
2.2 攻击原理
jwt漏洞通常是由于应用程序本身对jwt的处理有缺陷而产生的。与jwt有关的各种规范在设计上相对灵活,例如允许网站开发人员自行决定许多实现细节。但这也可能会引发一些安全问题。这些实现缺陷通常意味着jwt的签名没有被正确验证。即使严格检查签名,攻击者也可以通过令牌的载荷篡改传递给应用程序的值。签名是否可以信任在很大程度上也取决于服务器的秘钥是否仍然是安全的。如果这个密钥被泄露或者破解,那么攻击者就可以为任意令牌生成有效的签名,从而攻陷整个机制。
2.3 jwt签名验证
根据设计,一般情况下服务器不存储任何关于颁发的jwt的信息。因此每个令牌都是一个完全独立的实体。虽然这样做有许多优点,但也导致了一个隐患,即服务器实际上不知道关于令牌的原始内容,甚至不知道原始签名是什么。正因如此,如果服务器没有正确效验签名,也就无法阻止攻击者对令牌的其他部分任意篡改。
例如,一个包含以下声明的jwt如下:
{
"username": "carlos",
"isadmin": false
}
如果服务器是根据username来识别会话,那么攻击者就能够通过修改用户名来冒充其他已登录的用户。同样,如果isadmin值被用于访问控制,攻击者也可以提通过篡改这个值来实现提权。
03 jwt相关漏洞类型
3.1 接受任意签名
jwt库通常会提供一个验证令牌的方法,同时提供对其解码的方法。例如,对于node.js库jsonwebtoken来说,这两个方法分别是verify()和decode()。
但有时开发人员会混淆这两个方法,只把传入的令牌传给decode()方法。这实际上意味着应用程序根本就没有对签名进行验证。
3.2 接受未签名的令牌
jwt头部还包含一个alg参数。该参数的作用就是告诉服务器对令牌进行签名时使用的是哪种算法,换句话说就是在验证签名时需要使用哪种算法。
{
"alg": "hs256",
"typ": "jwt"
}
但本质上这种方法存在安全隐患,因为服务器只能隐式地信任提供令牌的用户的输入(注意,这些输入受控于该用户),而该令牌根本没有被验证过。换句话说攻击者可以直接影响服务器检查令牌是否值得信任的方式。
jwt既可以使用一系列不同的算法进行签名,也可以不签名。在这种情况下,alg参数被设置为none,表示所谓的 "不安全的jwt"。由于这种情况明显存在安全问题,因此服务器通常会拒绝没有签名的令牌。但由于这种过滤依赖于字符串解析,所以攻击者可以使用混淆技术绕过这些过滤器,如混合大写和非预期的编码等。(需要注意的是,即使令牌是未签名的,载荷部分也必须以点号结尾。)
3.3 暴力破解密钥
某些签名算法,例如hs256(hmac sha-256),会像密码一样使用一个任意的、独立的字符串作为秘密密钥。需要保证这个秘钥不被轻易猜到或暴力破解,否则攻击者能以任意的头部和载荷值来创建jwt,然后用密钥重新给令牌签名。
在实现jwt应用时,开发人员有时会忘记改变默认或占位的密码,甚至可能复制和粘贴在网上找到的代码片段,然后忘记改变作为示例提供的硬编码的密码。在这种情况下,攻击者使用流行的密码本,可以轻松对服务器的登陆凭据进行暴力破解。
3.4 jwt头部参数注入
根据jws规范,只有头部参数alg是必需的。然而实际中jwt头部(也称为jose头部)通常包含其他几个参数。以下是攻击者特别感兴趣的参数:
jwk(json web key):提供一个表示密钥的嵌入式json对象。
jku(json web key set url):提供一个url,服务器可以从中获取一组包含正确密钥的密钥。
kid(key id):提供一个id,在有多个密钥可供选择的情况下,服务器可以使用该id来识别正确的密钥。根据密钥的格式可能还有一个匹配的kid参数。
如上,这些用户可控制的参数用于告诉接收方服务器在验证签名时使用哪些密钥。
3.5 jwk参数注入
json web签名(jws)规范描述了一个可选的jwk头部参数,服务器可以用它将其公钥直接嵌入jwk格式的令牌本身。jwk(json web密钥)是一种标准化的格式,用于将密钥表示为json对象。
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参数,但jwt编辑器扩展提供了一个非常方便的功能,用于帮助研究人员测试这个漏洞:
在加载该扩展后,在burp的主选项卡栏中,转到jwt editor keys选项卡。
创建一个新的rsa密钥。
向burp repeater发送一个包含jwt的请求。
在消息编辑器中,切换到扩展生成的json web token选项卡,并以你喜欢的方式修改令牌的载荷。
点击attack按钮,然后选择embedded jwk。当收到提示时,选择新生成的rsa密钥。
发送请求,测试服务器的响应情况。
有些服务器并不会直接使用jwk头部参数来嵌入公钥,而是使用jku(jwk set url)头部参数来引用一个包含密钥的jwk set。当验证签名时,服务器会从这个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解析的差异来绕过这种过滤机制。
3.7 kid参数注入
服务器可能会使用多个加密密钥来为不同类型的数据进行签名。出于这个原因,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。该文件是一个空文件,读取时将返回null,可以用一个base64编码的null字节来给令牌签名将得到一个有效的签名。
如果服务器将其验证密钥存储在数据库中,kid头部参数也是一个潜在的sql注入攻击的载体。
3.8其他jwt头部参数
cty(内容类型):用来声明jwt载荷中内容的媒体类型。通常情况下会省略该参数,但底层解析库可能还是支持它。如果攻击者已经找到了绕过签名验证的方法,可能会尝试注入cty参数,将内容类型改为text/xml或application/x-java-serialized-object,这有可能为xxe和反序列化攻击提供新的向量。
x5c(x.509证书链):用于传递用于对jwt进行数字签名的x.509公钥证书或证书链。这个头部参数可用于注入自签证书,类似于上面讨论的jwk头部注入攻击。由于x.509格式及其扩展的复杂性,解析这些证书也很可能会引入漏洞。
3.9 jwt算法混淆
即使服务器使用了攻击者无法破解的强密码,对方仍然可以使用开发人员没有预料到的算法签名令牌来伪造有效的jwt,这就是所谓的算法混淆攻击。
04 防御jwt攻击
使用最新的库来处理jwt,并确保开发人员对相关安全问题足够了解。现代代码库的使用降低了在代码实现中引入安全漏洞的可能性,但由于相关规范固有的灵活性,也并非万无一失。
确保对收到的任何jwt进行严格的签名验证,并考虑边缘情况,如使用非预期的算法签名的jwt。
为jku头部提供允许主机白名单,并严格执行。
确保不会受到kid头部参数路径穿越或sql注入的影响。
05 参考链接
https://portswigger.net/web-security/jwt
https://blog.csdn.net/cdyunaq/article/details/122561096
- 关键词标签:
- 检测与防护能力 jwt攻击类型