Panorama#
Los tres capítulos web anteriores atacan el web clásico que documentó The Web Application Hacker’s Handbook en 2011: una aplicación monolítica, un navegador, un servidor, y una same-origin policy que aísla un sitio de otro. Ese modelo tenía fronteras de confianza nítidas —el login separa anónimo de autenticado, el origen separa un dominio de otro, el intérprete separa dato de código—, y las clases de 3.2 a 3.4 son maneras de romperlas. Pero el web dejó de ser monolítico. Las aplicaciones modernas delegan la identidad a un tercero, descentralizan el estado en tokens autocontenidos, hablan cross-origin por diseño, empujan lógica y datos al cliente, y procesan input rico a través de motores muy potentes.
Bajo la variedad de clases de este capítulo hay una sola idea: cada comodidad del web moderno reubica una frontera de confianza, y la vulnerabilidad vive en la frontera que se dejó de validar. Donde antes la aplicación validaba, ahora hay un límite nuevo —y a menudo nadie lo custodia—. Las clases se agrupan en dos familias que son los sucesores directos de los capítulos previos:
- Identidad y confianza delegadas (el sucesor de 3.3 y 3.4). La app
ya no autentica ni guarda la sesión por sí sola: delega el login a Google o Facebook (OAuth), lleva el
estado en un token autocontenido que el servidor no recuerda (JWT), y abre el origen para conversar con
otros dominios (CORS, postMessage, WebSockets). Cada delegación crea una frontera nueva —el
redirect_uri, la firma del token, elOrigin— que es el punto exacto del ataque. - Input rico y motores potentes (el sucesor de 3.2). La misma tesis de la inyección —romper la frontera entre dato y código— pero el intérprete ya no es una base de datos SQL: es un motor de plantillas (SSTI), un deserializador (object injection), un parser de archivos (file upload) o el propio stack HTTP del servidor convertido en cliente (SSRF). El resultado, casi siempre, es RCE directo en el servidor.
flowchart TD MOD["Web moderno:\ncada comodidad mueve\nuna frontera de confianza"] --> F1["Familia A —\nidentidad/confianza delegada"] MOD --> F2["Familia B —\ninput rico a motores potentes"] F1 --> OAUTH["OAuth: frontera = redirect_uri"] F1 --> JWT["JWT: frontera = la firma"] F1 --> CORS["CORS / postMessage / WS:\nfrontera = el Origin"] F2 --> SSTI["SSTI: motor de plantillas"] F2 --> DESER["Deserialización: unserialize()"] F2 --> UP["File upload: parser de archivos"] F2 --> SSRF["SSRF: el server como cliente HTTP"] OAUTH --> ATO["Account takeover\n/ robo de token"] JWT --> ATO CORS --> ATO SSTI --> RCE["RCE server-side"] DESER --> RCE UP --> RCE SSRF --> RCE
El capítulo recorre las dos familias y cierra con la lente púrpura que las une. Todo se apoya en el mapa de
superficie de 3.1 · Metodología: cada botón de “iniciar sesión con Google”, cada token en
Authorization: Bearer, cada campo que acepta un URL o un archivo, y cada postMessage en el JavaScript del cliente
es una frontera a probar.
Familia A · Identidad delegada: OAuth 2.0#
OAuth 2.0 (RFC 6749) resuelve un problema real del modelo clásico: para que una app de un tercero acceda a los datos
de un usuario, antes había que darle la contraseña —que podía guardarla en claro, obtenía acceso total y no
selectivo, y no había forma de revocarle acceso a uno sin revocárselo a todos—. OAuth lo reemplaza con cuatro roles:
el resource owner (el usuario), el client (la app del tercero, con un client_id público y un client_secret
privado), el resource server (donde viven los datos, protegidos por un access token) y el authorization
server (que emite el token). El usuario concede a la app un acceso definido por scope, sin entregar su
contraseña.
La frontera de confianza es el redirect_uri: la URI pre-configurada a la que el authorization server redirige
al terminar el flow, entregando el token o el código. Robar esa redirección es robar el token. Y lo que la hace tan
potente es una comodidad del protocolo: una vez que el usuario concedió acceso a una app, el authorization server
no vuelve a mostrar el prompt de consentimiento en reintentos del client (salvo que cambien los scopes). Así,
si se logra que el servidor acepte un redirect_uri controlado por el atacante para un client popular con muchos
scopes ya concedidos, el token llega solo, sin interacción de la víctima.
Hay dos grants. El authorization code (response_type=code) devuelve un code que se cambia server-side por
el token en un POST que incluye el client_secret (token = code + client_id + client_secret + redirect_uri). El
implicit (response_type=token) devuelve el token directo en el fragmento de la URL
(redirect_uri#access_token=...), sin secret —lo que lo hace más frágil: robar la redirección entrega el token sin
que falte ningún secreto—.
Las derrotas del redirect_uri#
- Open redirect por grant link malformado. Un grant link con valores basura en
scopeoclient_idhace que algunos authorization servers, en vez de mostrar el error en su propio dominio, redirijan alredirect_uricon parámetros de error. Es un open redirect encubierto que se dispara desde un dominio confiable (api.trusted.com) hacia el del atacante —LinkedIn era vulnerable—. - Hijack del
redirect_uri. Si la app permite solohttp://example.com/token/callback, hay trucos para colar uno propio: directory traversal (.../callback/../../our/path, con encoding y CRLF para evadir el chequeo), naked domain (si el permitido es el dominio pelado, algunas implementaciones aceptan subdominios →controlledsub.example.com), y TLD suffix confusion (dejar el dominio intacto y agregar un sufijo válido:example.com→example.com.mx; hallado en Instagram y Slack). - Covert Redirect. Si el dominio permitido tiene un open redirect propio (
/exit/redirect.php?u=...), se usa comoredirect_uri: pasa la validación —es el dominio permitido— pero reenvía el token al atacante por un 302. Especialmente potente en implicit grant. - Force-install por clickjacking. Como conceder es solo clickear “allow”, si el grant prompt no trae
X-Frame-Options, el clickjacking de 3.3 fuerza ese clic y concede scopes arbitrarios sin que el usuario lo note.
redirect_uri controlado—.Familia A · Estado autocontenido: JWT#
El JWT (JSON Web Token) lleva la sesión al extremo del token con significado de 3.4: en vez
de un identificador opaco que apunta a un estado server-side, el token contiene el estado. Son tres partes
base64url separadas por puntos: header.payload.signature. El header declara el alg (algoritmo de firma), el
payload lleva los claims que la app usa para autorizar (username, isAdmin, exp), y la firma se calcula
hasheando header.payload con una clave. Como el payload es solo base64url —codificado, no cifrado—,
cualquiera lo lee y lo modifica; cambiar "isAdmin":false a true es trivial. La firma es la única barrera, y
todos los ataques apuntan a ella:
- Firma no verificada. La app usa el
decode()/unverifiedde la librería en vez deverify(): se modifican los claims sin re-firmar y el token pasa. alg:none. La especificación admite tokens unsecured con"alg":"none"y firma vacía (el token termina en un punto). Si la app respeta elalgdel header sin whitelist, acepta un token sin firma con"isAdmin":true. El filtro que rechazanonese evade con variaciones de caso (NoNe,nOnE).- Secreto HMAC débil. Si usa HS256 con un secreto por defecto o de tutorial (
secret,changeme), se crackea offline: el atacante tieneheader.payloady la firma, y prueba candidatos hasta que el HMAC coincida (hashcat -m 16500,jwt_tool -C). Con el secreto, firma cualquier token. - Inyección de clave en el header (
jwk/jku/kid). Eljwkembebe la clave pública propia en el header → el atacante firma con su privada y el server verifica con la embebida. Eljkuapunta a un JWK Set en un dominio del atacante (whitelist de host evadible con discrepancias de parsing:https://trusted.com@attacker.com). Elkidselecciona la clave; si construye una ruta de archivo, un path traversal a/dev/null(contenido vacío)- firmar con secreto vacío coincide; si alimenta una query, se inyecta SQL.
- Algorithm confusion RS256→HS256. El más elegante: en RS256 el server firma con su clave privada y verifica con la pública (que es pública por diseño). Si el server acepta también HS256 y usa “la clave” para verificar sin distinguir el algoritmo, el atacante firma el token con HMAC-SHA256 usando la clave pública RSA como secreto —el server la toma como secreto HMAC y coincide—.
# alg:none con bypass de filtro por caso
{"alg":"nOnE","typ":"JWT"} # token: header.payload. (firma vacía)
# crackear secreto HMAC débil y re-firmar
hashcat -a 0 -m 16500 <jwt> jwt.secrets.list
python3 jwt_tool.py <jwt> -S hs256 -p 'secret123' -T
# algorithm confusion: la clave pública como secreto HMAC
python3 jwt_tool.py <jwt> -X k -pk public.pem -T
A esto se suman claims sin exp (replay indefinido de un token robado), sin aud (reuso cross-service), y
datos sensibles en el payload (legibles con un simple decode de base64). La defensa transversal: fijar y
validar el algoritmo esperado server-side (mata alg:none y algorithm confusion), secretos aleatorios ≥256
bits, no procesar jwk/jku/kid sin whitelist estricta, exp corto + aud, y nunca secretos en el payload.
Familia A · El origen abierto por diseño: CORS, postMessage, WebSockets, storage#
La same-origin policy que 3.3 trabajaba para eludir, el web moderno la relaja a propósito para que dominios distintos colaboren. Cada mecanismo de relajación traslada la frontera a una validación de origen que a menudo se implementa mal.
- CORS. Permite requests cross-domain vía
XMLHttpRequest: el navegador mandaOriginy el servidor respondeAccess-Control-Allow-Origin(ACAO) indicando quién puede leer la respuesta, con un preflightOPTIONSpara los requests no simples yAccess-Control-Allow-Credentialspara las credenciales. Las misconfigs peligrosas:ACAO: *(todos permitidos), o reflejar elOriginrecibido sin validar combinado conAllow-Credentials: true—eso reproduce el desastre delcrossdomain.xmlcon comodín que se anticipó en 3.3: cualquier sitio del atacante lee datos autenticados de la víctima cross-domain—. - Web Messaging (
postMessage). Comunicación cross-document. El receptor debe verificarevent.origincon una whitelist exacta (event.origin === 'chat.example.com'); el error clásico ese.origin.indexOf(".owasp.org"), quewww.owasp.org.attacker.comevade por substring. Yevent.dataes input no confiable aun de un dominio confiable: meterlo eninnerHTMLproduce DOM-based XSS. El emisor debe fijar el target origin explícito, nunca*(con*, una redirección filtra la data a un host desconocido). - WebSockets. Un canal full-duplex (
ws:///wss://) que hace un handshake de upgrade sobre HTTP. Es responsabilidad del servidor validar elOrigindel handshake; si no lo hace, acepta conexiones de cualquier origen → Cross-Site WebSocket Hijacking (CSWSH), un CSRF sobre el canal. Los WebSockets no traen autenticación ni autorización propias: valen los tests normales de sesión, control de acceso e IDOR de 3.4. - Local Storage.
localStorage/sessionStorageguardan pares clave/valor por dominio (~5 MB) que nunca viajan al servidor pero son legibles desde JavaScript: un solo XSS los vacía enteros. Por eso guardar session IDs o datos sensibles ahí es un error —una cookieHttpOnlylos protege de JS;localStorageno—.
A este cluster se suman los vectores DOM puros que completan la superficie client-side moderna: DOM clobbering
(HTML “seguro” como <img name="getElementById"> sobrescribe métodos del DOM y paraliza el JS de la página —el caso
GitHub Gist—), RPO (Relative Path Overwrite: abusar cómo el navegador resuelve la ruta relativa de un CSS para
que la propia página se importe como stylesheet → CSS injection y, en IE quirks mode, XSS por expression()),
client-side resource manipulation (un location.hash que llega al src de un <script> → XSS), y CSS
injection (con selectores de atributo se lee el DOM carácter a carácter, exfiltrando un token CSRF). Todos
convergen en el mismo sink del XSS del lado cliente: innerHTML en vez de textContent.
Familia B · Input rico y motores potentes → RCE server-side#
Esta familia es la tesis de la inyección de 3.2 —romper la frontera entre dato y código— con intérpretes que 2011 no atacaba. El desenlace ya no es leer una base de datos: es ejecución de código en el servidor.
SSTI (Server-Side Template Injection)#
Ocurre cuando input del usuario se embebe en una plantilla de forma insegura —concatenado dentro del string
de la plantilla en vez de pasado como dato de contexto— y el motor lo evalúa como código. La causa raíz es tratar la
plantilla como string: render_template_string("Hola " + name) en lugar de render_template("hola.html", name=name).
A diferencia del XSS, que ejecuta en el navegador, el SSTI ejecuta en el servidor, con acceso al filesystem, las
variables de entorno y, vía los globales del lenguaje, a comandos. La metodología tiene tres pasos:
flowchart TD
IN["Inyectar {{7*7}} / ${7*7} /\n<%= 7*7 %> / #{7*7}"] --> EVAL{"¿Devuelve 49?"}
EVAL -->|"no"| SAFE["Sin SSTI (o dato inerte)"]
EVAL -->|"sí"| FP{"Fingerprint del motor"}
FP -->|"{{7*'7'}} → 7777777"| JINJA["Jinja2 (Python)"]
FP -->|"{{7*'7'}} → 49"| TWIG["Twig (PHP)"]
FP -->|"${7*7} evalúa"| FREE["Freemarker / Mako (Java/Python)"]
FP -->|"<%= 7*7 %>"| ERB["ERB (Ruby)"]
JINJA --> RCE["Escapar a los globales del lenguaje\n→ os/subprocess/Runtime → RCE"]
TWIG --> RCE
FREE --> RCE
ERB --> RCEDetectado el motor, la explotación escapa del sandbox del motor a las primitivas de ejecución del lenguaje host. En
Jinja2, se navega la jerarquía de clases de Python hasta os:
{{config.__class__.__init__.__globals__['os'].popen('id').read()}} # Jinja2 (Python/Flask)
{{cycler.__init__.__globals__.os.popen('id').read()}} # variante que no depende de config
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id")}} # Twig (PHP)
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")} # Freemarker (Java)
<%= system('id') %> # ERB (Ruby)
Aun donde el sandbox bloquea los comandos, el SSTI vuelca secretos: en Jinja2, {{config}} expone toda la
configuración de Flask (incluida la SECRET_KEY —que reabre la forja de sesión de 3.4—) y las
variables de entorno filtran API keys y credenciales de base de datos. tplmap es el “sqlmap del SSTI”
(detección, fingerprint y --os-shell).
Deserialización: object injection y POP chains#
El serialize()/unserialize() de PHP convierte objetos a un formato de texto legible
(O:5:"Packt":1:{s:4:"name";...}) y los reinstancia. El peligro: pasar input del usuario a unserialize()
instancia objetos arbitrarios con valores controlados. Solo instanciar no daña; el daño viene de encadenar con las
magic functions —métodos que se ejecutan solos: __construct, __destruct, __wakeup—. Si en el código existe
una clase con una magic function peligrosa (un gadget), el atacante la instancia vía unserialize() y esa función
se dispara. El ejemplo canónico: una clase LogWriter cuyo __destruct() hace file_put_contents($this->logfile, $this->logdata)
—inyectando el objeto serializado con logfile = una ruta en el webroot y logdata = <?php system($_GET['cmd']); ?>,
al terminar el script se escribe un webshell—. Encadenar magic functions de gadgets presentes para lograr RCE
o traversal es la técnica de las POP chains (property-oriented programming), automatizable con phpggc. El
mismo patrón existe en Java (readObject, gadgets de ysoserial), .NET y Python (pickle).
File upload → RCE#
Cuando una app permite subir archivos y no filtra bien, subir y ejecutar un script server-side da RCE. El flujo:
probar ejecución con <?php echo phpversion(); ?>, plantar un one-liner de comandos
(<?php passthru($_GET['cmd']); ?> —con un nombre de parámetro largo y absurdo para que nadie más lo abuse—), escalar
a un web shell multifuncional (b374k, con file manager, command shell, acceso a base de datos y protección por
contraseña) y de ahí a una reverse shell por Netcat para pivotar. Ante filtros, la escalera de bypasses:
phpinfo.PHP # blacklist case-sensitive (no normaliza mayúsculas)
Content-Type: image/gif # falsificar el MIME de la parte multipart en Burp
# .htaccess de Apache: forzar a ejecutar extensiones benignas como PHP
AddType application/x-httpd-php .lol
# polyglot: PHP embebido en el EXIF de un JPG válido, renombrado a .php (vence getimagesize())
strings exploit.jpg | head -4 && mv exploit.jpg exploit.php
Donde solo se permiten imágenes, formatos como SVG (que es XML y admite <script>) y SWF (ActionScript
compilado con mtasc, getURL('javascript:...')) convierten un “subir avatar” en un XSS almacenado. Y una
librería de procesamiento de imágenes vulnerable habilita DoS por agotamiento de memoria: pixel flood (JPEG con
dimensiones EXIF de 65000×65000), frame flood (GIF con decenas de miles de frames) o una zlib-bomb en el zTXT
de un PNG.
SSRF (Server-Side Request Forgery)#
La app hace un request hacia un URL que controla el atacante y devuelve parte del resultado, usando al servidor
como proxy para saltar el firewall y alcanzar servicios internos, el loopback o la LAN. El código típico es un
“visor” que hace file_get_contents($_POST['url']). Usos: port-scan interno (por diferencia de error entre
puerto abierto y cerrado, a veces con banner), lectura de archivos (file://), y un abanico de protocol
handlers (gopher:// para forjar payloads TCP arbitrarios contra Redis o una base de datos interna, dict, ftp,
ldap). El salto más rentable del web moderno es a la nube:
# robar credenciales IAM del metadata de la instancia (AWS)
url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# port-scan interno por diferencia de error
url=http://scanme.internal:22/ → banner SSH-2.0-OpenSSH → puerto abierto
Apuntar el SSRF al metadata endpoint 169.254.169.254 roba las credenciales IAM de la instancia —el puente directo
de una vulnerabilidad web a comprometer la cuenta cloud entera—. El SSRF también nace del XXE de
3.2 (una entidad externa que apunta a un servicio interno), y la IDOR moderna
(?id=101 sin chequeo de permiso) —tan prevalente hoy que supera al XSS, porque los frameworks filtran el XSS solos
pero los permisos no se automatizan— cierra el círculo con el control de acceso de 3.4.
API: REST, GraphQL y la autorización que no viaja#
Hay una comodidad del web moderno que reordena todas las anteriores y merece sección propia: la aplicación dejó de ser una página que el servidor arma y pasó a ser un cliente rico —una single-page application o una app móvil— que consume una interfaz de programación (API). El servidor ya no devuelve HTML: devuelve datos, casi siempre en JSON, a través de una colección de endpoints. Ese cambio arquitectónico mueve una frontera crítica —la del control de acceso— a un lugar donde a menudo nadie la reconstruye, y por eso la clase de vulnerabilidad dominante de las API no es una novedad exótica sino la vieja falla de autorización de 3.4 multiplicada por la cantidad de endpoints. El marco de referencia es el OWASP API Security Top 10, y sus primeras posiciones son variantes de un mismo problema: la autorización que la interfaz clásica resolvía en un lugar ahora tiene que resolverse en cada uno de cientos de endpoints, y basta que uno la omita.
La falla número uno es la autorización rota a nivel de objeto (Broken Object Level Authorization, BOLA): es la IDOR de
3.4 a escala de API. Un endpoint como GET /api/pedidos/{id} recibe el identificador del objeto
en la petición y devuelve el pedido —pero si no verifica que ese pedido pertenece al usuario autenticado, cualquiera
itera los identificadores y lee los datos de todos—. Es tan prevalente porque la lógica de la API invita a confiar en el
identificador que el cliente envía, y porque probar la propiedad objeto por objeto es tedioso y se omite. Su hermana es la
autorización rota a nivel de función (Broken Function Level Authorization, BFLA): llamar a funciones para las que no
se tiene rol —adivinar el endpoint administrativo (/api/admin/usuarios), o cambiar el método HTTP (GET que se permite,
DELETE que no se controla)— porque el control de rol se aplicó en la interfaz de usuario pero no en el endpoint.
Dos fallas más completan el núcleo. La asignación masiva (mass assignment) ocurre cuando la API vincula
automáticamente el JSON que recibe a los campos de un objeto interno: si el cliente agrega un campo que la interfaz no
mostraba —{"nombre":"...","rol":"admin"} o {"saldo":999999}—, y el servidor lo asigna sin una lista explícita de qué
campos son modificables, el cliente escribe propiedades que no le corresponden. Es la contracara de escritura de la BOLA. Y
la exposición excesiva de datos (excessive data exposure) es su versión de lectura: la API devuelve el objeto
completo —con campos internos, otros usuarios, banderas privadas— confiando en que el cliente muestre solo lo público; el
atacante, que lee la respuesta cruda, obtiene todo lo que el servidor mandó de más.
GraphQL agrega su propia superficie sobre este fondo. Es un lenguaje de consulta donde el cliente pide exactamente los
datos que quiere en una sola petición, y esa flexibilidad crea vectores específicos. La introspección (introspection)
es la capacidad del esquema de describirse a sí mismo: una consulta de introspección devuelve el catálogo completo de
tipos, campos y operaciones, entregándole al atacante el mapa entero de la API —enormemente útil para el reconocimiento, y
peligroso si queda habilitado en producción—. La profundidad y complejidad de consulta habilitan una denegación de
servicio: como las relaciones se pueden anidar, una consulta profunda (autor → libros → autor → libros …) obliga al
servidor a resolver un árbol exponencial con una sola petición pequeña. Y el procesamiento por lotes (batching)
—enviar muchas operaciones en una sola petición— permite evadir los límites de tasa y las protecciones contra fuerza bruta
que cuentan peticiones, no operaciones. Bajo toda esa superficie nueva, sin embargo, la BOLA y la BFLA siguen mandando: los
resolvers de GraphQL tienen que verificar la autorización objeto por objeto igual que cualquier endpoint REST, y con
frecuencia no lo hacen.
Conviene notar que las clases de inyección de 3.2 no desaparecen en las API: un parámetro que llega por JSON y termina en una consulta a la base de datos es tan inyectable como el de un formulario. La API cambia el envoltorio y el modelo de autorización, no las leyes de la inyección.
Defensa y detección: la lente púrpura#
La defensa de todo el capítulo es una sola idea: reconstruir la frontera de confianza que la comodidad movió. Cada mecanismo moderno tiene un punto donde se debe volver a validar lo que antes validaba el monolito.
Endurecimiento (reconstruir la frontera)#
| Clase | Control primario |
|---|---|
| OAuth | Matching exacto y estricto del redirect_uri (comparación completa, sin prefijo/traversal/subdominios/sufijos TLD); errores en el dominio del authorization server (nunca redirigir); parámetro state (anti-CSRF del flow) + PKCE; preferir code + PKCE sobre implicit; X-Frame-Options en el grant prompt; sin open redirects en el client (mata el Covert Redirect) |
| JWT | Fijar y validar el algoritmo esperado server-side (nunca leer alg del header para decidir simétrico/asimétrico); secretos HMAC aleatorios ≥256 bits; no procesar jwk/jku/kid sin whitelist estricta; exp corto validado + aud; nunca datos sensibles en el payload |
| CORS | Nunca * con credenciales; whitelist de orígenes validada exactamente (no reflejar el Origin); Vary: Origin |
| postMessage / WS | Verificar event.origin/Origin con comparación exacta (no indexOf); event.data con textContent, no innerHTML; target origin explícito; validar el Origin del handshake WS + anti-CSRF por conexión |
| Local Storage | No guardar session IDs ni sensibles (usar cookie HttpOnly/Secure/SameSite); sanitizar lo que salga del storage al DOM |
| SSTI | Nunca concatenar input en la plantilla (pasarlo como variable de contexto); si el usuario provee plantillas, motor con sandbox (sabiendo que se han evadido) o lógica-menos (Mustache); least-privilege del proceso |
| Deserialización | Nunca unserialize()/pickle/readObject sobre input del usuario (usar JSON); allowed_classes en PHP 7+ |
| File upload | Whitelist de extensiones con normalización + magic bytes + re-encodear la imagen (destruye polyglots); servir uploads desde un dominio/directorio sin ejecución (AllowOverride None); nombres aleatorios; Content-Disposition: attachment para SVG/SWF |
| SSRF | Whitelist de destinos + resolver y validar el IP (bloquear rangos privados/link-local/loopback); deshabilitar handlers no-HTTP; egress filtering; en cloud, IMDSv2 (token) para que el SSRF no lea el metadata |
| API (BOLA/BFLA) | Autorización a nivel de objeto en cada endpoint (verificar propiedad/rol server-side, default-deny, no confiar en el identificador del cliente); control de rol en la función además de la interfaz; probar cada objeto y método |
| API (mass assignment / exposición) | Lista explícita de campos vinculables (allowlist, nunca bind automático del JSON completo); devolver solo los campos necesarios (no filtrar en el cliente) |
| GraphQL | Deshabilitar la introspección en producción; límites de profundidad y complejidad de consulta; rate limiting que cuente operaciones, no peticiones (mata el batching); autorización en cada resolver |
Detección (la señal en la telemetría)#
- La firma común de la Familia B es el RCE server-side: un proceso hijo anómalo del servidor de aplicación
(
python/java/php-fpm/w3wp→sh/cmd/id/whoami) es la señal más fuerte de un SSTI, una deserialización o un upload explotados —Event ID 4688 + Sysmon 1, relación padre-hijo—. El upload deja además un archivo nuevo en el webroot seguido de GET con un parámetro de comando en la query. - SSRF: conexiones salientes anómalas del servidor —a
169.254.169.254, a puertos internos secuenciales, o beaconing— en los logs de firewall/proxy. - OAuth/JWT: redirects OAuth hacia dominios externos inesperados (
redirect_uri/Locationfuera del client); un token JWT conalg:none,algen caso raro, ojku/kidanómalos; y —la señal de oro del token forjado— una acción privilegiada de un usuario cuyo token declara un rol que no coincide con el registro en la base de datos, o un mismo token usado desde IPs/geografías nuevas (replay). - CORS/postMessage/WS: conexiones WebSocket con
Originexterno y requests CORS conOriginanómalo; la exfiltración resultante aparece como conexión saliente del navegador a un dominio del atacante. - API (BOLA/BFLA): una sesión que itera identificadores de objeto secuenciales en un endpoint
(
/api/pedidos/101,102,103…) —la misma firma de harvesting de 3.4 llevada a la API—, peticiones a endpoints administrativos desde cuentas sin rol, y un volumen anómalo de respuestas exitosas sobre objetos ajenos; en GraphQL, consultas de introspección desde clientes de producción y consultas de profundidad inusual.
El emparejamiento púrpura es el mismo del resto del bloque web: el atacante opera sobre una frontera que la
aplicación dejó de custodiar, con requests que a menudo parecen legítimos, así que el defensor lo caza por la
anomalía —el proceso hijo que no debería existir, la conexión saliente inesperada, el claim que no coincide con
el registro, el Origin externo—. La correlación de esas señales a escala de SIEM se desarrolla del lado azul en la
Parte 5, con la misma lógica de 4.9 · Defensa de AD aplicada a la capa web. Con este capítulo se
cierra el bloque web de la Parte 3 (3.2→3.5); lo que sigue es la explotación de binarios y la
post-explotación, donde el foothold web se convierte en control del sistema.
Referencias#
red-web/mmwp/cap-06— OAuth 2.0 Security: los cuatro roles, elredirect_uricomo frontera crítica, code vs implicit grant, y las derrotas de la redirección (open redirect por grant malformado, traversal/naked domain/TLD suffix confusion, Covert Redirect, force-install por clickjacking).red-web/mmwp/cap-03— File Upload Vulnerabilities: dephpversion()al webshell y la reverse shell, la escalera de bypasses (case, MIME,.htaccess, polyglot EXIF), XSS por SVG/SWF y DoS por imagen malformada.red-web/mmwp/cap-05— Emerging Attack Vectors: SSRF (port-scan,file://, protocol handlers, MailChimp), IDOR (Flipkart/HackerOne), DOM clobbering (GitHub Gist), RPO/PRSSI, clickjacking y PHP Object Injection (unserialize- magic functions → POP chains).
red-web/web-moderno/cap-01— Server-Side Template Injection: causa raíz (concatenar en la plantilla), detección ({{7*7}}/polyglot), fingerprint ({{7*'7'}}), RCE por motor (Jinja2/Twig/Freemarker/ERB/Mako) y fuga de secretos ({{config}}), con tplmap.red-web/web-moderno/cap-02— JWT Attacks: estructura y firma como única barrera, firma no verificada,alg:none, secreto HMAC débil (hashcat -m 16500), inyecciónjwk/jku/kid, algorithm confusion RS256→HS256, yexp/aud/datos sensibles.red-web/owasp-tg4/cap-02— Client Side moderno: CORS (ACAO*/reflejo delOrigin), Web Messaging (event.origincon whitelist exacta), WebSockets (CSWSH), Local Storage, CSS injection y Cross Site Flashing.- OWASP — SSTI (WSTG-INPV-18), Deserialization, File Upload, SSRF y OAuth 2.0 Cheat Sheets; PortSwigger JWT y OAuth.
- MITRE — CWE-1336 SSTI, CWE-502 Deserialization, CWE-434 Unrestricted Upload, CWE-918 SSRF y CWE-347 Improper Verification of Signature.