Skip to content

Instantly share code, notes, and snippets.

@ajmeese7
Created December 5, 2022 00:52
Show Gist options
  • Save ajmeese7/3a3e19f4641455efcf3ed4dc4286fe43 to your computer and use it in GitHub Desktop.
Save ajmeese7/3a3e19f4641455efcf3ed4dc4286fe43 to your computer and use it in GitHub Desktop.
Medium > HTB University CTF 2022 "The Magic Informer" code snippets
import { decode } from "../helpers/JWTHelper.js";
const AdminMiddleware = async (req, res, next) => {
try{
if (req.cookies.session === undefined) {
if(!req.is('application/json')) return res.redirect('/');
return res.status(401).json({ status: 'unauthorized', message: 'Authentication required!' });
}
return decode(req.cookies.session)
.then(user => {
req.user = user;
if (req.user.username !== 'admin') return res.redirect('/dashboard');
return next();
})
.catch(() => {
res.redirect('/logout');
});
} catch(e) {
console.log(e);
return res.redirect('/logout');
}
}
export { AdminMiddleware };
import * as dotenv from 'dotenv';
import cookieParser from "cookie-parser";
import path from "path";
import express from "express";
import nunjucks from "nunjucks";
import fileUpload from "express-fileupload";
import * as router from "./routes/index.js";
import { Database } from "./database.js";
dotenv.config({path: '/app/debug.env'});
const app = express();
const db = new Database('admin.db');
app.use(express.json());
app.use(cookieParser());
app.use(
fileUpload({
limits: {
fileSize: 2 * 1024 * 1024 // 2 MB
},
abortOnLimit: true
})
);
nunjucks.configure('views', {
autoescape: true,
express: app
});
app.disable('etag');
app.set('views', './views');
app.use('/static', express.static(path.resolve('static')));
app.use(router.default(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'));
})();
import path from 'path';
import * as fs from 'fs';
import axios from 'axios';
import { Router } from 'express';
import { sign } from '../helpers/JWTHelper.js';
import { AuthMiddleware } from '../middleware/AuthMiddleware.js';
import { AdminMiddleware } from '../middleware/AdminMiddleware.js';
import { LocalMiddleware } from '../middleware/LocalMiddleware.js';
import { execSync } from 'child_process';
let db;
const router = Router();
const response = data => ({ message: data });
router.get('/', (req, res) => {
return res.render('index.html');
});
router.get('/register', (req, res) => {
return res.render('register.html');
});
router.get('/login', (req, res) => {
return res.render('login.html');
});
router.post('/api/register', async (req, res) => {
const { username, password } = req.body;
if (username && password) {
return db.getUser(username)
.then(user => {
if (user) return res.status(401).send(response('This username is already registered!'));
return db.registerUser(username, password)
.then(() => res.send(response('Account registered successfully!')))
})
.catch(() => res.status(500).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 = 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('/dashboard', AuthMiddleware, async (req, res) => {
if (req.user.username === 'admin') return res.redirect('/admin');
return db.getUser(req.user.username)
.then(user => {
if (!user) return res.redirect('/login');
return db.getFormData(user.username)
.then(enrollment => {
res.render('dashboard.html', { user, enrollment });
});
})
.catch(e => {
return res.redirect('/login');
})
});
router.post('/api/enroll', AuthMiddleware, async (req, res) => {
return db.getUser(req.user.username)
.then(user => {
if (!user) return res.redirect('/login');
const {full_name, phone, birth_date, gender, biography} = req.body;
return db.updateEnrollment(
full_name,
phone,
birth_date,
gender,
biography ,user.username
)
.then(() => res.send(response('Your information is saved successfully!')))
.catch((e) => res.status(401).send(response('Something went wrong!')));
})
.catch(e => {
return res.redirect('/login');
})
});
router.post('/api/upload', AuthMiddleware, async (req, res) => {
return db.getUser(req.user.username)
.then(async user => {
if (!user) return res.redirect('/login');
if (!req.files || !req.files.resumeFile) {
return res.status(400).send(response('No files were uploaded.'));
}
let enrollment = await db.getFormData(user.username);
let resumeFile = req.files.resumeFile;
let uploadFile = `${resumeFile.md5}.docx`;
resumeFile.mv(path.join('/app/uploads', uploadFile), (err) => {
if (err) return res.status(500).send(response('Something went wrong!'));
});
if(enrollment.resume_file && enrollment.resume_file !== uploadFile){
try {
fs.unlinkSync(path.join('/app/uploads', enrollment.resume_file));
}
catch (e) { console.log(e) }
}
return db.setResume(uploadFile,user.username)
.then(() =>{
res.send({
'message': 'Resume file uploaded successfully!',
'filename': uploadFile
});
})
.catch(() => res.status(500).send(response('Something went wrong!')));
})
.catch(e => {
return res.redirect('/login');
})
});
router.get('/download', AuthMiddleware, async (req, res) => {
return db.getUser(req.user.username)
.then(user => {
if (!user) return res.redirect('/login');
let { resume } = req.query;
resume = resume.replaceAll('../', '');
return res.download(path.join('/app/uploads', resume));
})
.catch(e => {
return res.redirect('/login');
})
});
router.get('/admin', AdminMiddleware, async (req, res) => {
return res.render('admin.html', { user: req.user });
});
router.get('/sms-settings', AdminMiddleware, async (req, res) => {
return res.render('sms-settings.html', { user: req.user });
});
router.post('/api/sms/save', AdminMiddleware, async (req, res) => {
const { verb, url, params, headers, resp_ok, resp_bad } = req.body;
if (!(verb && url && params && headers && resp_ok && resp_bad)) {
return res.status(500).send(response('missing required parameters'));
}
return db.saveSMSConfig(verb, url, params, headers, resp_ok, resp_bad)
.then(() => {
return res.send(response('SMS settings saved successfully!'));
})
.catch((e) => {
return res.status(500).send(response('Something went wrong!'));
});
});
router.post('/api/sms/test', AdminMiddleware, async (req, res) => {
const { verb, url, params, headers, resp_ok, resp_bad } = req.body;
if (!(verb && url && params && headers && resp_ok && resp_bad)) {
return res.status(500).send(response('missing required parameters'));
}
let parsedHeaders = {};
try {
let headersArray = headers.split('\n');
for(let header of headersArray) {
if(header.includes(':')) {
let hkey = header.split(':')[0].trim()
let hval = header.split(':')[1].trim()
parsedHeaders[hkey] = hval;
}
}
}
catch (e) { console.log(e) }
let options = {
method: verb.toLowerCase(),
url: url,
timeout: 5000,
headers: parsedHeaders
};
if (verb === 'POST') options.data = params;
axios(options)
.then(response => {
if (typeof(response.data) == 'object') {
response.data = JSON.stringify(response.data);
}
return res.json({status: 'success', result: response.data})
})
.catch(e => {
if (e.response) {
if (typeof(e.response.data) == 'object') {
e.response.data = JSON.stringify(e.response.data);
}
return res.json({status: 'fail', result: e.response.data})
}
else {
return res.json({status: 'fail', result: 'Address is unreachable'});
}
})
});
router.get('/sql-prompt', AdminMiddleware, async (req, res) => {
return res.render('sql-prompt.html', { user: req.user });
});
router.post('/debug/sql/exec', LocalMiddleware, AdminMiddleware, async (req, res) => {
const { sql, password } = req.body;
if (sql && password === process.env.DEBUG_PASS) {
try {
let safeSql = String(sql).replaceAll(/"/ig, "'");
let cmdStr = `sqlite3 -csv admin.db "${safeSql}"`;
const cmdExec = execSync(cmdStr);
return res.json({sql, output: cmdExec.toString()});
}
catch (e) {
let output = e.toString();
if (e.stderr) output = e.stderr.toString();
return res.json({sql, output});
}
}
return res.status(500).send(response('Invalid debug password supplied!'));
});
router.get('/logout', (req, res) => {
res.clearCookie('session');
return res.redirect('/');
});
export default database => {
db = database;
return router;
};
import { decode } from "../helpers/JWTHelper.js";
const AuthMiddleware = async (req, res, next) => {
try{
if (req.cookies.session === undefined) {
if(!req.is('application/json')) return res.redirect('/');
return res.status(401).json({ status: 'unauthorized', message: 'Authentication required!' });
}
return decode(req.cookies.session)
.then(user => {
req.user = user;
return next();
})
.catch((e) => {
console.log(e);
res.redirect('/logout');
});
} catch(e) {
console.log(e);
return res.redirect('/logout');
}
}
export { AuthMiddleware };
import { Database as sqlite } from 'sqlite-async';
import crypto from "crypto";
const md5 = data => crypto.createHash('md5').update(data).digest("hex");
export 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() {
let password = md5(crypto.randomBytes(16).toString('hex'));
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,
verified BOOLEAN NOT NULL DEFAULT false
);
INSERT INTO users (username, password, verified) VALUES ('admin', '${password}', true);
DROP TABLE IF EXISTS enrollments;
CREATE TABLE IF NOT EXISTS enrollments (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
username VARCHAR(255) NOT NULL UNIQUE,
full_name VARCHAR(255) NULL,
phone VARCHAR(255) NULL,
birth_date VARCHAR(255) NULL,
gender VARCHAR(255) NULL,
biography TEXT NULL,
resume_file VARCHAR(256) NULL
);
DROP TABLE IF EXISTS settings;
CREATE TABLE settings(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
config_key VARCHAR(2) NOT NULL,
config_val NUMERIC(9,6) NOT NULL
);
INSERT INTO settings (config_key, config_val)
VALUES
('sms_verb', 'POST'),
('sms_url', 'https://platform.clickatell.com/messages'),
('sms_params', '{"apiKey" : "xxxx", "toNumber": "recipient", "text": "message"}'),
('sms_headers', 'Content-Type: application/json\nAuthorization: Basic YWRtaW46YWRtaW4='),
('sms_resp_ok', '<status>ok</status>'),
('sms_resp_bad', '<status>error</status>');
`);
}
async registerUser(username, password) {
return new Promise(async (resolve, reject) => {
try {
let register_sql = await this.db.prepare('INSERT INTO users (username, password) VALUES ( ?, ?)');
let enrollment_sql = await this.db.prepare('INSERT INTO enrollments (username) VALUES (?)');
await register_sql.run(username, md5(password));
await enrollment_sql.run(username);
resolve();
} catch(e) {
reject(e);
}
});
}
async loginUser(username, password) {
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(username, md5(password)));
} catch(e) {
reject(e);
}
});
}
async getUser(username) {
return new Promise(async (resolve, reject) => {
try {
let stmt = await this.db.prepare('SELECT * FROM users WHERE username = ?');
resolve(await stmt.get(username));
} catch(e) {
reject(e);
}
});
}
async checkUser(username) {
return new Promise(async (resolve, reject) => {
try {
let stmt = await this.db.prepare('SELECT username FROM users WHERE username = ?');
let row = await stmt.get(username);
resolve(row !== undefined);
} catch(e) {
reject(e);
}
});
}
async getFormData(username) {
return new Promise(async (resolve, reject) => {
try {
let stmt = await this.db.prepare('SELECT * FROM enrollments WHERE username = ?');
resolve(await stmt.get(username));
} catch(e) {
reject(e);
}
});
}
async updateEnrollment(full_name, phone, birth_date, gender, biography, username) {
return new Promise(async (resolve, reject) => {
try {
let stmt = await this.db.prepare(`
UPDATE enrollments
SET
full_name = ?,
phone = ?,
birth_date = ?,
gender = ?,
biography = ?
WHERE username = ?
`);
resolve((await stmt.run(full_name, phone, birth_date, gender, biography, username)));
} catch(e) {
reject(e);
}
});
}
async setResume(filename, username) {
return new Promise(async (resolve, reject) => {
try {
let stmt = await this.db.prepare('UPDATE enrollments SET resume_file = ? WHERE username = ?');
resolve((await stmt.run(filename, username)));
} catch(e) {
reject(e);
}
});
}
async saveSMSConfig(verb, url, params, headers, resp_ok, resp_bad) {
return new Promise(async (resolve, reject) => {
const smsConfig = {
'sms_verb': verb,
'sms_url': url,
'sms_params': params,
'sms_headers': headers,
'sms_resp_ok': resp_ok,
'sms_resp_bad': resp_bad
}
for(const [col_name, col_data] of Object.entries(smsConfig)) {
try {
let stmt = await this.db.prepare('UPDATE settings SET config_val = ? WHERE config_key = ?');
await stmt.run(col_data, col_name)
} catch(e) {
reject(e);
}
}
resolve(true);
});
}
}
DEBUG_PASS=CzliwZJkV60hpPJ
const LocalMiddleware = async (req, res, next) => {
if (req.ip == '127.0.0.1' && req.headers.host == '127.0.0.1:1337') {
return next();
}
return res.status(401).json({ message: 'Blocked: This endpoint is whitelisted to localhost only.' });
}
export { LocalMiddleware };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment