[CTF Writeup / Web] DiceCTF 2024 - funnylogin

 

can you login as admin?

NOTE: no bruteforcing is required for this challenge! please do not bruteforce the challenge.



const app = express();

const db = require('better-sqlite3')('db.sqlite3');
db.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
    id INTEGER PRIMARY KEY,
    username TEXT,
    password TEXT
);`);

const FLAG = process.env.FLAG || "dice{test_flag}";
const PORT = process.env.PORT || 3000;

const users = [...Array(100_000)].map(() => ({
    user: `user-${crypto.randomUUID()}`,
    pass: crypto.randomBytes(8).toString("hex")
}));
db.exec(`INSERT INTO users (id, username, password) VALUES ${
    users.map((u,i) => `(${i}, '${u.user}', '${u.pass}')`).join(", ")
}`);

const isAdmin = {};
const newAdmin = users[Math.floor(Math.random() * users.length)];
isAdmin[newAdmin.user] = true;


문제에서 제공된 코드를 보면 특이한 방식으로 admin을 구성한다.


1. 100,000개의 user-X를 만든다.

2. 랜덤하게 1명의 user를 뽑는다.

3. isAdmin 리스트에 뽑힌 user를 추가한다.


위 과정을 거쳐 최종적으로 isAdmin 리스트에 딱 1명의 user가 존재하게 된다.



app.post("/api/login", (req, res) => {
    const { user, pass } = req.body;

    const query = `SELECT id FROM users WHERE username = '${user}' AND password = '${pass}';`;
    try {
        const id = db.prepare(query).get()?.id;
        if (!id) {
            return res.redirect("/?message=Incorrect username or password");
        }

        if (users[id] && isAdmin[user]) {
            return res.redirect("/?flag=" + encodeURIComponent(FLAG));
        }
        return res.redirect("/?message=This system is currently only available to admins...");
    }
    catch {
        return res.redirect("/?message=Nice try...");
    }
});


/api/login 로직을 살펴보면 SQL injection에 취약함을 알 수 있다.

db.prepare라는 sql injeciton으로부터 보호할 수 있는 메소드를 사용하고는 있지만, 이미 사용자의 input을 query 문자열에 대입하여 완성된 query를 이용해 prepare()를 사용했기 때문에 취약하다.


그러나 누가 admin인지 알 수 없으므로, 100,000번의 시도를 해야 하는 것 처럼 보인다.



  if (users[id] && isAdmin[user]) {
   return res.redirect("/?flag=" + encodeURIComponent(FLAG));
  }


`id`는 db의 result다. `user`는 http body로 넘긴 client request다.

users[id]는 존재해야하며, isAdmin[user]도 존재해야 한다.


id : __proto__

pw : ' union select 1 --


위 페이로드를 통해 공격하면 조건을 통과할 수 있다.

id는 `isAdmin[user]`에 사용되기 때문에 injection payload를 넣으면 안된다.

pw를 통해 sql injeciton 공격을 진행하고, id에는 __proto__를 넣어서 isAdmin[user]가 참이 되도록 만들어주면 로그인에 성공한다.







댓글