La sigla XSS viene del inglés cross-site scripting; se escribe X en lugar de C para no confundirla con las hojas de estilo CSS. Es una vulnerabilidad de inyección: el sitio inserta en su página un dato llegado del exterior sin neutralizarlo, y el navegador lo interpreta como código. MITRE la cataloga con el identificador CWE-79. Como el script parece proceder del propio sitio, el navegador le concede la misma confianza que al resto de la página.
Cómo funciona un ataque XSS
Todo empieza con un valor que controla el atacante: un parámetro de la URL, un campo de formulario, un comentario, una cabecera HTTP guardada en unas estadísticas. Si el código muestra ese valor en bruto, una cadena como <script>…</script> o un atributo onerror="…" se vuelve ejecutable. El script puede entonces leer el contenido de la página, enviar peticiones en nombre de la persona conectada, modificar lo que ve o redirigirla a otro sitio.

En un sitio WordPress, el objetivo preferido es el administrador. Un script que se ejecuta en su sesión puede crear una cuenta, instalar un plugin o cambiar un ajuste sin conocer nunca su contraseña: el propio navegador envía las cookies de sesión con cada petición.
Los distintos tipos de XSS
- XSS almacenada (persistente): el script se guarda en el servidor, por ejemplo en la base de datos, y se devuelve cada vez que se muestra. No se pide ninguna acción especial a la víctima: le basta con abrir la página.
- XSS reflejada: el script viaja en la petición, a menudo un enlace trampa, y el servidor lo copia de inmediato en su respuesta. La víctima tiene que hacer clic.
- XSS basada en el DOM: es el JavaScript de la página el que inserta un dato no fiable, como el fragmento de una URL. Puede que el servidor no vea pasar nada.
OWASP describe también la XSS «ciega»: una variante almacenada que se activa más tarde, en una interfaz que el atacante no ve. Es justo el caso de los registros, las estadísticas y los formularios recibidos que se consultan desde el Escritorio. Los casos reales de XSS en plugins populares demuestran que este escenario no tiene nada de teórico.
En WordPress: escapar en el último momento
La documentación para desarrolladores de WordPress fija una regla sencilla: escapar lo más tarde posible, en el momento en que se muestra el dato, con la función que corresponde al contexto.
| Contexto de salida | Función |
|---|---|
| Texto dentro de un elemento HTML | esc_html() |
| Valor de un atributo | esc_attr() |
| URL en href o src | esc_url() |
| JavaScript en línea | esc_js() |
| Contenido de un textarea | esc_textarea() |
| HTML con algunas etiquetas permitidas | wp_kses(), wp_kses_post() |
<h2><?php echo esc_html( $titulo ); ?></h2>
<a href="<?php echo esc_url( $enlace ); ?>" title="<?php echo esc_attr( $info ); ?>">Ver</a>
Validar y sanear las entradas sigue siendo útil, pero no sustituye este escape en la salida: un mismo valor puede mostrarse en varios contextos.
Cómo protegerte sin tocar el código
- Actualiza pronto plugins y temas: las XSS explotadas de forma masiva apuntan casi siempre a versiones ya corregidas.
- Mantén pocas cuentas con privilegios y elimina las que ya no se usan.
- Vigila la aparición de nuevos administradores, señal frecuente de una XSS explotada.
- Añade una política de seguridad de contenido, sabiendo que una versión estricta, que prohíba los scripts en línea, es difícil de mantener con WordPress. Nuestra guía de cabeceras de seguridad HTTP para WordPress detalla las opciones.
- Un cortafuegos de aplicaciones web puede detener cargas conocidas, sin garantía frente a una variante inédita.
WP Masquerade Pro envía cabeceras de seguridad, entre ellas una CSP básica opcional. Esa CSP no restringe los scripts, así que no bloquea una XSS. WP Masquerade no es un cortafuegos de aplicaciones ni un corrector de código: solo la actualización del componente defectuoso cierra la vulnerabilidad.
Errores frecuentes
- Confundir XSS y CSRF: la segunda hace que el navegador envíe una petición falsificada, sin ejecutar ningún script en la página. Una XSS, en cambio, permite saltarse las protecciones anti-CSRF, ya que el script lee los tokens de la página.
- Creer que un dato es «interno» porque se muestra en la administración: una cabecera HTTP o una URL visitada vienen del exterior.
- Usar
esc_html()en un atributo oesc_attr()para una URL: cada contexto tiene su función. - Pensar que la protección contra la inyección SQL basta: un dato bien guardado en la base puede seguir siendo peligroso al mostrarse.