////
Search
🚩

Missing Flavortext [111 points]

목차

문제 유형

SQLite Injection, Option 문제

문제 정보 확인

Flavortext를 찾아 달라는 문제이다.
index.js 파일의 내용은 아래와 같다.
const crypto = require('crypto'); const db = require('better-sqlite3')('db.sqlite3') // remake the `users` table db.exec(`DROP TABLE IF EXISTS users;`); db.exec(`CREATE TABLE users( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT );`); // add an admin user with a random password db.exec(`INSERT INTO users (username, password) VALUES ( 'admin', '${crypto.randomBytes(16).toString('hex')}' )`); const express = require('express'); const bodyParser = require('body-parser'); const app = express(); // parse json and serve static files app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static('static')); // login route app.post('/login', (req, res) => { if (!req.body.username || !req.body.password) { return res.redirect('/'); } if ([req.body.username, req.body.password].some(v => v.includes('\''))) { return res.redirect('/'); } // see if user is in database const query = `SELECT id FROM users WHERE username = '${req.body.username}' AND password = '${req.body.password}' `; let id; try { id = db.prepare(query).get()?.id } catch { return res.redirect('/'); } // correct login if (id) return res.sendFile('flag.html', { root: __dirname }); // incorrect login return res.redirect('/'); }); app.listen(3000);
JavaScript
index.js 에서 얻을 수 있는 정보
const db = require('better-sqlite3')('db.sqlite3')
해당 문제의 DB는 SQLite를 사용한다.
if ([req.body.username, req.body.password].some(v => v.includes('\'')))
입력한 req.body.usernamereq.body.password 에서 ' 문자가 존재하는지 탐지
app.use(bodyParser.urlencoded({ extended: true }));
bodyParser.urlencoded 의 옵션 중 extended 옵션이 true로 설정되어 있다. 이는 서버에 오브젝트 형식의 값을 전달할 수 있다는 의미이다.

1. OBJECT 형태 값 전달 / 필터링 BYPASS

문제 페이지로 접속하면 로그인을 할 수 있는 페이지를 확인할 수 있다.
그래서 필자는 이 문제가 SQL INJECTION 문제라고 생각하고 많은 삽질을 하였다. 결론적으로 Injection 문제는 맞지만, Injection 구문을 성공시키기 위해서는 한가지 방법을 더 추가해야 했다.
그게 바로 bodyParser.urlencoded({ extended: true }) 를 이용하는 방법이다.
bodyParser.urlencoded({ extended: true }) 를 사용하기 때문에 아래와 같이 object 값을 전달하여도 서버에서 값을 사용할 수 있다.
https://url.kr?test[]=123
Plain Text
위 내용을 webhook을 사용하여 테스트 해보았다.
?test[]=123 으로 요청할 경우 ["123"] 으로 값이 넘어오는것을 확인할 수 있다.
이렇게 넘어온 오브젝트 형 값은 includes() 함수의 필터링도 걸리지 않는다.

2. SQLite Injection

위 과정을 거쳐 필터링 우회까지 성공한 후, 쿼리문을 작성할 때도 큰 문제없이 쿼리문이 작성된다.
아래 index.js의 쿼리문 작성 부분과 동일하게 값을 설정해 보았고, 오브젝트 형 데이터를 사용해도 쿼리문이 잘 작성되는 것을 확인할 수 있었다. 아마 여러 값이 들어간 오브젝트가 아니라서 그런것 같다.
const query = `SELECT id FROM users WHERE username = '${req.body.username}' AND password = '${req.body.password}' `;
JavaScript
아래처럼 전송하면 위 예시처럼 문제의 SQLite 구문도 동일하게 우회가 가능할 것이다.
?username=admin&password[]='+or+1%3d1--
Plain Text
우회 성공 시, FLAG가 작성된 페이지를 보여준다.
FLAG
dice{sq1i_d03sn7_3v3n_3x1s7_4nym0r3}