0x00 引言
JWT(JSON Web Tokens)是一个应用层消息保护开放标准(RFC 7519),规定了一种Token实现方式,以JSON为格式。它在近年来被广泛应用于各种认证机制中,但也存在被误用的风险及由此产生的安全问题。
0X01 JWT
RFC 7519将JWT定义为一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对进行签名。
在互联网领域中,JWT适用领域非常普遍,其主要使用场景有:
场景描述 | 发起方 | 接收方 | JWT对象 |
---|---|---|---|
客户端应用程序注册 | 客户端应用程序 | 授权服务器 | 软件声明 |
OIDC身份认证 | 认证服务器 | 客户端应用程序 | 身份令牌 |
OAuth授权 | 客户端应用程序 | 授权服务器 | 访问令牌 |
其他自定义JWT结构 | API服务器端或API网关 | API接口后端服务 | JWE/JWS |
JWT本质上是一个字符串,分为三个部分:
Header: 存放Token类型和加密的方法
Payload: 包含一些用户身份信息.
Signature: 签名是将前面的Header,Payload信息以及一个密钥组合起来并使用Header中的算法进行加密
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{
"typ": "JWT",
"alg": "HS256"
}
在头部指明了签名算法是HS256算法。
当然头部也要进行BASE64编码,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(Payload)
{ "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(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
其他还有:nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;是否使用是可选的;
除此之外,payload中还可以包含更多有关该Token使用者的身份信息。
将上面的JSON对象进行base64编码可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。
eyJpc3MiOiJhZG1pbiIsImlhdCI6MTQxNjc5NDU2NiwiZXhwIjoxNTU4MzMzNDE5LCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJhZG1pbkBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6ImFkbWluIiwiU3VybmFtZSI6InJvb3QiLCJFbWFpbCI6ImFyb290QGV4YW1wbGUuY29tIiwiUm19sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19
签名(Signature)
将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTQxNjc5NDU2NiwiZXhwIjoxNTU4MzMzNDE5LCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJhZG1pbkBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6ImFkbWluIiwiU3VybmFtZSI6InJvb3QiLCJFbWFpbCI6ImFyb290QGV4YW1wbGUuY29tIiwiUm19sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19
最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用test作为密钥的话,那么就可以得到我们加密后的内容:
qmjRJ4WqDSunPEixLePXNGxotrhZCjxGFpHWl_EjNXg
最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT,它可以作为Token放在下文所述的三个位置中的任何一个。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTQxNjc5NDU2NiwiZXhwIjoxNTU4MzMzNDE5LCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJhZG1pbkBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6ImFkbWluIiwiU3VybmFtZSI6InJvb3QiLCJFbWFpbCI6ImFyb290QGV4YW1wbGUuY29tIiwiUm19sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.qmjRJ4WqDSunPEixLePXNGxotrhZCjxGFpHWl_EjNXg
即为
base64(header).base64(payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
0X02 其他概念介绍
1.Token Auth
在计算机科学中Token的含义十分广泛,而在Web安全中,我们通常所说的Token就是一次会话中验证身份和获取信息的凭证,典型代表有OAuth中的Access Token。会话Token是对客户端进行请求的令牌,本质上是由服务端生成的一串字符串,当第一次登录后,服务器会生成一个Token,并且将其返回给客户端,在之后的操作中,会话无需验证用户名和密码,客户端只需带上这个Token,就可请求数据。
Token的类型可分为两种:
bearer
. 包含一个简单的Token字符串.
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer mF_9.B5f-4.1JqM
mac
. 由消息授权码(Message Authentication Code)和Token组成.
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: MAC id="h480djs93hd8",
nonce="274312:dj83hs9s",
mac="kDZvddkndxvhGRXZhvuDjEhGeE="
使用Token的认证请求的方式有三种
放在请求头
放在请求体
放在URI
客户端可以选择一种来实现,但是不能同时使用多种。同样的,开发者也可使用不同的值来作为Token,常见的方法有用设备号/设备mac地址作为Token、用session值作为Token等。
2.JOSE
JOSE 是一种旨在提供在各方之间安全传递声明(claims)的方法的规范集,其中设计的技术均与JWT相关,主要有以下几个部分构成:
JWS-JSON Web签名,针对JWT格式进行数字签名的操作规范
JWE-JSON Web加密,针对JSON数据进行加密的操作规范
JWK-JSON Web密钥,用于JSON对象描述加密密钥、纪要及的数据结构和表示方式
JWA-JSON Web算法,用于JWS或JWE所使用的数字签名或加密算法列表
JWT-JSON Web令牌,描述以JSON对象在通信交互过程中使用的,一种可以签名或加密的标准数据格式
以上五者的关系如下图所示。
3.JWKS
JSON Web密钥集(The JSON Web Key Set,JWKS)是一组密钥,其中包含公钥,用于验证授权服务器发布并使用RSA或ECDSA算法签名的任何JSON Web令牌(JWT)。
0x03 漏洞利用角度
1.利用角度综述
对于常用身份认证机制,通用的漏洞挖掘角度包括:
是否存在敏感信息泄露
存在鉴权不充分而产生的劫持
是否存在伪造
是否可以被爆破
………
2. JWT 常见漏洞存在位置
而对于JWT来说,它所面临的安全问题则更加棘手。这是因为JWT是一种非常复杂的机制,如上文所述,其包含JWT、JWS、JWE、JWK、多种密码算法、两种不同的编码方式(或者说序列化)、压缩、可能采用不止一种签名、对多个收件人的加密——这其中无论哪一个微小的环节出问题,都会导致漏洞的产生。
(1)Header部分
是否支持修改算法为none/对称加密算法
是否可以删除签名
插入错误信息
kid字段是否有SQL注入/命令注入/目录遍历
jwk元素是否可信
是否强制使用白名单上的加密算法
(2)Payload部分
其中是否存在敏感信息检查过期策略,比如 exp, iat
(3)Signature部分
检查是否强制检查签名
密钥是否可以爆破(如HMAC)
是否可以通过其他方式拿到密钥
采用了自身存在脆弱性的算法(如ECDH-ES)
签名方法之间是否存在冲突
(4)其他
重放
通过匹配校验的时间做时间攻击
修改算法非对称算法为对称算法(如修改RS256为HS256)
弱密钥破解
不安全的配置所导致的敏感信息泄露(如在报错信息中泄露签名)
0x04 漏洞利用工具
笔者在此仅列举一部分工具,其余大同小异的工具不再赘述。
1. 解码jwt
https://jwt.io/
2.破解及漏洞利用
jwt_tool
查看帮助说明:python3 jwt_tool.py -h
usage: jwt_tool.py [-h] [-b] [-t TARGETURL] [-rc COOKIES] [-rh HEADERS] [-pd POSTDATA] [-cv CANARYVALUE] [-np] [-M MODE] [-X EXPLOIT] [-ju JWKSURL] [-S SIGN]
[-pr PRIVKEY] [-T] [-I] [-hc HEADERCLAIM] [-pc PAYLOADCLAIM] [-hv HEADERVALUE] [-pv PAYLOADVALUE] [-C] [-d DICT] [-p PASSWORD] [-kf KEYFILE] [-V]
[-pk PUBKEY] [-jw JWKSFILE] [-Q QUERY] [-v]
[jwt]
positional arguments:
#位置参数:JWT
jwt the JWT to tinker with (no need to specify if in header/cookies)
#要处理的的JWT(当在标头/cookie中时,无需指定)
optional arguments:
#可选参数
-h, --help show this help message and exit
#显示帮助信息并且推出
-b, --bare return TOKENS ONLY
#只返回TOKEN
-t TARGETURL, --targeturl TARGETURL
URL to send HTTP request to with new JWT
#包含新JWT向目标URL发送HTTP请求
-rc COOKIES, --cookies COOKIES
request cookies to send with the forged HTTP request
#向目标URL发送构造的HTTP请求cookie
-rh HEADERS, --headers HEADERS
request headers to send with the forged HTTP request (can be used multiple times for additional headers)
#与伪造HTTP请求一起发送的请求头(可多次用于其他头)
-pd POSTDATA, --postdata POSTDATA
text string that contains all the data to be sent in a POST request
#包含POST请求中要发送的所有数据的文本字符串
-cv CANARYVALUE, --canaryvalue CANARYVALUE
text string that appears in response for valid token (e.g. "Welcome, ticarpi")
#响应有效令牌出现的文本字符串(例如“Welcome,ticarpi”)
-np, --noproxy disable proxy for current request (change in jwtconf.ini if permanent)
#禁用当前请求的代理(如果永久更改jwtconf.ini)
-M MODE, --mode MODE Scanning mode:
#扫描模式:
pb = playbook audit
#标准审计
er = fuzz existing claims to force errors
#对现有的强制错误声明进行模糊测试
cc = fuzz common claims
#对通用声明进行模糊测试
at - All Tests!
#所有测试
-X EXPLOIT, --exploit EXPLOIT
eXploit known vulnerabilities:
#利用已知漏洞:
a = alg:none
n = null signature
b = blank password accepted in signature
#签名中接受的空白密码
s = spoof JWKS (specify JWKS URL with -ju, or set in jwtconf.ini to automate this attack)
#欺骗JWKS(使用-ju指定JWKS URL,或在jwtconf.ini中设置以自动执行此攻击)
k = key confusion (specify public key with -pk)
#密钥混淆(使用-pk指定公钥)
i = inject inline JWKS
#注入内联JWKS
-ju JWKSURL, --jwksurl JWKSURL
URL location where you can host a spoofed JWKS
#URL位置,您可以在其中托管伪造的JWKS
-S SIGN, --sign SIGN sign the resulting token:
hs256/hs384/hs512 = HMAC-SHA signing (specify a secret with -k/-p)
rs256/rs384/hs512 = RSA signing (specify an RSA private key with -pr)
es256/es384/es512 = Elliptic Curve signing (specify an EC private key with -pr)
ps256/ps384/ps512 = PSS-RSA signing (specify an RSA private key with -pr)
-pr PRIVKEY, --privkey PRIVKEY
Private Key for Asymmetric crypto
#非对称密码的私钥
-T, --tamper tamper with the JWT contents
#篡改JWT内容
(set signing options with -S or use exploits with -X)
-I, --injectclaims inject new claims and update existing claims with new values
#注入新声明并使用新值更新现有声明
(set signing options with -S or use exploits with -X)
(set target claim with -hc/-pc and injection values/lists with -hv/-pv
-hc HEADERCLAIM, --headerclaim HEADERCLAIM
Header claim to tamper with
#标题声明篡改
-pc PAYLOADCLAIM, --payloadclaim PAYLOADCLAIM
Payload claim to tamper with
#声称篡改的有效载荷
-hv HEADERVALUE, --headervalue HEADERVALUE
Value (or file containing values) to inject into tampered header claim
#值(或包含值的文件)注入到篡改的头声明中
-pv PAYLOADVALUE, --payloadvalue PAYLOADVALUE
Value (or file containing values) to inject into tampered payload claim
#值(或包含值的文件)注入到篡改的有效负载声明中
-C, --crack crack key for an HMAC-SHA token
(specify -d/-p/-kf)
#HMAC-SHA令牌的破解密钥
-d DICT, --dict DICT dictionary file for cracking
#用于破解的字典文件
-p PASSWORD, --password PASSWORD
password for cracking
#用于破解的密码
-kf KEYFILE, --keyfile KEYFILE
keyfile for cracking (when signed with 'kid' attacks)
#用于破解的密钥文件(使用“kid”攻击签名时)
-V, --verify verify the RSA signature against a Public Key
(specify -pk/-jw)
#根据公钥验证RSA签名
-pk PUBKEY, --pubkey PUBKEY
Public Key for Asymmetric crypto
#非对称密码体制的公钥
-jw JWKSFILE, --jwksfile JWKSFILE
JSON Web Key Store for Asymmetric crypto
#用于非对称加密的JSON Web密钥存储
-Q QUERY, --query QUERY
Query a token ID against the logfile to see the details of that request
#根据日志文件查询令牌ID以查看该请求的详细信息
e.g. -Q jwttool_46820e62fe25c10a3f5498e426a9f03a
-v, --verbose When parsing and printing, produce (slightly more) verbose output.
#在解析和打印时,生成(稍微多一些)详细的输出。
例如:使用JWT_TOOL进行漏洞检测
python3 jwt_tool.py [jwt] -X a
含义:显示存在"alg":"none",更改Headers 中"alg"为none,Payload中"admin"为true.也就是说,通过把算法置空,篡改身份来达到越权的目的。
JSON Web Token Attacker-JOSEPH
特性:
识别和标记
JWS/JWE编辑器
(半)自动攻击
Bleichenbacher MMA
密钥混淆(aka算法替换)
签名排除
Base64url 编/解码器
新攻击的易扩展性
JSON Web Tokens-JWT4B(burp插件)
特性:
自动识别
JWT编辑器
JWTs重签名
签名检查
自动攻击可用,如“Alg无”和“CVE-2018-0114”
有效性检查并支持有效负载中的“expires”、“not before”、“issued at”字段
cookie传输JWTs中安全标志的自动测试
jwt-hack
jwtXploiter
0X04 靶场演示
我们在这里使用的靶场为webgoat。
使用docker一键式安装运行docker run -it -p 127.0.0.1:80:8888 -p 127.0.0.1:8080:8080 -p 127.0.0.1:9090:9090 -e TZ=Europe/Amsterdam webgoat/goatandwolf:v8.2.1
或pull image下来之后再docker run -p 8080:8080 -t webgoat/webgoat-8.0
启动镜像后,访问 http://[IP]:[端口]/WebGoat/login 即可进入webgoat靶场
Tips-使用正则表达在代理工具的历史中搜索JWT特征语句:
ey[A-Za-z0-9_-]*\.[A-Za-z0-9._-]* //网址安全的JWT版本
ey[A-Za-z0-9_\/+-]*\.[A-Za-z0-9._\/+-]* //所有JWT版本(可能误报)
此处选择了Task4作为例子讲解jwt漏洞的利用。在这个题目中,我们需要更改令牌,并通过更改令牌成为管理员用户,一旦成为管理员,就能够重置投票。
先随便登陆一个账号。
我们可以看到Reset votes是旁边这个选项,但只有管理员才能使用。
我们先获取一个当前用户的token。
试着解码一下:
使用jwt_tools的“-T”参数进行调试,生成新JWT.
验证一下:
使用该jwt提交题目,成功取得了管理员权限,完成赛题。
0x05参考资料
《API安全技术与实战》钱君生等.机械工业出版社