X-Frame-Options es una cabecera de respuesta HTTP que indica al navegador si tu página puede mostrarse dentro de un elemento <iframe>, <frame> u <object>. Su propósito principal es prevenir ataques de clickjacking, una clase de ataques en los que un sitio malicioso incrusta el tuyo de forma invisible y engaña a los visitantes para que realicen acciones no deseadas.
Pese a ser una de las cabeceras de seguridad más antiguas (introducida hacia 2009), X-Frame-Options sigue ampliamente desplegada y aún proporciona una protección importante. Veamos por qué importa, cómo funciona y qué necesitan saber los propietarios de sitios WordPress.
Cómo funcionan realmente los ataques de clickjacking
El clickjacking es sorprendentemente simple en concepto. Imagina que gestionas un sitio WordPress con área de membresía, y la página de perfil tiene un botón "Eliminar mi cuenta". Un atacante crea una página que parece un quiz o un juego divertido. Incrusta tu página de perfil en un iframe invisible colocado justo encima de su botón "Haz clic aquí para ver tu resultado".
Cuando el visitante hace clic en lo que cree que es un botón inofensivo en la página del atacante, en realidad está pulsando el botón "Eliminar mi cuenta" de tu página oculta. Si el visitante tiene la sesión iniciada en tu sitio (y la mayoría de los navegadores conservan las cookies de sesión), la acción se ejecuta. El visitante no tiene ni idea de lo que acaba de pasar.
Esta técnica se ha usado en ataques reales para:
- Cambiar ajustes de privacidad en cuentas de redes sociales
- Autorizar aplicaciones OAuth sin que el usuario lo sepa
- Hacer clic en anuncios para generar ingresos fraudulentos
- Habilitar acceso a la webcam o al micrófono en navegadores antiguos
- Transferir dinero en interfaces de banca online
El ataque funciona porque el navegador no tiene una forma integrada de saber que el iframe se está usando con malas intenciones. X-Frame-Options da a tu servidor una manera de optar por no ser nunca enmarcado.
Valores de la cabecera X-Frame-Options explicados
La cabecera admite dos valores prácticos (más un tercero obsoleto):
DENY: la página no puede mostrarse en ningún frame, punto. Ningún sitio web, incluido el tuyo, puede incrustar esta página en un iframe. Es el ajuste más estricto y funciona bien para páginas que nunca deberían enmarcarse, como páginas de inicio de sesión o paneles de administración.SAMEORIGIN: la página solo puede mostrarse en un frame si el sitio que enmarca tiene el mismo origen (mismo protocolo, dominio y puerto). Es el valor más utilizado porque permite que tu propio sitio use iframes mientras bloquea sitios externos.ALLOW-FROM uri: se suponía que permitiría especificar un único origen autorizado a enmarcar tu página. Sin embargo, nunca tuvo soporte coherente entre navegadores. Chrome y Safari nunca lo implementaron. Firefox lo admitió un tiempo pero retiró el soporte. Este valor está prácticamente muerto y no debería usarse.
Cuándo usar DENY o SAMEORIGIN en WordPress
Para la mayoría de sitios WordPress, SAMEORIGIN es la opción correcta. Aquí tienes el motivo:
WordPress utiliza iframes internamente en varios lugares. El Customizer del tema carga una vista previa de tu sitio en un iframe. El cargador de la biblioteca de medios usa iframes. El cuadro de diálogo "Insertar medio" del editor clásico también. Si configuras DENY, todas estas funciones se romperán porque ni siquiera tu propio dominio podrá enmarcar la página.
DENY tiene sentido para páginas concretas en las que nunca debería producirse un enmarcado, como una página de login independiente o una página de procesamiento de pagos. Pero como valor por defecto a nivel de sitio para WordPress, SAMEORIGIN ofrece la protección que necesitas sin romper la funcionalidad del administrador.
Si gestionas una configuración de WordPress headless en la que la interfaz de administración está en un dominio y el front-end en otro, debes tener especial cuidado, porque SAMEORIGIN solo permite el enmarcado desde exactamente el mismo origen.
Por qué se desaconsejó ALLOW-FROM
Merece la pena entender el desuso de ALLOW-FROM, porque ilustra un punto más amplio sobre las cabeceras de seguridad web. La directiva formaba parte de la especificación original de X-Frame-Options, y la idea era sencilla: permitir que un sitio dijera "solo este dominio concreto puede enmarcarme". En la práctica, tenía varios problemas:
- Solo aceptaba un único URI, así que no se podían autorizar varios dominios
- Chrome nunca la implementó (el navegador más usado del mundo simplemente la ignoraba)
- Safari tampoco la admitió
- El comportamiento era inconsistente entre los navegadores que sí la admitían
La directiva frame-ancestors de CSP se diseñó como su reemplazo adecuado, soportando varios orígenes y con un comportamiento coherente entre navegadores. Si necesitas permitir que dominios externos concretos incrusten tus páginas, frame-ancestors es la forma de hacerlo.
X-Frame-Options frente a CSP frame-ancestors
La directiva frame-ancestors de la cabecera Content-Security-Policy cumple el mismo propósito que X-Frame-Options pero con más flexibilidad:
Content-Security-Policy: frame-ancestors 'self'Esto equivale a X-Frame-Options: SAMEORIGIN. Pero frame-ancestors también admite varios orígenes:
Content-Security-Policy: frame-ancestors 'self' https://socio-de-confianza.com https://otro-dominio.comEsto sería imposible solo con X-Frame-Options.
Cuando ambas cabeceras están presentes, el comportamiento del navegador varía. Los navegadores modernos generalmente priorizan frame-ancestors sobre X-Frame-Options. Sin embargo, la práctica recomendada es enviar ambas cabeceras para máxima compatibilidad. Los navegadores antiguos que no entienden CSP recurrirán a X-Frame-Options, mientras que los modernos usarán frame-ancestors.
Aquí tienes un ejemplo de uso conjunto:
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'Configurar X-Frame-Options para WordPress
De hecho, WordPress envía X-Frame-Options: SAMEORIGIN por defecto para el área de administración (wp-admin). Esto se hace en el archivo wp-includes/functions.php a través de la función send_frame_options_header(). Sin embargo, el front-end de tu sitio no recibe esta cabecera por defecto.
Para añadirla a nivel de sitio mediante Apache, pon esto en tu archivo .htaccess:
Header always set X-Frame-Options "SAMEORIGIN"Para Nginx:
add_header X-Frame-Options "SAMEORIGIN" always;También puedes configurarla mediante PHP en tu tema o un plugin:
add_action('send_headers', function() {
header('X-Frame-Options: SAMEORIGIN');
});Si usas plugins que necesitan incrustar tu sitio en un iframe en otro dominio (algunas herramientas de A/B testing, widgets de chat en vivo o servicios de previsualización), puede que necesites ajustar tu enfoque. En ese caso, usa frame-ancestors de CSP para autorizar esos dominios concretos en lugar de retirar X-Frame-Options por completo.
Qué comprueba InspectWP
InspectWP verifica si tu sitio WordPress envía la cabecera X-Frame-Options en sus páginas del front-end. Si la cabecera falta, el informe lo marca como un problema de seguridad porque cualquier sitio podría incrustar tus páginas en un iframe, posibilitando ataques de clickjacking contra tus visitantes. El informe también indica el valor de la cabecera si está presente, para que puedas verificar que está configurada como SAMEORIGIN o DENY según convenga a tu instalación.