Что такое кликджекинг и как защитить от него сайт
Основы3 минРедакция Innovialabs
Коротко
Кликджекинг — это когда ваш сайт загружают в невидимый iframe поверх приманки, и пользователь нажимает вашу кнопку, думая, что нажимает другую. Защита — запретить встраивание страниц заголовками CSP frame-ancestors и X-Frame-Options.
Коротко: что такое кликджекинг
Кликджекинг (clickjacking, «перехват клика») — атака, при которой злоумышленник загружает ваш сайт в прозрачный iframe на своей странице и располагает его точно поверх безобидной кнопки вроде «Получить приз» или «Закрыть рекламу».
Пользователь видит приманку, а кликает по невидимой кнопке вашего сайта. Если он в этот момент авторизован у вас, действие выполняется от его имени — со всеми его правами и cookie.
Как это выглядит технически
Страница атакующего содержит примерно такое:
<button style="position:absolute; top:200px; left:150px">Получить приз</button>
<iframe src="https://example.com/settings"
style="opacity:0; position:absolute; top:0; left:0; width:800px; height:600px; z-index:2">
</iframe>
Iframe полностью прозрачен (opacity:0) и лежит выше кнопки (z-index). Координаты подобраны так, что под кнопкой «Получить приз» оказывается, например, кнопка «Удалить аккаунт» или «Подтвердить».
Защита от CSRF тут не спасает: запрос идёт с вашего собственного сайта, внутри настоящей сессии, с настоящими токенами.
Реальные сценарии вреда
- Смена настроек: включение публичного доступа к профилю, отключение уведомлений безопасности.
- Подтверждение действий: перевод, покупка в один клик, подписка на платную услугу.
- Выдача разрешений: доступ к камере или микрофону, привязка стороннего приложения к аккаунту.
- Лайки и подписки: накрутка действий в соцсетях от имени пользователя.
- Удаление данных: кнопки «Удалить» с одним подтверждением.
Уязвимы прежде всего страницы, где одно-два нажатия приводят к важному действию.
Как защититься
Главное правило: страница сама сообщает браузеру, можно ли её встраивать в чужие фреймы. Для этого есть два заголовка ответа.
| Заголовок | Значение | Что делает |
|---|---|---|
Content-Security-Policy | frame-ancestors 'none' | Запрещает встраивание везде |
Content-Security-Policy | frame-ancestors 'self' | Разрешает только на своём домене |
Content-Security-Policy | frame-ancestors 'self' https://partner.example | Свой домен плюс явный список |
X-Frame-Options | DENY или SAMEORIGIN | Старый заголовок для устаревших браузеров |
CSP frame-ancestors — современный и гибкий вариант. X-Frame-Options стоит отправлять дополнительно для совместимости. Если заданы оба, современные браузеры ориентируются на frame-ancestors.
Пример для Nginx:
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always;
Пример для Next.js в next.config.js:
module.exports = {
async headers() {
return [{
source: '/(.*)',
headers: [
{ key: 'Content-Security-Policy', value: "frame-ancestors 'self'" },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
],
}];
},
};
Важно: frame-ancestors не работает в теге <meta>, только как HTTP-заголовок.
Дополнительные меры для критичных действий:
- Повторное подтверждение: ввод пароля или кода перед удалением аккаунта и переводами.
- Cookie сессии с атрибутом
SameSite=LaxилиStrict— в этом случае браузер не отправит их во фрейм на чужом сайте.
Как проверить, что страницу нельзя встроить
- Заголовки: откройте DevTools, вкладку Network, выберите документ и проверьте наличие
Content-Security-Policyсframe-ancestorsиX-Frame-Options. Или выполнитеcurl -I https://ваш-сайт. - Практический тест: создайте локальный HTML-файл с
<iframe src="https://ваш-сайт">и откройте его в браузере. Вместо сайта должна быть пустая область или ошибка, а в консоли — сообщение о блокировке поframe-ancestors. - Проверьте не только главную, но и страницы настроек, оплаты, админки — заголовки иногда задают не для всех маршрутов.
Частые ошибки
- Защитить только главную страницу, а личный кабинет оставить без заголовков.
- Пытаться задать
frame-ancestorsчерез<meta>. - Полагаться на JavaScript-скрипты «выхода из фрейма» — их можно обойти.
- Поставить
frame-ancestors *«на всякий случай», чтобы работал какой-то виджет.