X-Frame-Options и CSP frame-ancestors: защита от встраивания страницы во фрейм

Обновлено: 25.03.2026 34016

Практическое руководство по защите сайта от iframe и clickjacking: X-Frame-Options, CSP frame-ancestors, примеры настройки через Apache, Nginx и PHP и разбор частых ошибок.

Запрет отображения страницы во фрейме: X-Frame-Options и CSP

Время прочтения:

Если страницу сайта можно без ограничений встроить в <frame>, <iframe>, <embed> или <object> на стороннем сайте, это открывает дорогу для атак типа clickjacking. Пользователь видит ваш интерфейс, но поверх него могут быть наложены прозрачные элементы, чужие кнопки, псевдо-формы, обманные действия или сценарии, заставляющие его кликнуть не туда, куда он собирался.

Классическая защита от такого сценария — HTTP-заголовок X-Frame-Options. Но в современных проектах правильнее смотреть на задачу шире: сегодня основным и более гибким механизмом считается Content-Security-Policy: frame-ancestors, а сам X-Frame-Options удобно использовать как дополнительный защитный слой и для совместимости со старыми браузерами.

Коротко: если вы не хотите, чтобы ваш сайт отображался во фрейме на чужих доменах, задайте защиту на уровне HTTP response headers. Не через meta-теги, а именно через заголовки сервера.

X-Frame-Options и CSP frame-ancestors

От чего реально защищает X-Frame-Options

Основная задача этого заголовка — уменьшить риск clickjacking. Типовая схема атаки выглядит так:

  1. злоумышленник встраивает ваш сайт или конкретную страницу в iframe на своём домене;
  2. поверх интерфейса размещает невидимые или маскирующие элементы;
  3. пользователь кликает по тому, что выглядит как обычный элемент интерфейса;
  4. на деле выполняется не то действие, которое он ожидал.

Особенно неприятны такие сценарии на страницах входа, подтверждения действий, личных кабинетах, CMS, админках, формах оплаты и любых экранах, где есть значимые действия пользователя.

Что изменилось в современной практике

В старых статьях по веб-безопасности обычно делают упор только на X-Frame-Options. Это по-прежнему рабочий заголовок, но он уже не является самым гибким и современным способом управления встраиванием страницы.

Сегодня для новых проектов лучше ориентироваться на такую логику:

  • X-Frame-Options — простой и понятный защитный заголовок;
  • CSP frame-ancestors — более современный и гибкий механизм;
  • в идеале использовать оба: CSP как основную политику, X-Frame-Options как дополнительную совместимость.

Важно: защита через X-Frame-Options и через Content-Security-Policy: frame-ancestors задаётся только HTTP-заголовками. Попытка настроить X-Frame-Options через <meta http-equiv> не считается корректным и рабочим способом.

Основные значения X-Frame-Options

DENY

Полностью запрещает отображение страницы во фрейме, даже если попытка идёт с того же сайта.

X-Frame-Options: DENY

Это хороший вариант для максимально строгой защиты, если страницу в принципе не нужно встраивать никуда.

SAMEORIGIN

Разрешает отображение только в том случае, если родительская страница имеет тот же origin, что и сама страница.

X-Frame-Options: SAMEORIGIN

Это самый популярный компромиссный вариант: страница может использоваться во фрейме внутри своего же сайта, но не может быть безопасно встроена на стороннем домене.

ALLOW-FROM

Исторически существовал вариант:

X-Frame-Options: ALLOW-FROM https://example.com

Но сегодня такой подход считается устаревшим. Современные браузеры либо не поддерживают его, либо игнорируют. Поэтому если вам нужно разрешить встраивание страницы для конкретных доверенных доменов, лучше использовать CSP frame-ancestors.

Современная альтернатива: CSP frame-ancestors

Если нужно не просто “запретить всё” или “разрешить только same origin”, а гибко описать, кто именно может встраивать страницу, используйте:

Content-Security-Policy: frame-ancestors 'self';

Или более строгий вариант:

Content-Security-Policy: frame-ancestors 'none';

А если нужно разрешить только конкретные доверенные источники:

Content-Security-Policy: frame-ancestors 'self' https://partner.example.com https://cabinet.example.org;

Такой подход значительно удобнее, чем старый ALLOW-FROM, и лучше подходит для реальных современных интеграций.

Что лучше ставить на практике

Для большинства сайтов оптимальная схема выглядит так:

  • если страницу нельзя встраивать вообще — ставим X-Frame-Options: DENY и Content-Security-Policy: frame-ancestors 'none';
  • если внутри своего сайта фреймы допустимы — ставим X-Frame-Options: SAMEORIGIN и Content-Security-Policy: frame-ancestors 'self';
  • если нужны исключения под доверенные внешние домены — основной контроль делаем через frame-ancestors

Практическая рекомендация: для новых проектов лучше ориентироваться именно на frame-ancestors, а X-Frame-Options оставлять как дополнительную защиту и совместимость.

Настройка через .htaccess (Apache)

Если вы хотите разрешать фреймы только внутри своего сайта, базовая настройка для Apache может выглядеть так:

<IfModule mod_headers.c>
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Content-Security-Policy "frame-ancestors 'self';"
</IfModule>

Если нужно полностью запретить встраивание страницы где бы то ни было:

<IfModule mod_headers.c>
    Header always set X-Frame-Options "DENY"
    Header always set Content-Security-Policy "frame-ancestors 'none';"
</IfModule>

Такой вариант подходит для страниц входа, административных разделов, внутренних кабинетов, API-документации и других чувствительных зон.

Настройка в Nginx

Аналогичная защита для Nginx:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self';" always;

Для полного запрета:

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

Настройка через PHP

Если вам удобнее управлять заголовками на уровне приложения, то задать их можно прямо в PHP до любого вывода HTML:

<?php
declare(strict_types=1);

header('X-Frame-Options: SAMEORIGIN');
header("Content-Security-Policy: frame-ancestors 'self';");

Или для максимально строгого режима:

<?php
declare(strict_types=1);

header('X-Frame-Options: DENY');
header("Content-Security-Policy: frame-ancestors 'none';");

Почему meta-тег здесь не подходит

В старых примерах иногда можно встретить такой вариант:

<meta http-equiv="X-Frame-Options" content="deny">

Для реальной защиты это неправильный путь. Браузеры ожидают X-Frame-Options как HTTP response header. Поэтому ставить его нужно на уровне сервера, прокси или приложения, а не в HTML-разметке.

Ошибка: если вы оставили только <meta http-equiv="X-Frame-Options">, у вас может создаваться ложное ощущение, что защита настроена, хотя на практике она не работает так, как ожидается.

Что делать, если нужен Вебвизор Яндекс.Метрики

Здесь начинается самая неприятная часть практики. Исторически некоторые функции Яндекс.Метрики, связанные с воспроизведением и отображением данных во фрейме, могли конфликтовать с жёсткой защитой от framing. Из-за этого в интерфейсе Метрики могли появляться ошибки вида “установлен запрет на отображение страницы во фрейме”.

Но важный момент: это не повод делать глобальную слабую защиту для всего сайта. Хуже всего — строить исключение на основе HTTP_REFERER и считать, что это “правильное решение по безопасности”. Такой путь хрупкий и годится только как legacy-компромисс в старых проектах, где совсем нет других вариантов.

Что лучше делать вместо этого

  • сначала решить, действительно ли вам нужно ослаблять защиту ради конкретного инструмента;
  • если исключение действительно нужно — делать его как можно уже и точнее;
  • по возможности использовать отдельную политику не для всего сайта, а только для нужных страниц;
  • если требуется разрешить конкретный внешний trusted origin — делать это через CSP frame-ancestors, а не через устаревший ALLOW-FROM или общую логику по referer;
  • все такие послабления сначала тестировать на staging-окружении.

Для большинства сайтов базовая защита должна оставаться строгой, а исключения — быть редкими, точечными и хорошо обоснованными.

Пример точечного ослабления через PHP

Если на проекте действительно есть отдельный участок, которому нужно другое поведение, лучше задавать заголовки условно и явно:

<?php
declare(strict_types=1);

$requestUri = $_SERVER['REQUEST_URI'] ?? '/';

if (strpos($requestUri, '/public-preview/') === 0) {
    header('X-Frame-Options: SAMEORIGIN');
    header("Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com;");
} else {
    header('X-Frame-Options: DENY');
    header("Content-Security-Policy: frame-ancestors 'none';");
}

Это намного профессиональнее, чем пытаться принимать решение только по HTTP_REFERER, user-agent или другим косвенным признакам.

Как проверить корректность настройки

После внедрения обязательно проверьте реальные HTTP response headers. Смотреть нужно не HTML-код страницы, а именно заголовки ответа сервера.

Проверить можно так:

  • через DevTools в браузере, вкладка Network;
  • через curl -I https://example.com/;
  • через онлайн-сервисы проверки security headers;
  • через собственные smoke-тесты на нужных шаблонах и URL.
curl -I https://example.com/

В ответе вы должны увидеть хотя бы один из заголовков:

X-Frame-Options: SAMEORIGIN

и/или:

Content-Security-Policy: frame-ancestors 'self';

Типовые ошибки при настройке

  • надеяться на <meta http-equiv="X-Frame-Options"> вместо серверного заголовка;
  • использовать ALLOW-FROM как будто это современное и поддерживаемое решение;
  • задавать защиту только на части HTML-страниц, забывая про остальные шаблоны;
  • не проверять реальные response headers после внедрения;
  • ослаблять защиту глобально ради одного стороннего сервиса;
  • строить исключения по HTTP_REFERER и считать это надёжной моделью безопасности;
  • не тестировать работу важных виджетов, iframe и аналитики после включения политики.

Что выбрать в реальном проекте

Универсального единственного ответа нет, но для большинства современных сайтов разумная стратегия такая:

  • если фреймы не нужны вообще — ставьте DENY + frame-ancestors 'none';
  • если свои фреймы внутри сайта допустимы — ставьте SAMEORIGIN + frame-ancestors 'self';
  • если есть доверенные внешние встраивания — управляйте ими через frame-ancestors с явным allowlist;
  • если есть конфликт с аналитикой или legacy-интеграцией — делайте точечное исключение, а не слабую глобальную политику.

Вывод

X-Frame-Options — по-прежнему полезный защитный заголовок от clickjacking, но в современной практике лучше рассматривать его не изолированно, а вместе с Content-Security-Policy: frame-ancestors.

Если вам нужна просто понятная защита “по умолчанию”, ставьте SAMEORIGIN или DENY. Если нужен современный и гибкий контроль над тем, кто может встраивать страницу, — используйте frame-ancestors. А любые исключения, особенно ради сторонних сервисов и аналитики, делайте узко, осознанно и только после тестирования.

FAQ

Что лучше: X-Frame-Options или CSP frame-ancestors?

Для новых проектов приоритетнее frame-ancestors, потому что это более современный и гибкий механизм. Но X-Frame-Options всё ещё полезен как дополнительный слой и для совместимости.

Работает ли X-Frame-Options через meta-тег?

Нет, на практике защиту нужно задавать через HTTP response header, а не через <meta http-equiv>.

Можно ли использовать ALLOW-FROM?

Как современную рекомендацию — нет. Это устаревший путь. Для allowlist по внешним доменам лучше использовать Content-Security-Policy: frame-ancestors.

Нужно ли убирать защиту ради Вебвизора?

Обычно нет. Сначала нужно понять, действительно ли есть конфликт, и только потом делать точечное исключение. Глобально ослаблять защиту ради одного инструмента — плохая практика.

Какой базовый вариант подходит большинству сайтов?

Для большинства проектов хороший старт — это X-Frame-Options: SAMEORIGIN и Content-Security-Policy: frame-ancestors 'self';.

Связаться в Telegram

Рейтинг: 5/5
4 голосов

Следующие страницы вас также могут заинтересовать:

Обновлено: 25.03.2026