Безопасная работа с выходными данными: общие вопросы
Изучаем, как работать с выходными данными, избегать ошибок обработки, уязвимости памяти и XSS-атак.
В процессе работы приложение передает результаты своих вычислений во внешние системы или компоненты. Это может быть ответ на сетевой запрос, отображение данных пользователю, запись в базу данных или любая другая операция, предполагающая выдачу результата.
Все принципы, которые мы изучили для передачи данных другим приложениям, применимы и здесь. Например, разработчикам часто приходится санировать, экранировать или нормализовать данные, прежде чем возвращать их пользователю.
Важно: ограничивайте объем информации, которую приложение отображает пользователям, особенно если эти данные касаются системы безопасности.
Несколько важных правил
- Если ваше приложение требует аутентификации пользователя, предоставляйте как можно меньше информации до завершения проверки подлинности. Особенно важно не раскрывать номер версии вашего ПО до аутентификации, за исключением случаев, когда эту информацию легко получить иным способом или существует лишь одна версия приложения.
- При неудачной попытке входа следует регистрировать сам факт неудачи, но не раскрывать его причину. Например, не нужно указывать, что введено неверное имя пользователя, за исключением случаев, когда злоумышленник может легко получить список имен из открытых источников.
- Никогда не сообщайте, что пароль «почти правильный». Пароль может быть только верным или неверным.
- По умолчанию пароли не должны отображаться на экране в открытом виде. Например, в HTML следует использовать тип поля password, который автоматически маскирует вводимые символы (точками или звездочками). Это защищает от подсматривания и предотвращает случайное раскрытие пароля при записи экрана.
- Если требуется отобразить конфиденциальные данные в форме, вынесите их на отдельную страницу или реализуйте кнопку для скрытия/показа этой информации. По умолчанию данные должны быть скрыты. Этот подход также помогает предотвратить подсматривание и случайное раскрытие информации.
- Не отправляйте пользователям комментарии, содержащиеся в коде. Это увеличивает объем передаваемых данных и может привести к утечке информации. При использовании JavaScript минификация кода позволяет решить эту проблему.
Приложение должно быть устойчивым к ситуациям, когда злоумышленник намеренно отказывается принимать передаваемые данные или делает это крайне медленно. В противном случае атакующий может сделать приложение недоступным для других пользователей, просто заблокировав канал вывода. Хотя системы с асинхронной обработкой обычно менее уязвимы, подобная проблема может возникнуть в любой реализации.
Основная цель таких мер — предотвратить возможность парализации работы системы путем создания искусственных задержек в получении данных (например, через намеренно медленное TCP-соединение или «зависший» браузер). Проще говоря, не создавайте условий для легкой организации DoS-атак:
- освобождайте блокировки до отправки ответа,
- устанавливайте таймауты на операции сетевой записи,
- отслеживайте время с начала передачи и прерывайте подозрительно медленные операции.
Старайтесь соблюдать установленные соглашения используемой системы, так как другие компоненты часто зависят от них. В веб-разработке это особенно важно для HTTP-методов: GET и HEAD должны только получать информацию, не внося постоянных изменений. GET-запросы могут выполняться при простом переходе по ссылке, поэтому они не должны приводить к необратимым действиям, например, к покупкам или финансовым операциям. Для таких действий используйте методы POST, PUT или DELETE. Хотя спецификация HTTP не запрещает нарушать эти правила, они стали отраслевым стандартом.
Явно указывайте получателю, как именно следует интерпретировать передаваемые данные. Иначе, если в данных содержится информация от злоумышленника, он может обманом заставить получателя обработать эти данные неправильным образом. Эта проблема особенно часто встречается в веб-приложениях.
- Всегда явно указывайте MIME-тип передаваемых данных. Браузеры могут пытаться автоматически определить тип содержимого, но их предположения могут быть ошибочными.
- Всегда явно указывайте кодировку передаваемых данных. Если браузер вынужден самостоятельно определять кодировку, злоумышленник может сформировать данные так, что браузер ошибется в определении, что создаст уязвимость. При отправке HTML обычно следует указывать кодировку UTF-8. Оптимальный способ — указать кодировку с помощью параметра charset в HTTP-заголовках. Если это невозможно, укажите ее в HTML-заголовке: <meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″>
Также бывает нужно экранировать выходные данные (escape your output), чтобы информацию, которая могла быть сформирована злоумышленником, нельзя было использовать для атаки.
Работа с распространенными уязвимостями веб-приложений. Межсайтовый скриптинг (XSS)
Межсайтовый скриптинг (XSS) — одна из самых распространенных уязвимостей веб-приложений. При XSS-атаке злоумышленник пытается обмануть браузер, заставляя его выполнить вредоносные действия, внедряя данные в легитимную веб-страницу или приложение. Браузер жертвы воспринимает этот код как часть доверенного содержимого и выполняет его. Проблема особенно распространена на страницах и сайтах, где пользователи могут оставлять комментарии, поскольку такой функционал предоставляют злоумышленникам простой способ внедрения вредоносного кода.
При XSS-атаке конечной целью является веб-браузер пользователя. Однако причина уязвимости кроется в некорректной обработке данных на стороне отправителя (сервера). Выделяют три основных типа XSS-атак.
- Постоянный XSS — вредоносные данные сохраняются в базе данных для последующего извлечения. Классический пример: злоумышленник оставляет комментарий, содержащий вредоносный скрипт. Когда другие пользователи просматривают этот комментарий, скрипт выполняется в их браузерах.
- Отраженный XSS — вредоносные данные отправляются браузером жертвы на сервер (обычно внутри URL-адреса) и немедленно возвращаются обратно в браузер.
- DOM-ориентированный XSS — веб-клиент отправляет атакующие данные самому себе, обычно используя информацию, полученную от злоумышленника, и затем взаимодействует с DOM через JavaScript.
Основной способ защиты от XSS — экранировать все выходные данные, которые могут содержать информацию от злоумышленника и не были явно проверены. При правильном применении такая мера полностью нейтрализует атаку, поэтому при выводе ненадежных HTML-данных от злоумышленника всегда применяйте стандартное HTML-экранирование, если нет веских причин поступать иначе.
| Оригинал | Экранированный HTML |
| & | & |
| < | < |
| > | > |
| “ | " |
| ‘ | ' or ' |
При обработке выходных данных часто лучше определить «белый список» и экранировать все остальное. Однако иногда необходимые правила экранирования представляют собой четко определенный набор, который можно применить напрямую — как в случае с HTML, где мы можем безопасно перечислить все специальные символы. Разумеется, эти правила экранирования относятся только к самому HTML и их недостаточно для других форматов данных (таких как встроенные URL-адреса и HTTP-заголовки).
В большинстве случаев оптимальным решением против XSS является выбор фреймворка или библиотеки, автоматически экранирующей HTML-вывод. Система вывода автоматически применяет экранирование (иногда это называют автоэкранированием). Обычно оно реализуется с помощью систем шаблонизации, где вы определяете постоянный шаблон (которому доверяете и который не экранируется) и данные (где информация по умолчанию экранируется).
Существует множество вариантов с такой функциональностью. Например, библиотека React для JavaScript по умолчанию экранирует все значения, встроенные в JSX, перед их отображением. Веб-фреймворк Flask на Python использует библиотеку шаблонов Jinja, которая автоматически экранирует данные при рендеринге HTML-шаблонов. Это далеко не полный перечень — автоэкранирование является стандартной возможностью большинства веб-фреймворков.
Экранирование — хорошая практика, но не всегда применимая. Одна из причин отключить его — если вам нужно разрешить определенные действия, которые экранирование блокирует. Например, если нужно позволить пользователям использовать HTML для выделения текста курсивом, потребуется разрешить теги <i> и </i>, хотя обычно такие символы экранируются.
Проблема в том, что при разрешении любых дополнительных элементов код, необходимый для корректного экранирования вывода, становится значительно сложнее. Нужно убедиться, что каждому открывающему тегу <i> соответствует закрывающий тег, что теги правильно вложены, что они либо не содержат атрибутов, либо эти атрибуты разрешены, и что пропускаются только разрешенные теги и атрибуты. Во многих случаях это приводит к уязвимостям. В итоге, хотя многие фреймворки по умолчанию применяют экранирование, разработчики при отключении этой защиты иногда снимают ее со слишком большого объема данных.
В таких ситуациях следует использовать специальные библиотеки, которые разрешают только нужные элементы, а все остальное экранируют. Большинство популярных языков программирования и фреймворков уже содержат библиотеки, позволяющие указать разрешенные элементы, а остальные — экранировать, удалять или блокировать.
URL-адреса
Встроенные URL-адреса также требуют экранирования, но правила для них отличаются.
Синтаксис URL обычно имеет формат: протокол:[//хост]путь[?запрос][#фрагмент]. Например, в адресе https://www.linuxfoundation.org/about/ протокол — https, хост — www.linuxfoundation.org, путь — /about/, а параметры запроса и фрагмент отсутствуют.
Когда в пути, запросе или фрагменте требуются специальные символы, стандартный способ экранирования будет таким: сначала убедиться, что данные закодированы в UTF-8, а затем заменить все байты, кроме «безопасных» (обычно A-Z, a-z, 0-9, «.», «-», «*», «_»), на последовательность %hh, где hh — шестнадцатеричное представление байта.
Таким образом, оптимальная защита от XSS — использовать фреймворк или библиотеку с автоматическим экранированием вывода. Также идеально дополнить этот подход механизмом, ограничивающим последствия возможных ошибок. Для веб-приложений таким механизмом является политика безопасности контента (Content Security Policy, CSP), о которой мы поговорим позже.
Межсайтовая подделка запроса
Еще один распространенный тип уязвимости веб-сайтов — межсайтовая подделка запроса (Cross-Site Request Forgery, CSRF или XSRF). Хотя сегодня проблема встречается реже, она все еще актуальна. Давайте разберем, как работают такие атаки и как от них защититься.
При CSRF-атаке злоумышленник обманом побуждает пользователя отправить данные на сервер, который воспринимает этот запрос как легитимный и выполняет соответствующие действия.
Например, злоумышленник может создать на своем сайте форму с кнопкой отправки, которая передает данные на целевой сервер. Если пользователь в этот момент авторизован на этом сервере (например, в банковской системе), тот распознает его сессию как действительную и может выполнить операцию, которую пользователь не собирался совершать. Например, перевести деньги на счет злоумышленника.
В некотором смысле CSRF-атака является противоположностью XSS-атаки. XSS использует доверие пользователя к серверу, тогда как CSRF использует доверие сервера к пользователю (убеждая сервер, что запрос был намеренно отправлен пользователем). Говоря проще: XSS обманывает браузеры, а CSRF обманывает серверы.
Современные фреймворки для веб-приложений применяют надежную защиту от CSRF: каждый пользователь получает уникальный секретный токен, который добавляется во все формы и URL-адреса, способные изменять данные. При получении такого запроса сервер проверяет соответствие токена. Поскольку злоумышленник не может узнать этот токен, он не в состоянии подделать корректный запрос.
Современные браузеры также реализуют эффективную защиту от CSRF. Исторически они отправляли cookie при любом обращении к серверу, даже если основной контент загружался с другого сайта. Такой подход часто оказывался избыточным. Во многих случаях cookie не должны передаваться при взаимодействиях, инициированных сторонними сайтами. Но современные браузеры поддерживают атрибут SameSite со значениями Lax или Strict. При их использовании сессионные cookie не отправляются при запросах с посторонних сайтов, что эффективно блокирует CSRF-атаки.
Сейчас уязвимости типа CSRF встречаются все реже — это как раз связано с переходом индустрии на безопасные настройки по умолчанию. Данный пример показывает, что можно снизить вероятность появления целых категорий уязвимостей, если изначально проектировать системы с безопасными базовыми настройками.
Этот же принцип следует применять и в разработке: встраивайте защитные механизмы непосредственно в инструменты, стандарты и архитектуру систем. Что полезно делать:
- автоматически завершайте сеанс пользователя после определенного периода неактивности;
- для особо опасных операций (удаление аккаунта или перевод крупных сумм) требуйте отдельного подтверждения с повторной аутентификацией.
Открытые перенаправления и переадресации
Веб-приложение не должно принимать подконтрольные пользователю данные, содержащие URL-адрес внешнего сайта, и выполнять перенаправление по этому адресу без строгой проверки. Приложение с такой функциональностью содержит уязвимость открытого перенаправления.
Чтобы лучше понять эту уязвимость, рассмотрим пример. Предположим, веб-приложение имеет ссылку /redirect, которая принимает параметр url=, и затем просто перенаправляет запросы на указанное в параметре значение. Это означает, что злоумышленник может создать на любом сайте HTML-файл следующего содержания (пример основан на тексте MITRE о CWE-601):
<a href="https://bank.example.com/redirect?url=https://attacker.example.net">Click here to log in</a>
Пользователь, видя ссылку, подумает, что она ведет на доверенный домен bank.example.com. Технически это правда, ссылка действительно ведет туда. Однако при переходе этот «доверенный» домен незаметно перенаправит пользователя на другой, потенциально опасный сайт. Основная проблема открытых перенаправлений в том, что их можно использовать для обмана людей и создания более убедительных фишинговых атак.
Схожая ситуация со «внутренними перенаправлениями» (forwards). В этом случае веб-приложение перенаправляет запрос в другой раздел того же приложения. Сервер может ошибочно классифицировать такой запрос как внутренний, исходящий от самого приложения, а не от внешнего пользователя, и предоставить ему избыточные привилегии.
В руководстве OWASP по невалидированным перенаправлениям и переадресациям предлагаются следующие меры защиты:
- Лучшее решение — полностью отказаться от использования перенаправлений и переадресаций.
- Если это невозможно, не позволяйте пользователям напрямую указывать URL-адреса для перенаправления.
- Используйте короткие имена, идентификаторы или токены, которые на стороне сервера преобразуются в полные URL-адреса. Этот подход обеспечивает максимальную защиту от подмены URL злоумышленниками. Важно убедиться, что это не создает уязвимость перебора, когда злоумышленник может последовательно подбирать идентификаторы.
- Если необходимо использовать пользовательский ввод, проверяйте корректность значения, его соответствие приложению и правам пользователя. Для фильтрации ввода создавайте белые списки доверенных URL (списки разрешенных хостов или регулярные выражения), а не черные списки.
Непроверенные URL-адреса и подделка запросов на стороне сервера
URL (Uniform Resource Locator) — это способ указания местоположения веб-ресурса. Технически он является разновидностью URI (Uniform Resource Identifier), но для наших целей мы будем использовать эти термины как взаимозаменяемые.
Согласно спецификации IETF RFC 3986, общий синтаксис URI имеет следующий вид:
scheme:[//authority]path[?query][#fragment]
А синтаксис authority (авторитетной части) выглядит следующим образом:
[userinfo@]host[:port]
Иногда пользователи передают данные, которые разработчик планирует использовать в качестве URL (или преобразовать в URL). Однако это опасно. Если вы отобразите URL в данных, предоставляемых пользователю, он может перейти по этой ссылке. URL-адреса обладают широкими возможностями, и злоумышленник может попытаться использовать любую из них. Например:
- URL не обязан использовать схему https: — он может содержать и другие схемы, например, file: (для доступа к локальным файлам) или даже редко встречающиеся схемы вроде gopher:. Распространенный метод атаки — передача одной схемы (например, gopher:) в сервис, ожидающий совершенно другой протокол; злоумышленник может использовать эту несовместимость для проведения атаки.
- «Хост» может быть не тем, что вы ожидаете; он может ссылаться на произвольный другой компьютер или даже на компьютер, отправивший запрос.
- Злоумышленник может предоставить userinfo (имя пользовательской учетной записи) и/или порт. Например, указание порта позволяет URL-адресу запрашивать подключение к любому порту компьютера.
- В любой части URL-адреса могут присутствовать закодированные символы.
Когда сервер обманом побуждают отправить запрос по непроверенному URL-адресу, это называется подделкой запросов на стороне сервера (SSRF).
Основной способ защиты — строго ограничить процесс формирования URL-адресов для запросов. В идеале не следует использовать непроверенные данные для создания URL. Но если это невозможно, нужно максимально ограничить диапазон допустимых URL-адресов, обеспечив возможность создания только безопасных вариантов.
Строки формата и шаблоны
Вывод данных может быть сложной задачей, но большинство языков программирования предоставляют встроенные средства для ее решения: форматирование строк и шаблоны. Они помогают не только упростить работу, но и снизить риск уязвимости. Вспомните: мы уже обсуждали, что одним из наиболее эффективных способов защиты от межсайтового скриптинга (XSS) как раз является использование систем шаблонов.
Однако при использовании форматов вывода, предоставленных пользователями (то есть при применении строк формата и шаблонов из непроверенных источников) следует быть предельно осторожным. Вообще в большинстве случаев не следует разрешать пользователям настраивать форматы вывода в общих системах шаблонов без тщательной предварительной проверки. Некоторые системы форматирования вывода могут выполнять произвольный код или раскрывать информацию за пределами разрешенного набора данных — это совершенно недопустимо. Даже когда выполнение произвольного кода невозможно, такие системы по определению контролируют вывод и могут создавать вводящие в заблуждение результаты или перегружать систему, принимающую эти данные.
Системы форматирования в большинстве языков программирования не столь опасны, но все же могут вызывать проблемы. Оптимальное решение — сделать так, чтобы пользователь не мог задавать строку формата. Но если вы по какой-то причине не можете этого сделать, то хотя бы убедитесь, что используемая система не допускает уязвимостей. Например, поддерживайте только определенные типы форматов и обязательно проверяйте их или подключите библиотеку, специально созданную для безопасной работы с ненадежными шаблонами.
ИСТОРИЯ: Log4Shell / уязвимость в log4j
log4j — это библиотека на Java для ведения журналов событий (логирования). Она широко используется во многих системах, включая Apple iPhones, автомобили Tesla и чат Minecraft. Уязвимость Log4Shell (официально CVE-2021-44228) была обнаружена в версиях log4j 2.x и представляла собой критическую угрозу безопасности. В уязвимых версиях log4j злоумышленник, имеющий возможность контролировать содержимое записей журнала, мог выполнять произвольный код, загружаемый с LDAP-серверов. Это стало возможным из-за функции подстановки значений в сообщениях. Для эксплуатации уязвимости достаточно было добавить в логируемый текст специальную конструкцию.
Примечательно, что в проекте log4j существовало около 8 000 тестов, но ни один из них не выявил эту проблему. Тесты проверяли корректность работы функций, но не учитывали возможность злоупотребления функциональностью. Этот случай демонстрирует опасность предоставления пользователям контроля над обработкой данных. Когда система позволяет выполнять произвольные операции на основе пользовательского ввода, это создает серьезные риски безопасности.
Кэширование данных
Кэширование помогает ускорить обработку запросов, но оно не подходит для работы с особо чувствительными данными. Например, в веб-приложениях такие сведения следует передавать только через специальные страницы, полностью отключая их кэширование на сервере, клиенте и промежуточных прокси. Это предотвращает случайные утечки из кэша и соответствует принципу минимальных привилегий.
Кэширование может происходить на любом уровне системы, но особенно опасно неконтролируемое кэширование конфиденциальных данных на стороне вывода.
На сервере для кэширования обычно используются такие системы, как memcached и Redis. Наиболее надежный способ отключить кэширование в веб-браузере и прокси — задать следующий набор HTTP-заголовков:
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Некоторые из этих параметров актуальны только для очень старых браузеров. Если вас интересуют только современные браузеры, для отключения кэширования достаточно одного HTTP-заголовка:
Cache-Control: no-store, must-revalidate
Также стоит предусмотреть дополнительные проверки, чтобы убедиться, что любые кэшированные данные передаются только тем, кто имеет право их получать.
История: уязвимости системы кэширования в ChatGPT в 2023 году
В 2023 году в ChatGPT были обнаружены две серьезные проблемы, связанные с кэшированием данных (отчет «OpenAI Reveals Redis Bug Behind ChatGPT User Data Exposure Incident» за авторством Ravie Lakshmanan).
- Ошибка в библиотеке Redis приводила к утечке персональных данных пользователей, в том числе их имен, адресов электронной почты, платежных реквизитов и даже частичной информации о банковских картах. При определенных условиях отмененные запросы вызывали нарушение целостности соединений, в результате чего один пользователь мог видеть данные другого.
- Отдельная уязвимость механизма кэширования позволяла злоумышленнику получить полный контроль над учетной записью жертвы. Для этого создавалась специальная ссылка, приводящая к сохранению токена доступа в кэше CDN, что открывало доступ к истории чатов и платежной информации пользователя.
Работа с механизмами безопасности. Политика безопасности контента (CSP)
При создании веб-приложений крайне полезным инструментом для ограничения ущерба является политика безопасности контента (CSP). Она сообщает браузеру, какие действия разрешены, а какие запрещены с точки зрения безопасности.
CSP сама по себе не предотвращает большинство атак, но может значительно усложнить эксплуатацию уязвимостей и снизить потенциальный вред. Как правило, политика безопасности контента (CSP) передается через HTTP-заголовок Content-Security-Policy. Как и все HTTP-заголовки, браузер получает его до загрузки основного содержимого страницы. Также CSP можно задать через HTML-элемент <meta> с атрибутом http-equiv=”Content-Security-Policy”. Однако использование элемента <meta> менее предпочтительно, чем HTTP-заголовок, поскольку в этот момент система уже начала обработку HTML. Поэтому старайтесь применять именно HTTP-заголовок. Если же приходится применять HTML-элемент, следует разместить этот тег <meta> как можно раньше в документе, чтобы политика вступила в силу максимально быстро.
Вероятно, важнейшая возможность CSP — управление выполнением скриптов. По умолчанию браузер выполняет все полученные скрипты, что создает критическую угрозу при наличии XSS-уязвимости: злоумышленник может внедрить вредоносный код, который будет автоматически выполнен в браузере пользователя. Более безопасный подход, разделение кода и данных с ограничением привилегий, реализуется с помощью CSP.
Вот пример простой политики CSP, которая предотвращает множество атак. Данная политика предписывает браузеру загружать ресурсы (в частности, скрипты и стили) только с исходного сайта (‘self’) и запрещает выполнение встроенных скриптов и стилей, а также использование функции eval (поскольку эти возможности не были явно разрешены):
Content-Security-Policy: default-src 'self';
Основная сложность при использовании такой CSP заключается в необходимости полного отказа от встроенных стилей и скриптов. HTML-документ может подключать внешние файлы JavaScript и CSS, но сами скрипты и стили должны располагаться в отдельных файлах. Хотя HTML может содержать важные для скриптов и стилей данные (такие как теги, классы и идентификаторы), прямое встраивание кода и стилей в разметку становится невозможным.
Полный отказ от встроенных скриптов и стилей – это не просто хорошая практика, но и мощное средство безопасности. Если браузер игнорирует встроенные скрипты и стили, XSS-атака, компрометирующая HTML-страницу, не сможет легко внедрить и выполнить код или применить вредоносные стили. Это не предотвращает все атаки, но блокирует многие из них и значительно усложняет эксплуатацию остальных. Если вам удастся полностью исключить встроенные скрипты и стили и закрепить это требование в CSP, система станет значительно устойчивее к широкому спектру атак.
Спецификация CSP предлагает множество опций, которые позволяют разрешать дополнительные операции и ограничивать другие.
Руководствуйтесь принципом наименьших привилегий: старайтесь делать политику CSP максимально строгой. Наибольший эффект достигается при адаптации системы под использование более строгой CSP на всех страницах, но даже частичные ограничения приносят пользу. Вот часто используемые способы ослабления ограничений для скриптов и стилей.
Применяйте строгую политику безопасности контента (CSP) на как можно большем количестве веб-страниц.
Например, разрешайте выполнение скриптов и применение стилей только из определенных источников вашего сайта, полностью исключая встроенные скрипты и стили. На тех страницах, где это пока невозможно, допускается ослабить ограничения.
Разрешите загрузку скриптов и стилей с определенных внешних сайтов.
Для этого в директиве default-src (указывающей источники загрузки скриптов) можно перечислить конкретные доверенные сайты. Такая настройка сообщает браузеру о вашем полном доверии к этим ресурсам. Однако следует соблюдать осторожность: это может затронуть конфиденциальность пользователей. Например, организации, поддерживающие эти внешние сайты, будут получать данные о каждом обращении к ресурсам, включая IP-адреса пользователей.
Разрешите использование конкретных хеш-значений.
Вы можете настроить директиву default-src, разрешив выполнение конкретного встроенного скрипта, указав его криптографический хеш в формате <алгоритм>-<base64-значение>. Это может быть полезным промежуточным шагом, если у вас есть существующие встроенные скрипты, хотя в долгосрочной перспективе лучше перенести эти скрипты в отдельный файл.
В CSP существуют и другие механизмы ограничения привилегий. Особенно важным параметром является frame-ancestors, который служит мощным средством защиты от кликджекинга.
Кликджекинг — это атака, при которой злоумышленник «перехватывает» клик, предназначенный пользователем для одной цели, и перенаправляет его для выполнения другой операции. Обычно это реализуется через злоупотребление возможностями HTML-фреймов. Если вы не используете фреймы , просто добавьте в политику директиву frame-ancestors ‘none’. Если же фреймы используются, укажите frame-ancestors ‘self’.
Политика одинакового источника и общий доступ к ресурсам разных источников
Когда веб-браузер загружает HTML-файл, он разрешает этому файлу ссылаться на изображения, видео, CSS-стили и скрипты. Обычно браузер загружает и использует эти ресурсы независимо от их источника.
Однако когда браузер загружает и выполняет скрипт (например, JavaScript), опасно разрешать этому скрипту свободно взаимодействовать с другими сайтами. Иначе вредоносный скрипт может тайно передавать конфиденциальные данные на другие сайты или атаковать их (например, используя уязвимости или осуществляя DDoS-атаки).
Чтобы предотвратить множество проблем безопасности, веб-браузеры применяют к клиентским JavaScript-программам правила политики одинакового источника (Same-Origin Policy). Согласно этой политике, клиентские JavaScript-программы могут взаимодействовать только с ресурсами того же источника. Источник URL состоит из схемы (обычно https), порта (по умолчанию 443 для https) и хоста. Например, адреса https://example.com/foo и https://example.com/bar имеют одинаковый источник, поскольку их комбинация (https, 443, example.com) совпадает.
Политика одинакового источника эффективно блокирует многие угрозы безопасности, но в некоторых случаях оказывается чрезмерно ограничительной. Поэтому веб-сайт может целенаправленно разрешить взаимодействие с JavaScript из других источников, используя механизм CORS (Cross-Origin Resource Sharing). Хотя CORS полезен для ослабления ограничений политики одинакового источника, его некорректная настройка может создавать уязвимости.
CORS позволяет веб-серверам указывать, каким сторонним источникам разрешен доступ к определенным ресурсам (URL) и какие HTTP-методы (GET, POST, DELETE и др.) им разрешены. Браузеры запрашивают эти правила и используют их для определения возможности выполнения кросс-доменных запросов клиентскими JavaScript-программами. Обмен этой информацией происходит через специальные HTTP-заголовки. Запросы CORS используются, среди прочего, для междоменных XMLHttpRequest и Fetch-запросов.
Существует два типа CORS-запросов:
- простые (simple)
- предварительные (preflighted).
Простые запросы выполняются за один этап, а предварительные требуют двух. Любой CORS-запрос, который не может быть выполнен как простой, автоматически обрабатывается браузером как предварительный. Поскольку предварительные запросы имеют большую задержку, рекомендуется разрабатывать клиентский код так, чтобы он использовал простые CORS-запросы там, где это возможно.
Простой CORS-запрос используется, когда выполняются все следующие условия
- Запрашиваемый метод — GET, HEAD или POST.
- Заголовки запроса ограничены браузером (так называемым пользовательским агентом), с возможным добавлением безопасных для CORS заголовков. Примерами таких безопасных заголовков являются Accept, Accept-Language, Content-Language, Viewport-Width и Width.
- Заголовок Content-Type имеет одно из значений: application/x-www-form-urlencoded, multipart/form-data или text/plain.
- Выполняются некоторые другие требования. Подробности см. в спецификации; в большинстве случаев эти дополнительные требования выполняются автоматически.
При выполнении простого CORS-запроса браузер отправляет стандартный HTTP-запрос, добавляя к нему заголовок Origin с указанием источника скрипта. Веб-сервер проверяет допустимость запроса и в ответе устанавливает заголовок Access-Control-Allow-Origin, указывая разрешенные источники. Если значением является «*», доступ разрешен для всех источников. Браузер сверяет полученное значение Access-Control-Allow-Origin с исходным доменом, и при совпадении скрипт получает запрошенные данные.
Предварительный запрос, в отличие от простого, включает дополнительный этап. Сначала браузер отправляет запрос OPTIONS с заголовком Origin и другой информацией, чтобы проверить безопасность основного запроса. Если сервер подтверждает возможность выполнения, отправляется основной запрос.
По умолчанию браузеры не отправляют учетные данные (cookies и данные HTTP-аутентификации) в CORS-запросах. Однако специальный флаг в XMLHttpRequest или конструкторе Request позволяет включить их отправку. В этом случае сервер должен вернуть заголовок Access-Control-Allow-Credentials: true, иначе JavaScript-программа не получит результаты. Серверы должны крайне осторожно использовать эту возможность. Если она применяется, необходимо строго ограничивать разрешенные источники. Намного безопаснее вообще не использовать Access-Control-Allow-Credentials, так как это разрешает доступ к защищенным данным со сторонних сайтов.
Если определенные данные на вашем веб-сервере являются публичными и статичными (не зависят от пользователя или источника запроса), рекомендуется настроить для них заголовок Access-Control-Allow-Origin: * при обработке GET-запросов. Это позволяет клиентским JavaScript-приложениям напрямую получать и использовать эти данные. Такой подход, однако, означает, что злоумышленникам будет проще организовать DDoS-атаку на этот ресурс, поскольку он становится доступен для массовых запросов. Тем не менее, для многих сайтов распространение информации является приоритетной задачей, а защиту от DDoS можно обеспечить другими способами.
Иногда содержимое ответа может зависеть от источника запроса (это происходит, если для Access-Control-Allow-Origin задано любое значение кроме «*»). В таких случаях обязательно добавляйте заголовок Vary: Origin. Это указывает браузеру, что результат может различаться для разных источников, предотвращая утечку данных между ними через CORS.
Подробные инструкции по настройке CORS для различных сценариев доступны на сайте enable-cors.org.