Ataques web comunes
Tres familias de fallos aparecen una y otra vez en aplicaciones reales: dejar que el usuario elija qué fichero se lee, dejar que suba lo que quiera, y dejar que sus datos acaben dentro de un comando del sistema. Este módulo las trabaja una por una.
- Explotar la inclusión de ficheros locales (LFI) y el recorrido de rutas, entendiendo por qué
../funciona. - Conocer la frontera entre LFI y RFI y cuándo una lectura se convierte en ejecución.
- Romper validaciones de subida de ficheros en todas sus variantes habituales.
- Explotar la inyección de comandos, dominando los operadores de shell y la inyección a ciegas.
- Interiorizar que un mismo dato de entrada puede alcanzar varias superficies distintas.
1. Inclusión de fichero local (LFI) y recorrido de rutas
Ocurre cuando una aplicación construye una ruta de fichero a partir de un dato del usuario sin comprobar que el resultado se queda dentro de la carpeta prevista. Para entenderlo hay que entender ../: en una ruta, .. significa "el directorio padre". Si la aplicación hace abrir("plantillas/" + idioma) y tú pones como idioma el valor ../../../../etc/passwd, la ruta resultante sube cuatro niveles y sale por completo de plantillas/ hasta la raíz del sistema:
$ curl 'http://objetivo/pagina?fichero=es.txt' # uso normal
$ curl 'http://objetivo/pagina?fichero=../../../../etc/passwd' # traversal
Los ../ de más no molestan: en la raíz, subir un nivel te deja en la raíz. Por eso se ponen de sobra. No hace falta llegar a /etc/passwd: a menudo lo valioso es salir de la carpeta prevista para leer el código fuente de la propia aplicación (y con él, sus credenciales y otras vulnerabilidades) o sus ficheros de configuración.
2. Subida de ficheros: romper cada validación
El objetivo de atacar una subida es colocar en el servidor un fichero que luego se pueda ejecutar (una webshell). Para eso hay que saltarse la validación, y casi todas las validaciones débiles caen así:
| Comprobación débil | Por qué falla / cómo se evade |
|---|---|
El nombre "contiene" .jpg | shell.jpg.php también lo contiene: la comprobación no mira el final |
Confiar en el Content-Type de la petición | Lo fija el cliente; se cambia con un proxy a image/png |
| Comprobar solo la extensión declarada | Extensiones alternativas (.phtml, .php5) que el servidor sigue ejecutando |
| Comprobar los "números mágicos" (cabecera del fichero) | Anteponer los bytes mágicos de una imagen real seguidos del código |
Una validación correcta hace lo contrario de cada fila: comprueba la extensión al final contra una lista blanca, inspecciona el contenido real, renombra el fichero con un nombre generado por el servidor, y lo guarda en un directorio sin permiso de ejecución. Que se salte la validación no basta si además no se puede ejecutar lo subido.
$ curl -F 'fichero=@shell.jpg.php' http://objetivo/subir # doble extensión
$ curl -F 'fichero=@shell.php;type=image/png' http://objetivo/subir # Content-Type falso
3. Inyección de comandos
Sucede cuando una aplicación pasa un dato del usuario a un intérprete de comandos del sistema en lugar de invocar el programa directamente. Si el código hace ejecutar("ping -c1 " + host), el shell que ejecuta esa cadena también interpreta sus metacaracteres, y con ellos se encadenan órdenes propias:
| Operador | Efecto | Cuándo usarlo |
|---|---|---|
; | Ejecuta el segundo comando siempre | El caso general |
&& | Segundo comando solo si el primero tuvo éxito | Cuando controlas que el primero funcione |
|| | Segundo comando solo si el primero falla | Cuando fuerzas un fallo en el primero |
| | La salida del primero entra en el segundo | Encadenar filtros |
$(...) / backticks | Sustitución: ejecuta lo de dentro y lo inserta | Inyectar dentro de un argumento |
$ curl -d 'host=127.0.0.1; whoami' http://objetivo/diagnosticoInyección de comandos a ciegas: muchas veces la salida del comando no se muestra en la respuesta. Igual que en la SQLi ciega, hay dos canales para confirmar la ejecución: el tiempo (; sleep 5 y mides si la respuesta tarda) y la red (haces que el servidor te haga un ping o una petición a una máquina que tú controlas). Si tarda 5 segundos, o si recibes ese ping, el comando se ejecutó aunque no veas su salida.
La forma segura de invocar un programa externo pasa el binario y sus argumentos por separado (una lista, no una cadena), de modo que el intérprete de comandos no llega a entrar en juego y los metacaracteres se tratan como texto literal.
4. Un mismo dato, muchas superficies
La idea que une las tres familias de este módulo: un mismo parámetro de entrada puede acabar en sitios muy distintos —el sistema de ficheros (LFI), un comando del sistema (inyección de comandos), una consulta (SQLi, módulo 7), el HTML de otra víctima (XSS, módulo 8)—. La vulnerabilidad no está en el dato, sino en a dónde llega sin ser tratado. Por eso, ante cada entrada, la pregunta correcta no es "¿esto parece peligroso?", sino "¿a dónde va a parar este valor y qué lo interpreta allí?".
- OWASP: guías de Path Traversal, Unrestricted File Upload y Command Injection.
- PortSwigger Web Security Academy: laboratorios de file path traversal, file upload y OS command injection (incluida la variante a ciegas).
- Las guías de FFuF y RevShell de Ghost Academy (ver "Herramientas relacionadas") para descubrir rutas y para el paso de leer/ejecutar a una shell.
Guías y laboratorios ya publicados en Ghost Academy sobre las herramientas de este módulo:
Descomprime, lee el LEEME.md y lanza startlab.sh. Encontrarás 8 flags GO{...} repartidas entre inclusión de ficheros, subida de ficheros e inyección de comandos. Verifícalas en http://localhost:8383/verificador.
Ninguna vulnerabilidad de este laboratorio permite salir del contenedor: practica con confianza.
¿Atascado o ya terminado? La solución paso a paso revela todas las flags. Úsala cuando lo hayas intentado.
Autoevaluación
- ¿Por qué funcionan los
../de un recorrido de rutas, y por qué se ponen más de los necesarios? - ¿En qué se diferencian LFI y RFI, y cómo puede una LFI acabar ejecutando código?
- ¿Por qué comprobar que un nombre "contiene" una extensión no es lo mismo que comprobar que "termina" en ella?
- Además de saltarse la validación, ¿qué otra condición necesita cumplirse para que una webshell subida sea explotable?
- Explica la diferencia entre
;,&&y||en una inyección de comandos. - Si una inyección de comandos no muestra la salida, ¿cómo confirmas que se ejecutó?
- ¿Cómo evitarías por completo la inyección de comandos al invocar un programa externo con un dato del usuario?