Защита от Clickjacking: X-Frame-Options vs frame-ancestors
Защита от Clickjacking: X-Frame-Options vs frame-ancestors
Clickjacking (UI redress attack) — это атака, при которой злоумышленник встраивает ваш сайт в прозрачный iframe на своей странице и заставляет пользователя кликать по «невидимым» кнопкам: подтвердить платёж, сменить пароль, добавить разрешение. Защита от clickjacking — один из самых простых security-фиксов, но его всё ещё нет у 40% российских сайтов (по данным Security Scanner). Разбираем механику атаки и три слоя защиты: X-Frame-Options, CSP frame-ancestors и JS frame-busting.
Как работает clickjacking
Атакующий создаёт страницу с невидимым iframe вашего сайта поверх фейкового интерфейса. Пользователь думает, что жмёт «Получить приз», а на самом деле кликает «Удалить аккаунт» в своём залогиненом сеансе вашего сайта. Классический пример — Twitter worm 2009, когда злоумышленники заставили пользователей ретвитнуть вредоносный пост одним кликом. Подробнее — OWASP: Clickjacking.
X-Frame-Options (устаревший, но универсальный)
Заголовок X-Frame-Options описан в RFC 7034. Три значения:
DENY— страница не может быть встроена в iframe нигдеSAMEORIGIN— только с того же originALLOW-FROM uri— устарело, не работает в Chrome/Safari
X-Frame-Options: DENY
CSP frame-ancestors (современный стандарт)
Директива frame-ancestors в CSP Level 2+ заменяет X-Frame-Options и поддерживает множественные источники:
Content-Security-Policy: frame-ancestors 'none';
# или
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com;
Если присутствуют и X-Frame-Options, и frame-ancestors — браузер уважает CSP. Для legacy-клиентов держите оба.
SameSite cookies
Даже если clickjacking прошёл, cookie с атрибутом SameSite=Lax или SameSite=Strict не отправятся в cross-site iframe-запросе. Это второй слой защиты. Детали — в Cookie Security.
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict
JS frame-busting (не полагайтесь только на него)
if (self !== top) {
top.location = self.location;
}
Этот код ломается атрибутом sandbox на iframe злоумышленника. Используйте как fallback для браузеров без CSP-поддержки.
Конфигурация
nginx:
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;
Apache:
Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "frame-ancestors 'none'"
Express/NestJS:
import helmet from 'helmet';
app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.contentSecurityPolicy({
directives: { frameAncestors: ["'none'"] }
}));
Когда нужен SAMEORIGIN
Если у вас есть legitimate iframe-встройки между поддоменами (например, виджеты в админке), ставьте SAMEORIGIN или frame-ancestors 'self'. Никогда не используйте ALLOWALL — эквивалентно отсутствию защиты.
Проверка
curl -I https://example.com | grep -E "X-Frame-Options|Content-Security"
Или используйте Security Scanner enterno.io — он попробует встроить сайт в iframe и покажет, блокируется ли встройка. Обзор связанных заголовков — в HTTP Security Headers.
FAQ
Нужен ли X-Frame-Options, если есть CSP? Да, для браузеров старше CSP Level 2 (IE 11) и как defense-in-depth.
Можно ли разрешить iframe только одному домену? Только через frame-ancestors https://partner.com — X-Frame-Options ALLOW-FROM не работает.
Защищают ли frame-busting JS? Слабо — атакующий обходит через iframe sandbox или HTML5 double-framing.
Что с мобильными приложениями? WebView не следует CSP по умолчанию; дублируйте проверку на стороне сервера через Referer/Origin.
Вывод
Минимум: добавьте два заголовка и закройте тему. Мониторьте через мониторинг — любой регресс в конфиге будет виден сразу. Связанные темы: Cookie Security, все security headers.
Проверьте ваш сайт прямо сейчас
Проверить →