Imagina que alguien pone una lámina transparente sobre tu pantalla, con botones invisibles alineados justo encima de los de la página que estás mirando.
Haces clic en “Reclamar premio” y en realidad estás presionando un botón que estaba debajo, oculto: “Confirmar transferencia”, “Autorizar acceso” o “Eliminar cuenta”.
Eso es clickjacking — literalmente, secuestro de clics.
Cómo funciona el engaño
La técnica se apoya en un elemento HTML llamado <iframe>: un recuadro que puede cargar otro sitio web dentro de una página.
Un atacante construye su propia página con un iframe apuntando a tu sitio. Lo hace transparente con CSS y lo superpone exactamente sobre sus propios botones. El usuario ve la página del atacante, pero sus clics llegan a tu sitio sin que lo sepa.
Lo que el usuario VE:
┌─────────────────────────────┐
│ "¡Ganaste un premio!" │
│ │
│ [ Reclamar ] │
└─────────────────────────────┘
Tu sitio real, invisible encima:
┌─────────────────────────────┐
│ Panel de cuenta │
│ │
│ [ Eliminar cuenta ] │
└─────────────────────────────┘
El clic en “Reclamar” activa “Eliminar cuenta” en tu sitio.
Por qué es un problema real
Lo que hace este ataque especialmente peligroso es que funciona con la sesión activa del usuario. Si alguien está autenticado en tu plataforma y visita la página del atacante, sus clics inesperados se ejecutan con sus permisos completos.
Se ha usado para:
- Forzar likes, compartidos y suscripciones en redes sociales (Twitter lo sufrió en 2009 con retweets masivos involuntarios).
- Activar permisos de cámara y micrófono en Flash (Adobe, también 2009).
- Confirmar transacciones o cambios en cuentas bancarias y paneles de administración.
No es necesario robar credenciales: el usuario ya está autenticado y el atacante solo necesita que haga clic en el lugar equivocado.
La solución: un header HTTP
El fix es tan simple que cuesta creerlo: agregas un header HTTP que le dice al navegador “esta página no puede cargarse dentro de un iframe de otro sitio”.
Hay dos formas equivalentes de hacerlo.
Opción 1 — X-Frame-Options (compatible con todos los navegadores)
X-Frame-Options: DENY
DENY impide que tu página aparezca en cualquier iframe, sin excepción. Si necesitas usar iframes dentro de tu mismo dominio (por ejemplo, para un widget propio), usa SAMEORIGIN:
X-Frame-Options: SAMEORIGIN
Opción 2 — frame-ancestors en CSP (el estándar moderno)
Content-Security-Policy: frame-ancestors 'none';
Equivale a DENY. La ventaja de esta forma es que puedes ser más específico:
Content-Security-Policy: frame-ancestors 'self' https://panel.organizacion-ejemplo.cl;
Eso permite iframes solo desde tu propio dominio y desde una URL concreta que tú controlas.
Si ya tienes una cabecera CSP configurada, agrega frame-ancestors ahí en vez de usar X-Frame-Options por separado — los navegadores modernos priorizan CSP cuando los dos están presentes.
Cómo configurarlo
En Apache — en .htaccess o en el virtualhost:
Header always set X-Frame-Options "DENY"
Si usas CSP:
Header always append Content-Security-Policy "frame-ancestors 'none';"
Asegúrate de tener el módulo headers activado:
sudo a2enmod headers
sudo systemctl reload apache2
En nginx:
add_header X-Frame-Options "DENY" always;
O con CSP:
add_header Content-Security-Policy "frame-ancestors 'none';" always;
Cómo verificar que está activo
Desde terminal, en un solo comando:
curl -sI https://tu-sitio.cl | grep -iE "x-frame|frame-ancestors"
Si está bien configurado, la respuesta incluirá algo como:
x-frame-options: DENY
Si el comando no devuelve nada, el header no está configurado.
También puedes usar el Security Headers checker ingresando tu dominio — muestra el estado de todos los headers de seguridad relevantes en una sola vista.
En resumen
Clickjacking aprovecha que cualquier sitio puede cargar otro dentro de un iframe. La defensa es decirle al navegador que no lo permita. Un header, dos minutos de configuración, problema cerrado.
En FreeScan, el hallazgo HDR-CLICKJACK aparece cuando ninguno de los dos controles — X-Frame-Options ni frame-ancestors — está presente en las respuestas del sitio.