Перейти к содержанию
Innovialabs

Что такое кликджекинг и как защитить от него сайт

Основы3 мин

Коротко

Кликджекинг — это когда ваш сайт загружают в невидимый 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-Policyframe-ancestors 'none'Запрещает встраивание везде
Content-Security-Policyframe-ancestors 'self'Разрешает только на своём домене
Content-Security-Policyframe-ancestors 'self' https://partner.exampleСвой домен плюс явный список
X-Frame-OptionsDENY или 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 — в этом случае браузер не отправит их во фрейм на чужом сайте.

Как проверить, что страницу нельзя встроить

  1. Заголовки: откройте DevTools, вкладку Network, выберите документ и проверьте наличие Content-Security-Policy с frame-ancestors и X-Frame-Options. Или выполните curl -I https://ваш-сайт.
  2. Практический тест: создайте локальный HTML-файл с <iframe src="https://ваш-сайт"> и откройте его в браузере. Вместо сайта должна быть пустая область или ошибка, а в консоли — сообщение о блокировке по frame-ancestors.
  3. Проверьте не только главную, но и страницы настроек, оплаты, админки — заголовки иногда задают не для всех маршрутов.

Частые ошибки

  • Защитить только главную страницу, а личный кабинет оставить без заголовков.
  • Пытаться задать frame-ancestors через <meta>.
  • Полагаться на JavaScript-скрипты «выхода из фрейма» — их можно обойти.
  • Поставить frame-ancestors * «на всякий случай», чтобы работал какой-то виджет.

Частые вопросы

Риск ниже, но заголовок стоит добавить всё равно: это одна строка конфигурации, а сайт со временем может получить формы, оплату или вход.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.