JWT安全&预编译CASE注入

一.JavaWeb SQL注入攻击与预编译绕过

一、SQL注入防御方法

  1. Session防御

// 使用session防止用户篡改参数

String sql = "SELECT * FROM users WHERE user = '" + session.getAttribute("UserID") + "'";

// session存储在服务器端,相对安全

  1. 预编译机制(参数绑定)

// 正确使用PreparedStatement

String sql = "SELECT * FROM users WHERE username = ?";

PreparedStatement pstmt = connection.prepareStatement(sql);

pstmt.setString(1, username); // 参数绑定,防止注入

原理:SQL语句预编译后,参数值不会作为SQL指令执行

二、预编译机制绕过技术

ORDER BY注入绕过

-- 利用CASE WHEN进行注入

SELECT * FROM users ORDER BY

(CASE WHEN (SELECT SUBSTRING(password,1,1) FROM users WHERE id=1)='a'

THEN username ELSE id END);

-- 可构造盲注攻击,逐字符猜测数据

二.Java 的jwt令牌原理

JWT(JSON Web Token)身份验证攻击

一、JWT基本结构

  1. 三部分组成(Base64URL编码)

Header.Payload.Signature

  1. Header(头部)

{

"alg": "HS256", // 签名算法(HS256、HS512、None等)

"typ": "JWT" // 令牌类型

}

  1. Payload(声明/载荷)

包含用户信息和其他元数据:

{

"sub": "user123", // 主题(用户ID)

"name": "John Doe",

"exp": 1609459200, // 过期时间

"iat": 1609455600, // 签发时间

"role": "admin" // 用户角色

}

  1. Signature(签名)

text

HMACSHA256(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

secret

)

二、JWT修改伪造攻击

  1. 算法修改攻击(alg: "none")

// 原始Header

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

// 修改为

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

利用方式:

将算法改为none,删除签名部分

2.服务器可能接受无签名的JWT令牌 未验证声明攻击

修改payload中的敏感字段:

// 原始

{"user": "guest", "role": "user"}

// 修改为

{"user": "admin", "role": "administrator"}

简单的思路如图所示:(同时注意更改结束时间exp,在线网站里面更改时间戳)

可以使用靶场webgoat

下载最新jar包

wget https://github.com/WebGoat/WebGoat/releases/download/v8.2.2/webgoat-server-8.2.2.jar

wget https://github.com/WebGoat/WebGoat/releases/download/v8.2.2/webwolf-8.2.2.jar

启动WebGoat

java -jar webgoat-server-8.2.2.jar --server.port=8080

启动WebWolf(另一个终端)

java -jar webwolf-8.2.2.jar --server.port=9090

方法就跟上面的图和ctf的思路大同小异

三.CTF-Node.js- 前端JWT登录安全-伪造admin实现getflag

https://www.ctfhub.com/#/challenge

进靶场是一个登录的界面

注册登录后尝试抓包

发现:

username=lllxxy&password=123&authorization=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MCwidXNlcm5hbWUiOiJsbGx4eHkiLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTc2NjQ5ODM3Nn0.KGLNWQEYzwpwTxRLi8_1rw1RWJuHa4Qj-XMqNKThVbs

authorization=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MCwidXNlcm5hbWUiOiJsbGx4eHkiLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTc2NjQ5ODM3Nn0.KGLNWQEYzwpwTxRLi8_1rw1RWJuHa4Qj-XMqNKThVbs很有可能是Jwt

用jwt在线网站输入得到:

Set-Cookie: sses:aok=eyJ1c2VybmFtZSI6ImxsbHh4eSIsIl9leHBpcmUiOjE3NjY1ODQ3OTAyNzMsIl9tYXhBZ2UiOjg2NDAwMDAwfQ==; path=/; expires=Wed, 24 Dec 2025 13:59:50 GMT; httponly

Set-Cookie: sses:aok.sig=E5V4dAA2JYpmdhXgmsR55lmAfEs; path=/; expires=Wed, 24 Dec 2025 13:59:50 GMT; httponly

然后放包

发现

可以找到:http://challenge-b9b05e4e541de457.sandbox.ctfhub.com:10800/static/js/app.js

内容:

复制代码
/**
 *  或许该用 koa-static 来处理静态文件
 *  路径该怎么配置?不管了先填个根目录XD
 */

function login() {
    const username = $("#username").val();
    const password = $("#password").val();
    const token = sessionStorage.getItem("token");
    $.post("/api/login", {username, password, authorization:token})
        .done(function(data) {
            const {status} = data;
            if(status) {
                document.location = "/home";
            }
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function register() {
    const username = $("#username").val();
    const password = $("#password").val();
    $.post("/api/register", {username, password})
        .done(function(data) {
            const { token } = data;
            sessionStorage.setItem('token', token);
            document.location = "/login";
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function logout() {
    $.get('/api/logout').done(function(data) {
        const {status} = data;
        if(status) {
            document.location = '/login';
        }
    });
}

function getflag() {
    $.get('/api/flag').done(function(data) {
        const {flag} = data;
        $("#username").val(flag);
    }).fail(function(xhr, textStatus, errorThrown) {
        alert(xhr.responseJSON.message);
    });
}

然后:http://challenge-b9b05e4e541de457.sandbox.ctfhub.com:10800/controllers/api.js

内容:

复制代码
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
    'POST /api/register': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || username === 'admin'){
            throw new APIError('register error', 'wrong username');
        }

        if(global.secrets.length > 100000) {
            global.secrets = [];
        }

        const secret = crypto.randomBytes(18).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret)

        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
        
        ctx.rest({
            token: token
        });

        await next();
    },
    
    'POST /api/login': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || !password) {
            throw new APIError('login error', 'username or password is necessary');
        }
        
        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
        
        console.log(sid)
        
        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

        const secret = global.secrets[sid];

        const user = jwt.verify(token, secret, {algorithm: 'HS256'});

        const status = username === user.username && password === user.password;

        if(status) {
            ctx.session.username = username;
        }

        ctx.rest({
            status
        });

        await next();
    },

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

    'GET /api/logout': async (ctx, next) => {
        ctx.session.username = null;
        ctx.rest({
            status: true
        })
        await next();
    }
};

用普通注册用户的时候:

"code":"permission error","message":"permission denied"

​ 这里我们分析一下逻辑,注册不能注册成admin, 但只有登录后session 中username为admin才可得到flag 所以这里我们伪造登录token 我们先注册一个账户发现会返回一个token 然后登录时token会作为验证 ​修改时间戳和用户名

直接让ai给你生成一个

构造攻击载荷:

Header: {"alg":"none","typ":"JWT"} (绕过签名验证)

Payload: {"secretid":[],"username":"admin","password":"123","iat":1734978500}

secretid: [] 利用 Node.js 比较特性 ([] >= 0 为 true) 绕过逻辑检查。

username: "admin" 实现提权。

发起攻击:通过 /api/login 接口提交伪造的JWT,成功获取管理员会话凭证 (sses:aok 和 sses:aok.sig)。

获取凭证:利用管理员Cookie访问受保护的 /api/flag 接口,最终获得Flag。

这是一个将 JWT 协议层漏洞 (alg: none) 与 应用程序逻辑漏洞 (secretid 验证) 结合利用

最后得到:"flag":"ctfhub{f268ba1c0a94d24e10275b03}\n"

相关推荐
BOB-wangbaohai27 分钟前
软考-系统架构师-数据库系统(二)
数据库·数据分析·软考·系统架构师
冉冰学姐28 分钟前
SSM校园人才市场391d8(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·开题报告·java 开发·ssm 框架应用
橘橙黄又青1 小时前
redis复习(2)
数据库·redis·缓存
计算机毕设VX:Fegn08958 小时前
计算机毕业设计|基于springboot + vue医院设备管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Mr__Miss8 小时前
保持redis和数据库一致性(双写一致性)
数据库·redis·spring
Knight_AL9 小时前
Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式
数据库·sql·spring
倔强的石头_9 小时前
时序数据时代的“存储与分析困局”解析及金仓解决方案
数据库
计算机毕设VX:Fegn08959 小时前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
倔强的石头_10 小时前
场景化落地指南——金仓时序数据库在关键行业的应用实践
数据库
SelectDB11 小时前
驾驭 CPU 与编译器:Apache Doris 实现极致性能的底层逻辑
运维·数据库·apache