Clickjacking — X-Frame-Options

Clickjacking — атака, при которой злоумышленник накладывает невидимый iframe с целевым сайтом поверх своей страницы, заставляя пользователя кликать по скрытым элементам. X-Frame-Options и CSP frame-ancestors защищают от этого.

Зачем нужно

Без защиты пользователь может непреднамеренно подтвердить банковский перевод, удалить аккаунт или изменить права — думая, что кликает по безобидной кнопке на странице злоумышленника. Особенно опасно для банков, соцсетей и панелей управления.

Где используется

  • Страницы с кнопками действий (оплата, удаление, смена пароля)
  • OAuth-флоу и формы аутентификации
  • Панели администратора и настроек аккаунта

Основной контент

X-Frame-Options (устаревший, но широко поддерживается)

# Запретить встраивание в iframe полностью
X-Frame-Options: DENY

# Разрешить только тому же origin
X-Frame-Options: SAMEORIGIN

CSP frame-ancestors (современный стандарт, приоритетнее)

# Полный запрет
Content-Security-Policy: frame-ancestors 'none';

# Только сам сайт
Content-Security-Policy: frame-ancestors 'self';

# Разрешить несколько доменов
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com;

Express + helmet

const helmet = require('helmet');

app.use(helmet.frameguard({ action: 'deny' }));

// Или вручную, оба заголовка одновременно
app.use((req, res, next) => {
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
  next;
});

Nginx

add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;

Пример атаки (для понимания угрозы)

<!-- Страница злоумышленника -->
<style>
  iframe {
    position: absolute; opacity: 0.01;
    width: 900px; height: 700px; z-index: 999;
  }
  button.fake { position: absolute; top: 330px; left: 400px; }
</style>
<iframe src="https://bank.example.com/transfer?to=hacker&amount=5000"></iframe>
<button class="fake">Выиграй iPhone!</button>

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

  • Использование только X-Frame-Options: ALLOW-FROM — не поддерживается Chrome/Firefox
  • Применение заголовков только к главной странице, а не ко всем ответам
  • Попытка защититься через JavaScript frame-busting — обходится атрибутом sandbox на iframe
  • Отсутствие always в Nginx — заголовки не отправляются при ошибках 4xx/5xx

Связанные темы

Ресурсы