本文主要普及一下JWT(JSON Web Token)的相关知识以及安全性问题。
01
JWT简介
JWT是JSON web Token的缩写,它是为了在网络应用环境间传递声明而执行的一种基于JSON的开放式标准(RFC 7519),该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的业务逻辑所必须声明信息,该token也可被直接用于认证,也可用作加密。
02
JWT应用场景
这个是使用JWT最常见的场景,一旦用户登录,后续每个请求都将包括JWT,从而允许用户访问该令牌允许的路由、服务以及资源。单点登录(SSO)是目前使用JWT最广泛的一个场景,JWT的方式可以让用户在不同的域中轻松灵活使用。
JWT的头部承载两部分信息:
声明类型,这里是jwt,声明加密的算法 通常直接使用 HMAC SHA256。
完整的头部就像下面这样的JSON:
`{` `'typ': 'JWT',``'alg': 'HS256'``}`
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是可解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
`{` `"sub": "1234567890",` `"name": "John Doe",` `"admin": true``}`
然后将其进行base64加密,得到JWT的第二部分。
IxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9sig
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
`// javascript``var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);``var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ`
将这三部分用.
连接成一个完整的字符串,构成了最终的JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
一般是在请求头里加入Authorization
,并加上Bearer
标注:
`fetch('api/user/1', {` `headers: {` `'Authorization': 'Bearer ' + token` `}``})`
服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:
参考数据包如下:
`POST / HTTP/1.1``Host: xxxx` `Connection: close``Content-Length: 0``Pragma: no-cache``Origin: xxxx` `Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJuYW1lIjoi55yB5YWs5a6J5Y6FZ29uZ2FudGluZ19hZG1pbiIsInVpZCI6ImdvbmdhbnRpbmdfYWRtaW4iLCJ1c2VydHlwZSI6IjIiLCJmaXJzdFVwZGF0ZVB3ZCI6IjAiLCJpYXQiOjE2MDA2NzAwOTIsIm9yZ0lkIjoiNzVya2d2amVzYy1leC12dWZhZHd5cSJ9.CIYHEQYrGnmeJMOnjIL7F7yA_sqWm6LC2hZFgzsXwlexbs3PgYhcUx-iL7g6vL4_rwi5hyUX19AgOK9bfzmhag(jwt特征,带有Bearer字段)``Accept: application/json, text/plain, */*``Cache-Control: no-cache, no-store, must-revalidate``trim: true``User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36``Expires: 0``Referer:` `Accept-Language: zh-CN,zh;q=0.9``Cookie: JSESSIONID=C7J4Eyr9tSIeLmKY9xEe_wIpPLEA78gNmaKc1XKm; isClear=1; portal-token=eyJhbGciOiJIUzUxMiJ9.eyJuYW1lIjoi55yB5YWs5a6J5Y6FZ29uZ2FudGluZ19hZG1pbiIsInVpZCI6ImdvbmdhbnRpbmdfYWRtaW4iLCJ1c2VydHlwZSI6IjIiLCJmaXJzdFVwZGF0ZVB3ZCI6IjAiLCJpYXQiOjE2MDA2NzAwOTIsIm9yZ0lkIjoiNzVya2d2amVzYy1leC12dWZhZHd5cSJ9.CIYHEQYrGnmeJMOnjIL7F7yA_sqWm6LC2hZFgzsXwlexbs3PgYhcUx-iL7g6vL4_rwi5hyUX19AgOK9bfzmhag。`
关于jwt的使用安全性,其实国外已经有很详细的研究文章进行介绍了,具体参考地址: https://research.securitum.com/jwt-json-web-token-security/.
freebuf翻译版本地址: https://www.freebuf.com/vuls/219056.html
使用base64解密该密钥如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c,
所见,使用此“ API密钥”(其主要内容在payload中),我们可以实现身份验证(我有与API进行通信的特权)和授权(在上面的有效负载中,您可以看到示例操作)可以由密钥的所有者执行)。基本是常见的场景下,jwt是用来做身份校验的,识别请求者的身份以及用于鉴权。那么,从安全性的角度来看,至少存在两个潜在的问题。
1、缺乏机密性
我们能够轻松解码有效载荷payload(和报头header)。有时间就是这样要求的,但是当我们要求对令牌中发送的数据进行保密时,有一种更好的方法可以做到这一点:JWE(JSON Web加密)。
2、用户插入另一个操作(例如删除)并绕过授权的潜在可能性。
在这种情况下,解决方案是在令牌中使用签名(请注意,在上面的示例中,我们看到了“签名”)。标头中指示的HS256算法是标准的HMAC-SHA256 –一种确保整个消息完整性的机制(由于这样,用户无法更改有效负载)在签名验证期间检测篡改)。要配置HS256,您需要生成一个密钥(字符串)并将其放入API配置中。
综上所述,JWT看上去比API密钥灵活得多-您可以轻松地传输任何数据,确保其完整性,并在必要时保持机密性。此外,所有信息(秘密密钥除外)都可以位于令牌本身中。但是,世界上没有十全十美
问题一:JWT是一种非常复杂的机制。JWT / JWS / JWE / JWK,多种密码算法,两种不同的编码(序列化),压缩方式,一个以上签名的可能性,对多个接收者的加密-这些仅是几个示例。所有与JWT相关的规范都有300多个页面!
问题二:根据JWT的正式规范,虽然通常假定“适当的” JWT是带有签名(JWS)的JWT,但是签名不是强制性的
因此,会有这种header
`{``“alg “: “none “,``“typ “: “JWT “``}`
有趣的是,"none" 这个算法配置是根据RFC实现的两种算法之一,在JSON Web算法[JWA]中指定的签名和MAC算法中,仅"HS256"和"none"通过符合JWT的实现。
03
JWT漏洞攻击思路
方法一:修改签名算法
攻击者可以获得一个JWT(带有签名),对其进行更改(例如,添加新权限等),然后将其放在标头{" alg":"none"}中。然后将整个内容发送到API(带或不带签名)。这时候,服务器应该接受这样的令牌吗?从理论上讲是可以的,但是它将破坏JWT签名的整个思想。然而,这样的情况真的发生了。
在许多图书馆实施JWTs:
如果标头中有一个签名算法(例如HS256或HS512),但是我们从令牌中删除了整个签名部分,会发生什么?
案例链接https://github.com/FusionAuth/fusionauth-jwt/issues/2
正如你所看到的,有时候这样的令牌就会被验证,这是比上面方法更危险的配置。JWTDecoder.decode中的输入验证漏洞,即使缺少有效签名,该漏洞也可能导致JWT被解码并因此被隐式验证。
如果攻击者不知道如何创建适当的签名,也许会将其插入错误消息中https://github.com/jwt-dotnet/jwt/issues/61。
因此,如果有人更改了有效负载并将此类令牌发送给服务器,则服务器会礼貌地通知我们有关信息,并提供与我们的有效负载匹配的正确令牌。为了使系统正常运行,必须将服务器配置为向用户显示异常,虽然这很普遍,但是这是个不安全的配置。
另一个例子https://auth0.com/docs/security/bulletins/cve-2019-7644
低于1.0.4的所有Auth0-WCF-Service-JWT NuGet软件包版本 均在JWT签名验证失败时发出的错误消息中包含有关预期JWT签名的敏感信息。
由于加密字的强度过低,因此hmac的密钥可以被破解。破解jwt的加密字,标准方法采用API生成的令牌并运行经典的蛮力/字典/混合攻击。一次迭代需要计算两个SHA256哈希(这是HMAC-SHA256的工作方式),并且还有一些工具可以使整个操作自动化,例如hashcat使用GPU实现JWT密钥的破解。借助几个快速的GPU,您可以实现每秒超过十亿次检查的速度。而且,整个操作可以脱机完成,而无需与API进行任何交互(足以获得一个带有签名的任意令牌)。
hashcat -m 16500 jwt.txt -a 3 -w 3 ?a?a?a?a?a?a
破解工具:
c-jwt-cracker https://github.com/brendan-rius/c-jwt-cracker
hashcat
PyJWT
library https://github.com/jpadilla/pyjwt,https://github.com/hashcat/hashcat/issues/1057
因此,密钥太弱会被爆破,那么我们该用什么强度的密钥呢?
此算法必须使用与哈希输出大小相同的密钥(例如,“ HS256”为256位)或更大。(此要求基于NIST SP 800-117 [NIST.800-107]的第5.3.4节(HMAC密钥的安全性影响),其中规定,有效的安全性强度是密钥的安全强度中的最小值。两倍于内部哈希值的大小)。
很多jwt的安全问题来源于复杂的标准。到目前为止,JWS签名算法已经有HMAC和SHA256函数,但是这并不是唯一的选择,各种签名的描述可以在这个链接里找到https://auth0.com/blog/json-web-token-signing-algorithms-overview/
常见的选择是使用非对称算法-RSA。在这种情况下,我们将在header中的"alg":" RS512"或"alg":" RS256"中看到。
提醒一下:RSA私钥用于签名,与其关联的公钥可以验证签名。因此,在这种情况下,我们生成了一对RSA密钥,而不是对称密钥(如HS256算法中的对称密钥)。
如果您第一次看到RS512或RS256,您可能会想到使用512或256位RSA密钥的要求?
但是,这样的密钥可以以最小的成本和时间来破坏(请参阅:https ://eprint.iacr.org/2015/1000.pdf或https://www.theregister.co.uk/2010/01/07/rsa\_768\_broken/)。
即使是1024位RSA密钥也不被认为是安全的。幸运的是,这仅指向与RSA结合使用的特定SHA函数。例如,RS512表示RSA加SHA512功能。但是RSA密钥呢?长度由生成它的人员设置,这是另一个潜在的问题(此外,在不同的在线教程中,您可以使用OpenSSL并生成1024位密钥来找到特定的命令)
回到这一点,使用RSA算法,我们至少还有一个有趣的安全问题。如我之前所写,公钥用于签名验证,因此通常会在API配置中将其设置为verify_key。在这里,值得注意的是,对于HMAC,我们只有一个对称密钥同时用于签名和验证。
攻击者如何伪造JWT令牌?
1、他获得了一个公共密钥(它的名字表明它可以公开使用)。有时,它在JWT自身内部传输。
2、使用header中设置的HS256算法发送令牌(有效载荷已更改)(即HMAC,而不是RSA),并使用公共RSA密钥对令牌进行签名。是的,这里没有错误–我们使用公共RSA密钥(以字符串形式给出)作为HMAC的对称密钥。
3、服务器接收令牌,检查将哪种算法用于签名(HS256),验证密钥在配置中设置为公共RSA密钥。
4、签名经过验证(因为使用了完全相同的验证密钥来创建签名,并且攻击者将签名算法设置为HS256)。
尽管我们打算仅使用RSA验证令牌的签名,但有可能由用户提供签名算法。因此,要么我们只强制一个选定的签名算法(我们不提供通过更改令牌来更改它的可能性),要么让我们为我们支持的每种签名算法提供单独的验证方法(和密钥!)
下面是这个漏洞的真实案例:https://www.cvedetails.com/cve/CVE-2016-10555/。
攻击者可以在令牌中提供自己的密钥,然后API会使用该密钥进行验证!请参阅CVE-2018-0114中的节点 node-jose漏洞的细节:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0114
该漏洞是由于遵循JSON Web令牌(JWT)的JSON Web签名(JWS)标准而导致的节点丢失。该标准指定可以将表示公共密钥的JSON Web密钥(JWK)嵌入JWS的标头中。然后将此公钥信任进行验证。攻击者可以通过以下方法来伪造有效的JWS对象:删除原始签名,向标头添加新的公钥,然后使用与该JWS标头中嵌入的公钥关联的(攻击者拥有的)私钥对对象进行签名,从而利用此漏洞早于2016年,在Go-jose库(https://mailarchive.ietf.org/arch/msgif/jose/gQU\_C\_QURVuwmy-Q2qyVwPLQlcg)中检测到类似的漏洞。
在这里,您可以从几种算法中选择(消息本身的加密或用于加密消息的对称密钥的加密)。再次,有一些有趣的研究,即JWE的几种实现使攻击者可以恢复私钥:https://blogs.adobe.com/security/2017/03/critical-vulnerability-uncovered-in-json-encryption.html。更具体地说,ECDH-ES算法的实现存在问题(顺便说一下,在相关的RFC文档中是推荐级别https : //tools.ietf.org/html/rfc7518)。
也许以前的漏洞只是一个意外?让我们在GCM模式下查看AES:https ://rwc.iacr.org/2017/Slides/nguyen.quan.pdf。谷歌研究人员正在JWT的背景下写这篇文章,他们总结道:GCM很脆弱,但很少检查其实现。
我们还可以选择带有PKCS1v1.5填充的RSA算法。它出什么问题了?自1998年以来该问题已经知道:ftp://ftp.rsa.com/pub/pdfs/bulletn7.pdf。而有些人 概括起来是这样的:https://blog.cryptographyengineering.com/2012/09/06/on-provable-security-of-tls-part-1/
PKCS#1v1.5非常棒–如果您要教授关于如何攻击密码协议的课程。
使用JWE会永远注定失败吗?当然不是,但是值得验证我们是否使用了适当的安全加密算法(及其安全实现)。
现在,我们对众多选择感到有些不知所措。毕竟,我们只想在API端“解码”令牌并使用其中包含的信息。但是请记住,“decode”并不总是与“verify”相同,但是不同的库可能提供不同的功能来解码和/或验证令牌。可以在下面链接找到此类问题或疑问的示例。https://github.com/auth0/jwt-decode/issues/4
简而言之,如果我使用encode()函数,则可能只对BASE64URL的有效负载(或标头)进行解码,而无需进行任何验证。验证可以是一个单独的函数,尽管它也可以内置在decode()中。有时,是用户要求这种选项(在下面引用的情况下),有人要求重载decode()方法,以便它也可以接受令牌本身(没有密钥):
方法八:上下文相同令牌
JWT经常指出的优点之一是,无需执行对数据库的查询,即可实现身份验证(或授权-取决于将使用整个上下文的上下文)。此外,我们可以在几个独立的服务器(API)上并行执行此操作。毕竟,仅令牌的内容就足以在此处做出决定。它还有一个缺点–如果许多服务器上可用的签名密钥以某种方式泄漏了怎么办?当然,有可能生成使用适当密钥进行验证的所有机器所接受的正确签名的令牌。攻击者可以从中获得什么?例如,未经授权访问API函数或其他用户帐户。
在这种情况下,可以使用规范本身定义的某些参数:iss(发出者)和aud(听众)。多亏了他们,令牌才被我们的特定接收者接受。
{
“iss ” = “my_api “,
“login ” = “manager “,
“aud ” = “store_api “
}
如果特定令牌只能使用一次怎么办?让我们想象一个场景,当用户编写一个生成的令牌以执行我们API中的DELETE方法时。然后,例如一年后(理论上他不再拥有相应的权限)之后,他尝试再次使用它(所谓的重播攻击)。
为此,请使用以下声明:jti和exp。Jti(JWT ID)是令牌标识符,必须是唯一的,而exp是令牌到期日期的定义。这两个字段的组合将使我们在适当程度上缩短令牌的有效性及其唯一性。
但是,值得注意的是,我们是否正确实施了这两个部分。现在看看根本没有考虑exp值的bug(https://github.com/jwt-dotnet/jwt/issues/134)。
JWT不会在.NetCore中抛出ExpiredTokenException
库开发人员使用到期声明(不在JWT规范中)执行到期检查;报告后,该错误已得到纠正。
如果通过具有正确签名的字节接一个字节地检查来自JWS 的签名(由接受JWS的一方生成),并且如果验证在第一个不一致的字节上完成,则我们可能会受到时间攻击。
请注意,在这种情况下,我们拥有的匹配字节越多,需要的比较就越多,因此响应所需的时间越长。
可以通过生成连续的签名来观察响应时间,从签名的第一个字节开始,然后再移至第二个签名。有关此类攻击的确切描述,请参见:https://hackernoon.com/can-timing-attack-be-a-practical-security-threat-on-jwt-signature-ba3c8340dea9
04
参考文章
https://research.securitum.com/jwt-json-web-token-security/
https://www.jianshu.com/p/576dbf44b2ae
https://www.freebuf.com/tag/JWT
https://www.freebuf.com/vuls/211842.html
如有侵权,请联系删除
好文推荐
关注我,学习网络安全不迷路
求点赞关注~