什么是JWT
JWT全称JSON Web Token,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。JWT通过将用户信息封装成token的方式进行身份验证。
JWT的组成
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
JWT 这种结构化体可以分为HEADER(头部)、PAYLOAD(数据体)和 SIGNATURE(签名)三部分。经过签名之后的 JWT 的整体结构,是被句点符号分割的三段内容,结构为 header.payload.signature。详情请见https://jwt.io/introduction
例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
在https://jwt.io/网站中解密得到
HEADER:该部分其实也是一个 JSON 对象,表示装载令牌类型和算法等信息,是 JWT 的头部。其中,typ 表示第二部分 PAYLOAD 是 JWT 类型,alg 表示使用 HS256 对称签名的算法。
PAYLOAD:数据体主要是我们的结构化数据,这组数据基本上都是我们自定义的信息。但是有几个关键点大家需要注意一下:exp(令牌的过期时间戳)、iat(令牌颁发的时间戳)
而基本的payload的部分由如下部分组成:
iss: 该JWT的签发者
sub: 该JWT所面向的用户
aud: 接收该JWT的一方
exp(expires): 什么时候过期,这里是一个Unix时间戳
iat(issued at): 在什么时候签发的
SIGNATURE:表示对 JWT 信息的签名。其实就是对HEADER与PAYLOAD进行一次加密处理,主要是验证整个JWT内容有没有被篡改。首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
这几个部分当然不是直接写在token中,那样太明显了,而是由base64加密成字符串,JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。
JWT攻击实施
从之前的JWT分析过程中我们发现我们可以伪造任意用户获得无限的访问权限,而且还可能进一步发现更多的安全漏洞,如信息泄露,越权访问,SQLi,XSS,SSRF,RCE,LFI等。
但是我们要利用这个漏洞首先就要知道这个是否是利用了JWT进行身份验证
验证是否使用JWT
首先我们需要识别应用程序正在使用JWT,最简单的方法是在代理工具的历史记录中搜索JWT正则表达式:
[= ]ey[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*-稳定的JWT版本 [= ]ey[A-Za-z0-9_\/+-]*\.[A-Za-z0-9._\/+-]*-所有JWT版本(可能误报)
确保同时“区分大小写”和匹配“正则表达式”
这里是使用常用的burpsuit来进行演示的
JWT漏洞利用
当我们获得一个基于JWT的token时,我们可以利用什么呢?(攻击方式)
被泄露的敏感信息
由于Header和Payload部分是使用可逆base64方法编码的,因此任何能够看到令牌的人都可以读取数据。可以通过base64decode分别将每个部分解码就可以去阅读token内的数据。
防御措施:由于其中明文传递的username和password都是可以看到的(因此,Token不能随意公布,发送的数据不得包含任何敏感数据(例如密码))
将算法修改为none
算法是指将Header部分的alg(全称algorithm),也就是alg字段被设置为空,那么SIGNATURE的签名部分将被置空,这样的话任何token都将是有效的
设定该功能的最初目的是为了方便调试。但是,若不在生产环境中关闭该功能,攻击者可以通过将alg字段设置为“None”来伪造他们想要的任何token,接着便可以使用伪造的token冒充任意用户登陆网站。
例如:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4iLCJhY3Rpb24iOiJ1cGxvYWQifQ.
这就是将alg设置为none的结果,解构后的JWT为:
{"typ":"JWT","alg":"none"}.
{"user":"admin","action":"upload"}.
[No signature!]
那么如果返回页面为有效页面那么攻击成功,存在此种漏洞,反之不存在
防御措施:JWT配置应该指定所需的签名算法,不要指定”none”。(也就是必须是用此种算法得来的token才会被接受s)
密钥混淆攻击
JWT最常用的两种算法是HMAC和RSA。
HMAC(对称加密算法)用同一个密钥对token进行签名和认证。
而RSA(非对称加密算法)需要两个密钥,先用私钥加密生成JWT,然后使用其对应的公钥来解密验证。
如果将alg参数改成HS256,这样就将算法RS256修改为HS256,让服务器不使用对称加密,转而使用非对称加密算法
那么,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名。由于公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。
后端代码会使用RSA公钥+HS256算法进行签名验证。进而攻击者将达到目的。
防御措施:JWT配置应该只允许使用HMAC算法或公钥算法,决不能同时使用这两种算法。
无效签名攻击
无效签名是指当用户端提交请求给应用程序,服务端可能没有对token签名进行校验,这样,攻击者便可以通过提供无效签名简单地绕过安全机制。
实际上,就和上面将算法置空很像,同样是签名无效了,导致攻击者只需要更换user参数就可以实现越权访问,可能是横向越权也可能是纵向越权
防御措施:严格审核JWT签名
暴力破解密钥或密钥泄露
HMAC签名密钥(例如HS256 / HS384 / HS512)使用对称加密,这意味着对令牌进行签名的密钥也用于对其进行验证。由于签名验证是一个自包含的过程,因此可以测试令牌本身的有效密钥,而不必将其发送回应用程序进行验证。
我们可以利用github上的工具c-jwt-cracker来完成,但是局限性很大。
首先秘钥不能太复杂,其次还需要一段已知的签过名的token。
如果可以破解HMAC密钥,则可以伪造令牌中的任何内容,攻击者就可以随意的进行越权。
如果我们无法通过暴力破解,那么可以通过其他手段得到密钥也可以实现伪造令牌的任何内容,实现各种操作
修改KID
kid(key ID)是JWT的header部分的可选参数,作用是用来指定加密算法秘钥。
开发人员可以用它标识认证token的某一密钥
例如:
{ "alg": "HS256", "typ": "JWT", "kid": "1" //使用密钥1验证token //“kid”:”/home/jwt/1.pem” //这个kid参数还可以是文件路径 }
由于kid参数是用户可控的,那么攻击者就可以用来构造攻击,比如服务器更新了密钥,但是旧的密钥并未删除且该密钥已经被攻击者所知,或者通过ftp等渠道上传了密钥,那么就会导致攻击者能够直接控制签名验证从而绕过签名
KID的其他攻击利用方式:
- sql注入
KID也可以用于在数据库中检索密钥。在该情况下,攻击者很可能会利用SQL注入来绕过JWT安全机制。
通过对KID的修改,导致数据库信息泄露
例如:
“kid”:”1 ' UNION SELECT 'key';--” //使用字符串"key"验证token
这个注入会导致应用程序返回字符串“ key”,因此我们得到了key字符串的值,服务器也将以key的值来验证token
举例:
【网鼎杯2020玄武组】js_on
通过提示,我们知道存在token注入,token分为三段,第一段为header,第二段为payload,第三段为签名,header可以不变,payload存在自定义字段
将payload的进行base64解码
{"user":"admin","news":"key: xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6"}
这里的user字段实际就是注入点
写入payload:
{"user":"admin'/**/and/**/'1'='2'#","news":"key: xRt*YMDqyCCxYxi9a@LgcGpnmM2X8i&6"}
将其进行base64加密,之后通过jwt官方网站得到token
jwt官网:https://jwt.io/#debugger
- RCE(任意命令执行)
有时候服务器会直接通过函数对KID文件进行调用,这时候我们可以通过这类函数执行系统命令,例如Ruby open()。攻击者只需在输入的KID文件名后面添加命令,即可执行系统命令
“kid”: "key_file" | whoami;
理论上,每当应用程序将未审查的头部文件参数传递给类似system(),exec()的函数时,都会产生此种漏洞。
操纵头部参数
除KID外,JWT标准还能让开发人员通过URL指定密钥。
下面是可以用来进行密钥指定的头部参数:
JKU
JKU全称JWKSet URL,它是头部的一个可选字段,用于指定链接到一组加密token密钥的URL。若允许使用该字段且不设置限定条件,攻击者就能托管自己的密钥文件,并指定应用程序,用它来认证token。
JWK
JWK全称JSON Web Key,使得攻击者能将认证的密钥直接嵌入token中
X5U,X5C
和JKU,JWK类似,攻击者可以通过对这种头部参数的控制决定验证Token的公钥证书或证书链。但是不同的是x5u以URI形式指定信息,而x5c允许将证书值嵌入token中。