Заголовки безопасности HTTP
Заголовки безопасности HTTP.
Содержание
1 Типы проблем безопасности
- Отсутствие или неправильная настройка HTTP-заголовков безопасности создаёт конкретные векторы атак.
| Заголовок | Последствие отсутствия | Последствие неправильного наличия |
|---|---|---|
X-Frame-Options | Кликджекинг | Блокировка легитимных фреймов или слабая защита |
X-Content-Type-Options | XSS через загрузку файлов | Обычно безопасен |
X-XSS-Protection | Снижение защиты для старых броузеров | Может вызвать блокировку страниц |
HSTS | SSL-стриппинг | Недостаточное время жизни, проблемы с откатом |
CSP | XSS-атаки всех видов | Сломанный сайт или бесполезная политика |
Referrer-Policy | Утечка приватных данных в URL | Сломанная аналитика или передача слишком много данных |
Server / X-Powered-By | Безопасно | Раскрытие информации о версиях |
1.1 X-Frame-Options
1.1.1 Отсутствие
- Сайт становится уязвимым к кликджекингу (clickjacking).
- Злоумышленник встраивает вашу страницу в
<iframe>на своём сайте и накладывает поверх неё прозрачные элементы. - Пользователь думает, что кликает по кнопке на сайте злоумышленника, а на самом деле взаимодействует с вашим сайтом (например, переводит деньги, меняет пароль).
- Без этого заголовка броузер не запрещает встраивание в фреймы.
1.1.2 Наличие
X-Frame-Options: ALLOW-FROM https://bad-site.com— устаревший и плохо поддерживаемый параметр.- Если установлено
DENY— можно случайно заблокировать легитимные встраивания (например, виджеты на партнёрских сайтах). - Если
SAMEORIGIN— не защищает от атак через поддомены (если они скомпрометированы).
1.2 X-Content-Type-Options
1.2.1 Отсутствие
- Броузер может выполнять MIME-сниффинг (автоопределение типа содержимого).
- Если злоумышленник загрузит на ваш сервер файл с расширением
.jpg, но с содержимым HTML/JavaScript, броузер может распознать его как скрипт и выполнить в контексте вашего домена. - Это приводит к XSS-атакам через загрузку файлов (например, в аватарках).
1.2.2 Наличие
- Проблем нет, если установлено
nosniff. - Это строгое требование доверять заголовку
Content-Type, что полностью предотвращает сниффинг.
1.3 X-XSS-Protection
1.3.1 Отсутствие
- В старых броузерах (Internet Explorer, Safari, Chrome до версии 78) была встроенная защита от отражённого XSS.
- Если её отключить или не установить заголовок, броузер может не включить эту защиту.
- Однако в современных броузерах этот заголовок устарел и игнорируется, поэтому основной риск — для устаревших клиентов.
1.3.2 Наличие
X-XSS-Protection: 0— отключает защиту, что плохо.X-XSS-Protection: 1; mode=block— включает защиту с блокировкой страницы.
1.3.3 Проблема
- Сам механизм защиты может создавать уязвимости (например, DOM-based XSS с использованием
mode=block), поэтому современные рекомендации советуют удалить этот заголовок и полагаться на CSP. - Но в корпоративных средах со старыми броузерами его временно оставляют.
1.4 Strict-Transport-Security (HSTS)
1.4.1 Отсутствие
- Пользователь может быть атакован через SSL-стриппинг (злоумышленник перехватывает первый HTTP-запрос и перенаправляет на поддельный сайт без HTTPS).
- Даже если у вас включён HTTPS, первый запрос может быть по HTTP, и атакующий может вмешаться до переадресации.
- Без HSTS броузер не запоминает, что к вашему сайту нужно ходить только по HTTPS.
1.4.2 Наличие
- Слишком короткий
max-age(например, 5 минут) не даёт достаточной защиты. - Если не включён
includeSubDomains, то поддомены остаются незащищёнными. - Ошибка в настройке может привести к тому, что броузер будет отказываться подключаться к сайту по HTTP, что сломает доступ для пользователей, если у них нет HTTPS.
- Если вы установите
preloadи отправите домен в список HSTS preload, вы не сможете легко отказаться от HTTPS в будущем.
1.5 Content-Security-Policy (CSP)
1.5.1 Общая информация
- CSP — это белый список для броузера.
- Разрешает работать с ресурсами только из списка.
- Для защиты от XSS-атак (межсайтовый скриптинг).
- Даже если хакер внедрит свой скрипт на вашу страницу, броузер не выполнит его, потому что его нет в белом списке.
1.5.2 Отсутствие
- Отсутствие CSP позволяет выполнять любой инлайн-скрипт и загружать ресурсы с любых источников.
- Это делает сайт максимально уязвимым для XSS-атак любого типа (отражённых, хранимых, DOM-based).
- Злоумышленник, внедривший скрипт, сможет украсть куки, сессии, данные форм и т.д.
1.5.3 Наличие
- Слишком слабая политика (например,
default-src *илиscript-src 'unsafe-inline') почти не даёт защиты (эквивалентно отсутствию). - Если установить слишком строгую политику, то сайт перестаёт работать: не грузятся скрипты, стили, картинки, шрифты с CDN, блокируются виджеты, API. Это приводит к нарушению функционирования сайта, но не к уязвимости.
- Неправильные директивы для
frame-ancestorsмогут разрешить кликджекинг (если не используетсяX-Frame-Options). - Отсутствие
report-uriилиreport-toмешает отслеживать нарушения и корректировать политику.
1.6 Referrer-Policy
1.6.1 Отсутствие
- Броузер по умолчанию передаёт полный URL предыдущей страницы в заголовке
Referer. - Это может раскрывать чувствительные данные в строке запроса (например,
?token,?sessionid) при переходе на сторонние сайты. - Утечка таких данных может привести к компрометации сессий.
1.6.2 Наличие
- Неправильная политика, например
no-referrer, может сломать аналитику или логику, зависящую от источника перехода. unsafe-urlпередаёт полный URL даже внутри сайта.- Рекомендуется
strict-origin-when-cross-origin(передаёт только домен для кросс-доменов и полный для своих), это безопасно.
1.7 Server и X-Powered-By
1.7.1 Наличие
- Раскрытие информации.
- Заголовок
Server: Apache/2.4.54 (Ubuntu)сообщает злоумышленнику точную версию веб-сервера и ОС. - Заголовок
X-Powered-By: PHP/8.1.2раскрывает версию интерпретатора. - Это помогает атакующему подобрать известные эксплойты именно для версии сайта.
1.7.2 Отсутствие
- Усложняет исследование сайта.
- Однако их отсутствие не создаёт прямой уязвимости, но скрытие уменьшает поверхность атаки.
1.8 Приоритеты безопасности
Критично (высокий приоритет):
- Добавить
Strict-Transport-Security(HSTS) на всех HTTPS-портах. - Добавить
X-Content-Type-Options: nosniff(защита от MIME-атак). - Убрать
ServerиX-Powered-By(скрытие информации).
- Добавить
Важно (средний приоритет):
- Добавить
X-Frame-Options(защита от кликджекинга). - Добавить
Content-Security-Policy(защита от XSS, но требует тонкой настройки, чтобы не сломать сайт).
- Добавить
Рекомендуется (низкий приоритет):
X-XSS-Protection(устаревает в современных броузерах, но полезен как запасной вариант).Referrer-Policy(контроль конфиденциальности).
1.9 Риски массового применения (через общий конфиг)
- Если применять одни и те же заголовки ко всем виртуальным хостам, возможны следующие проблемы:
- CSP для одного сайта может оказаться слишком строгим для другого (например, на одном используются внешние скрипты, а на другом — нет).
- HSTS на поддоменах (
includeSubDomains) может заставить все поддомены работать только по HTTPS, что может быть проблематично, если некоторые из них используют HTTP для внутренних целей. - Referrer-Policy может повлиять на работу аналитических систем.
2 Устранение недостатков
- Все проблемы решаются настройкой веб-сервера (Nginx, Apache).
2.1 Добавить недостающие заголовки безопасности
В зависимости от вашего веб-сервера, добавьте следующие директивы:
Для Nginx (в блоке
server):add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # ТОЛЬКО ДЛЯ HTTPSДля Apache (в
.htaccessили конфиге):Header always set X-XSS-Protection "1; mode=block" Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';" Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" env=HTTPS
2.2 Убрать информативные заголовки (Server, X-Powered-By)
Эти заголовки раскрывают версию ПО, что помогает злоумышленникам.
Nginx: В
nginx.confв секцииhttpдобавьтеserver_tokens off;.Apache: Включите
ServerSignature OffиServerTokens Prod.Убрать
X-Powered-By:- PHP:
expose_php = Offвphp.ini. - Python/Flask: Используйте
app.config['PROPAGATE_EXCEPTIONS'] = Trueили удалите заголовок в middleware. - Java: Настройте
server.servlet.session.cookieили используйте фильтр для удаления заголовка.
- PHP:
2.3 Настройка CSP (Content-Security-Policy)
2.3.1 Пример работы
Пусть заголовок содержит:
default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; ...Это означает:
default-src 'self': все ресурсы по умолчанию грузить только со своего домена.'unsafe-inline': разрешить выполнять скрипты и стили, написанные прямо в HTML.
Шаблон блокирует работу, если на сайте есть:
- Яндекс.Метрика или Google Analytics (скрипты грузятся с их серверов).
- Видео с YouTube или Vimeo (iframe).
- Шрифты Google Fonts (загружаются с
fonts.googleapis.com). - Картинки с внешних CDN.
- Виджеты или API (подключение к сторонним сервисам).
Например, броузер увидит попытку загрузить скрипт с
https://mc.yandex.ruи заблокирует его, потому что в политике разрешен только'self'(свой хост).
2.3.2 Настройка под сайт
- Необходимо предварительно исследовать работу сайта.
Режим отчетов (Report-Only)
- В Apache используется
Header set Content-Security-Policy-Report-Only. - В этом режиме броузер не блокирует ресурсы, а пишет в отлабочную консоль ошибки.
# Включаем безопасный режим наблюдения, чтобы ничего не сломать Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';"- В Apache используется
Консоль разработчика
- Откройте сайт.
- Нажмите F12 в броузере (Chrome/Firefox), перейдите на вкладку Console (Консоль).
Вы увидите красные ошибки типа:
Refused to load the script ‘https://mc.yandex.ru/metrika/tag.js’ because it violates the following Content Security Policy directive: “script-src ‘self’ ‘unsafe-inline’”.
Правка шаблона
- Глядя на ошибки, добавляйте недостающие домены в нужные директивы.
Шаблон:
script-src 'self' 'unsafe-inline';Видим ошибку с Яндекс.Метрикой:
- Добавляем
https://mc.yandex.ruhttps://yandex.ru:script-src 'self' 'unsafe-inline' https://mc.yandex.ru https://yandex.ru;
- Добавляем
Видим ошибку с Google Fonts (стили и шрифты):
- Добавляем в
style-srcиfont-src:style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;font-src 'self' https://fonts.gstatic.com;
- Добавляем в
Видим ошибку с загрузкой картинок с внешнего CDN:
- Добавляем в
img-src:img-src 'self' data: https://cdn.myhost.ru;
- Добавляем в
- Глядя на ошибки, добавляйте недостающие домены в нужные директивы.
Включайте политику
- Когда в режиме
Report-Onlyконсоль чиста, меняете название заголовка сContent-Security-Policy-Report-OnlyнаContent-Security-Policy(убираете-Report-Only). - Теперь защита работает.
- Когда в режиме
2.4 Конфигурационный файл
2.4.1 Apache
Файлы конфигурации
- Стандартные каталоги Apache для хранения общих конфигураций:
Дистрибутив Путь для общих конфигураций Файл конфигурации Debian/Ubuntu /etc/apache2/conf-available/,/etc/apache2/conf.d//etc/apache2/apache2.confRHEL/CentOS/Fedora /etc/httpd/conf.d//etc/httpd/conf/httpd.conf
Сокрытие информации о сервере
- Поместить в основной файл конфигурации:
# Скрытие информации о сервере ServerTokens Prod ServerSignature Off
- Поместить в основной файл конфигурации:
Подключение
Файл в conf.d / conf-available
Создайте файл с заголовками безопасности
Debian/Ubuntu:
sudo touch /etc/apache2/conf-available/security-headers.confRHEL/CentOS:
sudo touch /etc/httpd/conf.d/security-headers.conf
Добавьте нужные заголовки:
# /etc/apache2/conf-available/security-headers.conf (для Debian/Ubuntu) # или /etc/httpd/conf.d/security-headers.conf (для RHEL/CentOS) <IfModule mod_headers.c> # Удаление дубликатов, приходящих от бэкенда Header unset X-Frame-Options Header unset X-Content-Type-Options Header unset Referrer-Policy # Header unset Content-Security-Policy # Установка корректных заголовков безопасности Header always set X-Frame-Options "SAMEORIGIN" Header always set X-Content-Type-Options "nosniff" Header always set Referrer-Policy "strict-origin-when-cross-origin" # HSTS Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # XSS Protection Header always set X-XSS-Protection "0" # CSP Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';" # Дополнительные заголовки (опционально) Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()" Header always set Cross-Origin-Resource-Policy "same-origin" Header always set Cross-Origin-Opener-Policy "same-origin" # Header always set Cross-Origin-Embedder-Policy "require-corp" # Перестал работать Gravatar Header always set Cross-Origin-Embedder-Policy "credentialless" </IfModule>
- Подключите файл
Debian/Ubuntu
sudo a2enconf security-headers sudo systemctl reload apache2- Или вручную добавьте в
/etc/apache2/apache2.conf:IncludeOptional /etc/apache2/conf-available/security-headers.conf
- Или вручную добавьте в
RHEL/CentOS
- Файлы в
conf.d/подключаются автоматически. - Перезагрузите Apache:
sudo systemctl reload httpd
- Файлы в
Использовать .htaccess
Если включена обработка
.htaccess, можно разместить общие заголовки в корневом.htaccessсайта..htaccessснижает производительность, так как Apache проверяет его при каждом запросе.Для продакшена лучше использовать основной конфиг.
Файл:
/var/www/html/.htaccessили/путь_к_сайту/.htaccess# .htaccess с общими заголовками безопасности Header always set X-Frame-Options "SAMEORIGIN" Header always set X-Content-Type-Options "nosniff" Header always set X-XSS-Protection "1; mode=block" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Общий файл в каждом виртуальном хосте
- Если нужно, чтобы разные виртуальные хосты использовали разные общие файлы:
<VirtualHost *:443> ServerName site1.example.com DocumentRoot /var/www/site1 # Подключаем общий файл с заголовками Include /etc/apache2/conf-available/security-headers.conf # Дополнительные настройки для этого хоста SSLEngine on SSLCertificateFile /path/to/cert.pem </VirtualHost>
- Если нужно, чтобы разные виртуальные хосты использовали разные общие файлы:
Проверка конфигурации
Проверьте синтаксис конфигурации:
sudo apachectl configtest # или sudo httpd -tПроверьте, что конфигурация загружена:
# Debian/Ubuntu sudo apache2ctl -t -D DUMP_CONFIG | grep -i "security-headers" # RHEL/CentOS sudo httpd -t -D DUMP_CONFIG | grep -i "security-headers"
2.4.2 Nginx
Общий конфигурационный файл
Создаётся отдельный файл в одной из стандартных директорий:
/etc/nginx/snippets/— стандартная директория для подключаемых фрагментов конфигурации (предпочтительно)./etc/nginx/conf.d/— альтернативный вариант (файлы из этой директории подключаются автоматически).Создадим файл:
sudo mkdir -p /etc/nginx/snippets sudo touch /etc/nginx/snippets/security-headers.conf
Файл
security-headers.conf- Файл содержит необходимые заголовки безопасности.
- Используется параметр
always, чтобы заголовки добавлялись не только к успешным ответам (2xx, 3xx), но и к ответам с ошибками (4xx, 5xx).
# /etc/nginx/snippets/security-headers.conf # Общие заголовки безопасности для всех сайтов # HSTS (только для HTTPS server-блоков) add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # Защита от кликджекинга add_header X-Frame-Options "SAMEORIGIN" always; # Запрет MIME-сниффинга add_header X-Content-Type-Options "nosniff" always; # Контроль Referer add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Управление разрешениями браузера (опционально) add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always; # Устаревший XSS-фильтр add_header X-XSS-Protection "0" always; # Дополнительные заголовки (опционально) add_header Cross-Origin-Resource-Policy "same-origin" always; add_header Cross-Origin-Opener-Policy "same-origin" always; # add_header Cross-Origin-Embedder-Policy "require-corp" always; # Перестал работать Gravatar add_header Cross-Origin-Embedder-Policy "credentialless" always; # CSP (Режим отчета, чтобы ничего не сломать) add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';" always; # CSP (Активна, сайт может не работать) #add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
Подключение файла
- В Nginx директива
add_headerне наследуется из внешнего блока (httpилиserver), если во вложенном блоке (location) определена хотя бы одна своя директиваadd_header. - В этом случае все заголовки из внешнего блока будут проигнорированы.
- Если в каком-то
locationнужно добавить специфичный заголовок, придётся продублировать туда и все общие заголовки, либо использовать директивуincludeвнутри этогоlocation.
Подключение внутри каждого
serverблокаserver { listen 443 ssl; http2 on; server_name example.com; # Подключаем общий файл с заголовками include /etc/nginx/snippets/security-headers.conf; # ... остальная конфигурация (SSL, location и т.д.) }
Глобальное подключение
- Можно подключить файл в главном конфигурационном файле
nginx.confвнутри блокаhttp. - Это применит заголовки ко всем сайтам по умолчанию.
http { # ... другие директивы ... include /etc/nginx/snippets/security-headers.conf; # ... подключение остальных конфигураций ... }- Можно подключить файл в главном конфигурационном файле
- В Nginx директива
Скрыть версию сервера
- Заголовки
ServerиX-Powered-By.
Скрыть версию Nginx
- Добавить директиву
server_tokens off;в блокhttpфайла/etc/nginx/nginx.conf. - Это изменит заголовок
Serverсnginx/1.24.0на простоnginxи уберет версию со страниц ошибок.http { server_tokens off; # ... }
- Добавить директиву
Скрыть
X-Powered-Byот бэкенда- Если Nginx работает как reverse proxy, заголовок
X-Powered-Byможет приходить от приложения. - Его можно убрать с помощью директивы
proxy_hide_headerв блокеlocation, который проксирует запросы.location /api/ { proxy_pass http://backend:3000; # ... proxy_hide_header X-Powered-By; }
- Если Nginx работает как reverse proxy, заголовок
- Заголовки
Дублирование заголовков
- При работе Nginx в качестве обратного прокси (reverse proxy) заголовки
X-Frame-Options,X-Content-Type-OptionsиReferrer-Policyмогут дублироваться (например,nosniff, nosniff). - Заголовок приходит от бэкенд-приложения.
- Nginx добавляет его повторно через директиву
add_header. - Нужно скрыть заголовок, приходящий от бэкенда, а затем добавить свой собственный.
- Для этого используется директива
proxy_hide_header. - В блоке
location, который проксирует запросы на бэкенд, добавить:location / { proxy_pass http://ваш_бэкенд; # Скрываем дублирующиеся заголовки от бэкенда proxy_hide_header X-Frame-Options; proxy_hide_header X-Content-Type-Options; proxy_hide_header Referrer-Policy; # Если CSP или другие заголовки тоже дублируются, скройте и их proxy_hide_header Content-Security-Policy; }
- При работе Nginx в качестве обратного прокси (reverse proxy) заголовки
3 Проверка
3.1 Ручная проверка через командную строку
Используйте
curlдля каждого IP и порта, чтобы увидеть заголовки ответа:# Для HTTP curl -I http://hostname:80 # Для HTTPS curl -I https://hostname:443 -kОжидаемый результат:
HTTP/1.1 200 OK Server: nginx # <--- Скрыли версию X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Referrer-Policy: strict-origin-when-cross-origin Content-Security-Policy: default-src 'self'; ... Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadВ выводе вы должны быть добавленные заголовки (
Strict-Transport-Security,X-Frame-Options,X-Content-Type-Optionsи т.д.).Чтобы проверить, что заголовки добавляются и к ответам с ошибками (например, 403), нужно выполнить запрос к несуществующей странице или специально ограниченному ресурсу и проверить наличие заголовков с параметром
always.
3.2 Использование онлайн-инструментов
3.2.1 SecurityHeaders.com
- Сайт блокирует доступ из России.
- Перейдите на сайт SecurityHeaders.com.
- Введите URL вашего сервера.
- Он выдаст оценку (A+, A, B и т.д.) и покажет, какие заголовки все еще отсутствуют.
3.3 Расширения для броузера
3.3.1 Web Security Headers Checker
- Расширение для Chrome, которое сканирует заголовки и выдает оценку от A+ до F прямо на панели инструментов.
- Chrome: https://chromewebstore.google.com/detail/security-headers/oahaipcejmlamohcchffgpcnfbidaklj
3.4 Скрипт автоматической проверки
- bash-скрипт для проверки всех IP и портов:
#!/bin/bash hosts=("host:80" "host:443" "host:3128") for host in "${hosts[@]}"; do echo "Checking $host" curl -s -I "http://$host" | grep -E "X-XSS-Protection|X-Content-Type-Options|X-Frame-Options|Referrer-Policy|Content-Security-Policy|Strict-Transport-Security|Server|X-Powered-By" echo "---" done
