Cómo se evade un WAF (y por qué un blocklist casi nunca basta)
«Tenemos un WAF» tranquiliza a mucha gente más de lo que debería. Un Web Application Firewall bien puesto detiene el ruido y a los escáneres automáticos, pero la mayoría funcionan por lista negra: bloquean cadenas «malas conocidas». Y una lista negra persigue texto, no intención. Como hay infinitas formas de escribir el mismo ataque, casi siempre hay una que la lista no contempla.
Qué es (y qué no es) un WAF
Un WAF inspecciona las peticiones y bloquea las que casan con sus reglas. Hay dos filosofías: lista negra (denegar lo malo conocido — la mayoría) y lista blanca / seguridad positiva (permitir solo lo esperado — más segura, más difícil de mantener). La lista negra es un badén: te frena, no te para. Estas son las cuatro evasiones que se ven una y otra vez.
1. El mismo ataque, otra forma (XSS sin <script>)
El error clásico: bloquear <script> y creer que has parado el XSS. Pero JavaScript se ejecuta de muchas maneras. Un atributo on… en cualquier etiqueta vale igual:
Bloqueado: <script>alert(1)</script>
Pasa: <img src=x onerror=alert(1)>
Pasa: <svg onload=alert(1)>
La regla perseguía una etiqueta concreta; el ataque (ejecutar JS) tiene decenas de vías.
2. Codificar lo prohibido (path traversal)
El WAF bloquea ../ en claro, pero el servidor decodifica %XX antes de usar la ruta. Si codificas los puntos y las barras, el filtro no ve el patrón y el servidor sí lo reconstruye:
Bloqueado: ../../etc/passwd
Pasa: %2e%2e%2f%2e%2e%2fetc%2fpasswd
La misma idea sirve para colar caracteres «prohibidos» en casi cualquier parámetro.
3. Romper el patrón (mayúsculas, comentarios, sin espacios)
Muchas reglas de SQLi son ingenuas: buscan or en minúscula con espacios, o la palabra UNION. Se rompen cambiando la forma sin cambiar el significado:
Bloqueado: ' OR 1=1 --
Pasa: '/**/OR/**/1=1 (comentarios en línea como separador)
Pasa: 'oR'1'='1' (sin espacios, comparación de cadenas)
Mayúsculas alternadas, comentarios /**/, tabuladores en vez de espacios… todo apunta a lo mismo: el motor SQL es tolerante; el filtro, literal.
4. Doble codificación (contra el WAF «listo»)
Algunos WAF decodifican una vez y luego filtran. La respuesta es codificar dos veces: el WAF ve una capa limpia y la aplicación, al decodificar de nuevo, revela el ataque.
%3C -> "<" (una capa: el WAF lo bloquea)
%253C -> "%3C" -> "<" (dos capas: el WAF ve %3C, la app ve <)
Por qué siempre hay un hueco
Porque una lista negra intenta enumerar lo infinito. El ataque es una intención (ejecutar JS, leer un fichero, alterar una query); las representaciones de esa intención son incontables (codificaciones, mayúsculas, comentarios, caracteres alternativos, fragmentación…). El defensor tiene que acertar todas; el atacante, solo una.
Lo que sí cierra el agujero
- SQLi → consultas parametrizadas (prepared statements). No concatenes entrada en la query.
- XSS → codificación de salida según el contexto + una CSP estricta; no confíes en filtrar la entrada.
- Path traversal → canonicaliza la ruta y valida contra una lista blanca; nunca pases entrada del usuario directa al sistema de ficheros.
- El WAF → como capa extra (defensa en profundidad), en modo seguridad positiva si puedes, y con logging para detectar los intentos.
Checklist
- ✅ ¿Tu WAF es lista negra? Asume que es evadible; no es tu única defensa.
- ✅ XSS: no basta con bloquear
<script>; piensa en manejadoreson…. - ✅ Normaliza/decodifica antes de filtrar, y una sola representación canónica.
- ✅ Arregla la vuln en el código; el WAF es tiempo prestado.
Un WAF es un buen guardia en la puerta, pero no repara la cerradura rota de dentro. Úsalo como lo que es —una capa— y arregla el fallo donde de verdad vive: en el código.