Подделка межсайтовых запросов (CSRF)

Spring предусматривает комплексные средства поддержки для защиты от подделки межсайтовых запросов (CSRF). В следующих разделах мы рассмотрим:

  • >Что такое CSRF-атака?

  • Защита от CSRF-атак

  • Рекомендации по поводу CSRF

Что такое CSRF-атака?

Лучше всего можно понять, что такое CSRF-атака, рассмотрев конкретный пример.

Предположим, что на сайте вашего банка есть форма, позволяющая перевести деньги от текущего пользователя, вошедшего в систему, на другой банковский счет. Например, форма перевода может выглядеть следующим образом:

Форма перевода
<form method="post"
	action="/transfer">
<input type="text"
	name="amount"/>
<input type="text"
	name="routingNumber"/>
<input type="text"
	name="account"/>
<input type="submit"
	value="Transfer"/>
</form>

Соответствующий HTTP-запрос может выглядеть следующим образом:

Передача HTTP-запроса
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876

Теперь представьте, что вы авторизовались на сайте своего банка, а затем, не выходя из системы, посетили сайт злоумышленников. Веб-сайт злоумышленников содержит HTML-страницу со следующей формой:

Форма перевода злоумышленников
<form method="post"
	action="https://bank.example.com/transfer">
<input type="hidden"
	name="amount"
	value="100.00"/>
<input type="hidden"
	name="routingNumber"
	value="evilsRoutingNumber"/>
<input type="hidden"
	name="account"
	value="evilsAccountNumber"/>
<input type="submit"
	value="Win Money!"/>
</form>

Вам нравится выигрывать деньги, поэтому вы нажимаете на кнопку отправки. При этом вы непреднамеренно передали 100 долларов злоумышленнику. Это происходит потому, что, хотя сайт злоумышленников и не видит ваши файлы cookie, файлы cookie, связанные с вашим банком, все равно отправляются вместе с запросом.

Хуже всего то, что весь этот процесс можно было бы автоматизировать с помощью JavaScript. Это означает, что вам даже не нужно было нажимать на кнопку. Более того, это может так же легко произойти при посещении добросовестного сайта, который стал жертвой XSS-атаки. Как же защитить пользователей от таких атак?

Защита от CSRF-атак

Причина, по которой возможно осуществлять CSRF-атаки, заключается в том, что HTTP-запрос с сайта жертвы и запрос с сайта злоумышленника абсолютно одинаковы. Это значит, что нет возможности отклонять запросы, поступающие с сайта злоумышленников, и разрешать запросы, поступающие с сайта банка. Для защиты от CSRF-атак нам нужно убедиться, что в запросе есть что-то, что сайт злоумышленников неспособен передавать, чтобы мы могли отличить эти два запроса.

Spring предусматривает два механизма для защиты от CSRF-атак:

  • Шаблон токена синхронизатора

  • Задание атрибута SameSite в файле cookie в рамках сессии

Оба вариант защиты требуют, чтобы безопасные методы были идемпотентными

Безопасные методы должны быть идемпотентными

Для того чтобы сработал любой из вариантов защиты от CSRF, приложение должно удостовериться, что "безопасные" HTTP-методы являются идемпотентными. Это значит, что запросы с HTTP-методом GET, HEAD, OPTIONS и TRACE не должны изменять состояние приложения.

Шаблон токена синхронизатора

Преобладающим и наиболее полным способом защиты от CSRF-атак является использование шаблона синхронизатора токенов. Это решение заключается в том, что каждый HTTP-запрос будет требовать, чтобы в дополнение к нашему сеансовому файлу cookie в HTTP-запросе присутствовало безопасное случайно сгенерированное значение, называемое CSRF-токеном.

После отправки HTTP-запроса, сервер должен осуществить поиск предполагаемого CSRF-токена и сравнить его с фактическим CSRF-токеном в HTTP-запросе. Если значения не совпадут, HTTP-запрос должен быть отклонен.

Ключевым моментом в этой работе является то, что фактический CSRF-токен должен находиться в той части HTTP-запроса, которая не добавляется браузером автоматически. Например, требование ввести фактический CSRF-токен в HTTP-параметр или HTTP-заголовок защитит от CSRF-атак. Запрос фактического CSRF-токена в файле cookie не сработает, поскольку файлы cookie автоматически добавляются браузером в HTTP-запрос.

Мы можем сделать ожидаемые события менее строгими и требовать только фактический CSRF-токен для каждого HTTP-запроса, который обновляет состояние приложения. Чтобы это сработало, наше приложение должно удостовериться, что безопасные HTTP-методы являются идемпотентными. Это повысит удобство использования, поскольку нам требуется разрешить ссылки на наш сайт с помощью ссылок с внешних сайтов. Кроме того, нам не нужно добавлять случайный токе в HTTP-метод GET, поскольку это может привести к утечке токенов.

Давайте посмотрим, как изменится наш пример при использовании шаблона токена синхронизатора. Предположим, что фактический CSRF-токен должен находиться в HTTP-параметре с именем _csrf. Форма перевода в нашем приложении будет выглядеть следующим образом:

Форма токена синхронизатора
<form method="post"
	action="/transfer">
<input type="hidden"
	name="_csrf"
	value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
	name="amount"/>
<input type="text"
	name="routingNumber"/>
<input type="hidden"
	name="account"/>
<input type="submit"
	value="Transfer"/>
</form>

Теперь форма содержит скрытые входные данные со значением CSRF-токена. Внешние сайты не смогут прочитать CSRF-токен, поскольку та же политика по поводу источников гарантирует, что сайт злоумышленников не сможет прочитать ответ.

Соответствующий HTTP-запрос на перевод денег будет выглядеть следующим образом:

Запрос токена синхронизатора
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

Вы можете заметить, что HTTP-запрос теперь содержит параметр _csrf с безопасным случайным значением. Сайт злоумышленников не сможет передать правильное значение для параметра _csrf (которое должно быть явным образом передано на сайте злоумышленников), а перевод завершится ошибкой, когда сервер сравнит фактический CSRF-токен с ожидаемым CSRF-токеном.

Атрибут SameSite

Находящийся в стадии становления способ защиты от CSRF-атак заключается в указании атрибута SameSite для файлов cookie. Сервер может указать атрибут SameSite при настройке cookie, чтобы обозначить, что cookie не следует отправлять при поступлении с внешних сайтов.

Spring Security не управляет напрямую созданием cookie для сессии, поэтому он не предусматривает поддержку атрибута SameSite. Spring Session предусматривает поддержку атрибута SameSite в приложениях на основе сервлетов. CookieWebSessionIdResolver в Spring Framework обеспечивает поддержку атрибута SameSite в приложениях на базе WebFlux.

Пример HTTP-заголовка ответа с атрибутом SameSite может выглядеть следующим образом:

HTTP-ответ с атрибутом SameSite
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax

Допустимыми значениями для атрибута SameSite являются:

  • Strict – при указании этого параметра любой запрос, приходящий с того же сайта, будет включать cookie. В противном случае cookie не будет включен в HTTP-запрос.

  • Lax – если заданные cookies будут отправлены при поступлении с того же сайта или если запрос поступает из навигации на верхнем уровне, а метод является идемпотентным. В противном случае файл cookie не будет добавлен в HTTP-запрос.

Давайте посмотрим, как наш пример можно защитить с помощью атрибута SameSite. Банковское приложение может защититься от CSRF-атак, задав атрибут SameSite в файле cookie для сессии.

При установке атрибута SameSite для нашего сессионного файла cookie браузер будет продолжать отправлять файл cookie JSESSIONID с запросами, поступающими с банковского сайта. Однако браузер больше не будет отправлять файл cookie JSESSIONID с запросом на передачу данных, поступающим с веб-сайта злоумышленников. Поскольку сессия больше не значится в запросе на перевод, поступающем с сайта злоумышленников, приложение будет защищено от CSRF-атаки.

Существует несколько важных нюансов, о которых следует знать при использовании атрибута SameSite для защиты от CSRF-атак.

Установка атрибута SameSite в значение Strict обеспечивает более надежную защиту, но может запутать пользователей. Рассмотрим пользователя, который заходит на сайт социальной сети, расположенный по адресу social.example.com. Пользователь получает электронное письмо на адрес email.example.org, в котором содержится ссылка на сайт социальной сети. Если пользователь нажимает на ссылку, он вполне обоснованно ожидает, что будет аутентифицирован на сайте социальной сети. Однако если атрибут SameSite имеет значение Strict, файл cookie не будет отправлен, а пользователь не будет аутентифицирован.

Мы могли бы улучшить защиту и удобство использования механизма защиты от CSRF-атак с использованием SameSite, реализовав gh-7537.

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

По этой причине обычно рекомендуется использовать атрибут SameSite в качестве глубокоэшелонированной защиты, а не как единственный механизм защиты от CSRF-атак.

Случаи использования защиты от CSRF

Когда следует использовать защиту от CSRF? Мы рекомендуем использовать защиту CSRF для любого запроса, который может быть обработан браузером, используемым обычными пользователями. Если вы создаете сервис, который будут использовать исключительно небраузерные клиенты, то, скорее всего, вам понадобится отключить защиту от CSRF.

Защита от CSRF и формат JSON

Частый вопрос: "Нужно ли мне защищать JSON-запросы, выполняемые javascript?". Краткий ответ: "Зависит от ситуации". Однако следует проявлять бдительность, поскольку существуют CSRF-эксплойты, которые могут повлиять на JSON-запросы. Например, злоумышленник может осуществить CSRF-атаку с помощью JSON, используя следующую форму:

CSRF-атака с помощью JSON-формы
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
	<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
	<input type="submit"
		value="Win Money!"/>
</form>

В результате будет получена следующая структура JSON

CSRF-атака с помощью JSON-запроса
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}

Если бы приложение не валидировало Content-Type, то оно было бы подвержено этому эксплойту. В зависимости от конфигурации, приложение Spring MVC, которое проверяет Content-Type, все еще может быть подвергнуто действию эксплойта путем обновления суффикса URL-адреса таким образом, чтобы он заканчивался на .json, как показано ниже:

CSRF-атака при помощи JSON-формы в Spring MVC
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
	<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
	<input type="submit"
		value="Win Money!"/>
</form>

CSRF и приложения для браузеров, не сохраняющие состояние

Что делать, если мое приложение не сохраняет состояние? Данные факт не говорит о том, что вы отлично защищены. Фактически, если пользователю не требуется выполнять какие-либо действия в веб-браузере для данного запроса, он, скорее всего, все равно будет уязвим для CSRF-атак.

Например, рассмотрим приложение, которое использует для аутентификации вместо JSESSIONID кастомный cookie, содержащий все состояние внутри него. При осуществлении CSRF-атаки кастомный файл cookie будет отправлен вместе с запросом тем же образом, что и файл cookie JSESSIONID из нашего предыдущего примера. Это приложение будет уязвимо для CSRF-атак.

Приложения, использующие базовую аутентификацию, также уязвимы к CSRF-атакам. Приложение уязвимо, поскольку браузер будет автоматически включать имя пользователя и пароль в любые запросы тем же способом, каким в нашем предыдущем примере был отправлен файл cookie JSESSIONID.

Рекомендации по поводу CSRF

При реализации защиты от CSRF-атак необходимо учитывать несколько особых нюансов.

Вход в систему

Для защиты от подделки запросов на вход в систему HTTP-запрос на вход должен быть защищен от CSRF-атак. Защита от подделки запросов на вход в систему необходима для того, чтобы злоумышленник не смог считать конфиденциальную информацию жертвы. Атака осуществляется следующим образом:

  • Пользователь-злоумышленник выполняет вход в систему посредством CSRF, используя учетные данные пользователя-злоумышленника. После этого жертва аутентифицируется как пользователь-злоумышленник.

  • Пользователь-злоумышленник обманом заставляет жертву посетить взломанный сайт и ввести конфиденциальную информацию.

  • Информация привязывается к учетной записи пользователя-злоумышленника, после чего он может войти в систему со своими учетными данными и просмотреть конфиденциальную информацию жертвы.

Возможное осложнение в обеспечении защиты HTTP-запросов на вход в систему от CSRF-атак заключается в том, что пользователь может столкнуться с временем ожидания сессии, в результате чего запрос будет отклонен. Время ожидания сессии вызовет удивление у пользователей, которые не ожидали, что для входа в систему им понадобится сессия.

Выход из системы

Для защиты от подделки запросов на выход из системы, HTTP-запрос на выход из системы должен быть защищен от CSRF-атак. Защита от подделки запросов на выход из системы необходима для того, чтобы злоумышленник не смог считать конфиденциальную информацию жертвы. Более подробную информацию об атаке можно найти в этом блоге.

Возможное осложнение в обеспечении защиты HTTP-запросов на выход из системы от CSRF-атак заключается в том, что пользователь может столкнуться с временем ожидания сессии, в результате чего запрос будет отклонен. Время ожидания сессии вызовет удивление у пользователей, которые не ожидали, что для выхода из системы им понадобится сессия.

CSRF и время ожидания сессий

Чаще всего ожидаемый CSRF-токен хранится в сессии. Это значит, что как только сессия истечет, сервер не найдет ожидаемый CSRF-токен и отклонит HTTP-запрос. Существует несколько вариантов решения проблемы с временем ожидания, каждый из которых имеет свои недостатки.

  • Лучший способ смягчить последствия времени ожидания – использовать JavaScript для запроса CSRF-токена при отправке формы. Затем форма обновляется с помощью CSRF-токена и отправляется.

  • Другим вариантом является использование JavaScript, который сообщает пользователю, что срок действия его сессии истекает. Пользователь может нажать кнопку, чтобы продолжить и обновить сессию.

  • Наконец, ожидаемый CSRF-токен может храниться в файле cookie. Это позволяет ожидаемому CSRF-токену пережить сессию.

    Кто-то может спросить, почему ожидаемый CSRF-токен не хранится в cookie по умолчанию. Это связано с известными эксплойтами, в которых заголовки (например, для указания cookies) могут устанавливаться другим доменом. По этой же причине Ruby on Rails больше не пропускает проверки CSRF при наличии заголовка X-Requested-With. Подробности о том, как выполнить эксплойт, смотрите в этой ветке webappsec.org. Другим недостатком является то, что, удалив состояние (то есть время ожидания), вы теряете возможность принудительно объявить недействительным токен, если он был скомпрометирован.

Многокомпонентность (загрузка файлов)

Защита многокомпонентных запросов (загрузка файлов) от CSRF-атак приводит к проблеме курицы и яйца. Для предотвращения CSRF-атаки необходимо прочитать тело HTTP-запроса, чтобы получить фактический CSRF-токен. Однако чтение тела означает, что файл будет загружен, а это в свою очередь значит, что внешний сайт сможет загрузить любой файл.

Существует два варианта использования защиты от CSRF с помощью multipart/form-data. Каждый вариант имеет свои компромиссы.

  • Размещение CSRF-токена в теле

  • Размещение CSRF-токена в URL-адресе

Прежде чем интегрировать CSRF-защиту Spring Security с загрузкой многокомпонентных файлов, сначала убедитесь, что сможете загружать файлы без CSRF-защиты.

Размещение CSRF-токена в теле

Первый вариант – добавить фактический CSRF-токен в тело запроса. Если поместить CSRF-токен в тело, тело будет прочитано до того, как будет выполнена авторизация. Это означает, что любой сможет размещать временные файлы на вашем сервере. Однако только авторизованные пользователи смогут отправить файл, который будет обработан вашим приложением. В целом, это рекомендуемый подход, поскольку загрузка временного файла должна оказывать незначительное влияние на большинство серверов.

Добавление CSRF-токена в URL-адрес

Если возможность загрузки временных файлов неавторизованными пользователями неприемлема, альтернативой может быть добавление ожидаемого CSRF-токена в качестве параметра запроса в атрибут действия в форме. Недостатком этого подхода является возможность утечки параметров запроса. В целом, наиболее оптимальным способом считается размещение конфиденциальных данных в теле или заголовках, что позволяет исключить их утечку. Дополнительную информацию можно найти в разделе 15.1.3 "RFC 2616: Кодирование конфиденциальной информации в URI".

HiddenHttpMethodFilter

В некоторых приложениях параметр формы может быть использован для переопределения HTTP-метода. Например, приведенная ниже форма может быть использована для обработки HTTP-метода как delete, а не post.

Скрытая от CSRF форма HTTP-метода
<form action="/process"
	method="post">
	<!-- ... -->
	<input type="hidden"
		name="_method"
		value="delete"/>
</form>

Переопределение HTTP-метода происходит в фильтре. Этот фильтр должен быть установлен перед средствами поддержки Spring Security. Обратите внимание, что переопределение происходит только в post, так что это вряд ли вызовет какие-либо реальные проблемы. Тем не менее, наиболее оптимальным является его размещение перед фильтрами Spring Security.

Безопасные заголовки HTTP-ответов

Существует множество заголовков для HTTP-ответов, которые можно использовать для повышения безопасности веб-приложений. Этот раздел посвящен различным заголовкам для HTTP-ответов, для которых в Spring Security предусмотрена явная поддержка. При необходимости Spring Security также можно сконфигурировать на использование кастомных заголовков.

Заголовки безопасности по умолчанию

Spring Security предусматривает стандартный набор связанных с безопасностью заголовков для HTTP-ответов, предназначенных для обеспечения безопасных параметров по умолчанию.

По умолчанию Spring Security содержит следующие заголовки:

Безопасные заголовки для HTTP-ответов по умолчанию
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security is only added on HTTPS requests

Если параметры по умолчанию не соответствуют вашим потребностям, вы можете легко удалить, изменить или добавить заголовки из этих параметров по умолчанию. Для получения дополнительной информации о каждом из этих заголовков обратитесь к соответствующим разделам:

  • Управление кэшем

  • Параметры типа содержимого

  • HTTP Strict Transport Security

  • Заголовок X-Frame-Options

  • Заголовок X-XSS-Protection

Управление кэшем

По умолчанию Spring Security отключает кэширование для защиты пользовательского содержимого.

Если пользователь авторизуется для просмотра конфиденциальной информации, а затем выходит из системы, нам нужно избежать ситуации, когда злоумышленник сможет нажать кнопку "Назад" и просмотреть конфиденциальную информацию. По умолчанию отправляются следующие заголовки управления кэшем:

Заголовки HTTP-ответов с управлением кэша по умолчанию
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0

Чтобы обеспечить защиту по умолчанию, Spring Security добавляет эти заголовки по умолчанию. Однако, если ваше приложение предусматривает свои собственные заголовки управления кэшем, Spring Security не будет применять свои. Это позволяет приложениям обеспечить кэширование статических ресурсов, таких как CSS и JavaScript.

Параметры типа содержимого

Исторически браузеры, включая Internet Explorer, предпринимали попытки угадать тип содержимого запроса с помощью сниффинга содержимого. Это позволило браузерам улучшить взаимодействие с пользователем путем угадывания типа содержимого для ресурсов, в которых тип содержимого не был задан. Например, если браузер примет файл JavaScript, для которого не задан тип содержимого, он сам сможет определить тип содержимого и запустить его.

В случае если разрешена загрузка содержимого, есть еще множество дополнительных мер, которые следует предпринять (например, отображать документ только в отдельном домене, убедиться, что установлен заголовок Content-Type, подвергнуть документ правке и т. д.). Однако эти меры выходят за рамки того, что предусматривает Spring Security. Также важно отметить, что при отключении сниффинга содержимого необходимо задать тип содержимого, чтобы все работало надлежащим образом.

Проблема со снифингом содержимого заключалась в том, что это позволяло злоумышленникам использовать полиглоты (т.е. файл, который допустимо использовать в качестве нескольких типов содержимого) для проведения XSS-атак. Например, некоторые сайты могут позволять пользователям отправить на сайт и просмотреть допустимый postscript-документ. Пользователь-злоумышленник может создать postscript-документ, который также является допустимым JavaScript-файлом, и осуществить с его помощью XSS-атаку.

В Spring Security сниффинг содержимого отключен по умолчанию путем добавления следующего заголовка к HTTP-ответам:

nosniff Заголовок ответа HTTP
X-Content-Type-Options: nosniff

HTTP Strict Transport Security (HSTS)

Когда вы набираете веб-сайт своего банка, то вводите mybank.example.com или mybank.example.com? Если пропускать протокол https, то это создаст потенциальную уязвимость к атакам типа "человек посередине" (атака посредника). Даже если сайт выполняет переадресацию на mybank.example.com, злоумышленник может перехватить первоначальный HTTP-запрос и манипулировать ответом (например, переадресовать на mibank.example.com и украсть учетные данные).

Многие пользователи пропускают протокол https, поэтому был создан протокол HTTP Strict Transport Security (HSTS). После добавления mybank.example.com в качестве HSTS-хоста браузер может заранее понимать, что любой запрос к mybank.example.com должен быть интерпретирован как mybank.example.com. Это значительно снижает вероятность атаки типа "человек посередине".

В соответствии с RFC6797, HSTS-заголовок внедряется только в HTTPS-ответы. Для того чтобы браузер подтвердил заголовок, он должен сначала доверять центру сертификации, подписавшему SSL-сертификат, используемый для создания соединения (а не только SSL-сертификату).

Одним из способов пометить сайт как HSTS-хост является предварительная загрузка хоста в браузер. Второй способ – добавить в ответ заголовок Strict-Transport-Security. Например, по умолчанию Spring Security добавляет следующий заголовок, который предписывает браузеру считать домен HSTS-хостом в течение года (в году примерно 31536000 секунд):

Заголовок HTTP-ответа по спецификации Strict Transport Security
Strict-Transport-Security: max-age=31536000 ; includeSubDomains ; preload

Необязательная директива includeSubDomains сообщает браузеру, что поддомены (например, secure.mybank.example.com) также должны считаться HSTS-доменами.

Необязательная директива preload сообщает браузеру, что домен следует предварительно загружать в браузер как HSTS-домен. Более подробную информацию о предварительной загрузке HSTS можно найти на сайте hstspreload.org.

HTTP Public Key Pinning (HPKP)

В целях обеспечения пассивной работы Spring Security все еще предусматривает поддержку HPKP в сервлетных окружениях, но по причинам, перечисленным выше, команда по безопасности больше не может рекомендовать к использованию HPKP.

HTTP Public Key Pinning (HPKP) указывает веб-клиенту, какой открытый ключ использовать с определенным веб-сервером для предотвращения атак типа "человек посередине" (MITM) с поддельными сертификатами. При правильном использовании HPKP может добавлять дополнительные уровни защиты от скомпрометированных сертификатов. Однако из-за сложности HPKP многие специалисты больше не рекомендуют его использовать, а Chrome даже прекратил его поддерживать.

Для получения дополнительной информации о том, почему HPKP больше не рекомендуется использовать, читайте в статьях "Is HTTP Public Key Pinning Dead?" и "I’m giving up on HPKP".

Заголовок X-Frame-Options

Разрешение добавлять ваш сайт во фрейм может стать проблемой с точки зрения безопасности. Например, с помощью хитроумной CSS-стилизации можно обманом заставить пользователей нажать туда, куда они нажимать не собирались. Например, пользователь, вошедший в свой интернет-банк, может нажать кнопку, которая предоставит доступ другим пользователям. Этот вид атаки известен как кликджекинг.

Еще один современный подход к борьбе с кликджекингом – использование спецификации Content Security Policy (CSP).

Существует способов противостоять атакам типа кликджекинг. Например, для защиты устаревших браузеров от атак типа кликджекинг можно использовать код отключения фреймов. Хотя он и не идеален, код отключения фреймов – это лучшее, что можно предпринять в случае с устаревшими браузерами.

Более современный подход к решению проблемы кликджекинга заключается в использовании заголовка X-Frame-Options. По умолчанию Spring Security запрещает виузализировать страницы во встроенном фрейме (iframe) при использовании следующего заголовка:

X-Frame-Options: DENY

Заголовок X-XSS-Protection

Некоторые браузеры имеют встроенную поддержку для фильтрации отраженных XSS-атак. Это ни в коем случае не может считаться надежной защитой, но помогает при защите от XSS-атак.

Фильтрация чаще всего активирована по умолчанию, поэтому добавление заголовка обычно просто обеспечивает ее активацию и сообщает браузеру, что нужно делать при обнаружении XSS-атаки. Например, фильтр может попытаться изменить содержимое наименее агрессивным способом, чтобы визуализировать всё необходимое. Иногда такая замена может сама по себе стать XSS-уязвимостью. Вместо этого лучше заблокировать содержимое, а не пытаться его исправить. По умолчанию Spring Security блокирует содержимое, используя следующий заголовок:

X-XSS-Protection: 1; mode=block

Спецификация Content Security Policy (CSP)

Content Security Policy (CSP) – это механизм, который веб-приложения могут использовать для снижения риска возникновения уязвимостей к внедрению содержимого, таких как межсайтовый скриптинг (XSS). CSP – это декларативная политика, которая предоставляет авторам веб-приложений возможность объявлять и в конечном итоге сообщать клиенту (пользовательскому агенту) об источниках, из которых веб-приложение ожидает загрузку ресурсов.

Content Security Policy не решит все проблемы, связанные с уязвимостями к внедрению содержимого. Вместо этого CSP можно использовать для снижения вреда, наносимого атаками через внедрение содержимого. В рамках первой линии защиты авторы веб-приложений должны валидировать свои входные данные и кодировать выходные.

Веб-приложение сможет использовать CSP, если добавить в ответ один из следующих HTTP-заголовков:

  • Content-Security-Policy

  • Content-Security-Policy-Report-Only

Каждый из этих заголовков используется в качестве механизма реализации политики обеспечения безопасности для клиента. Политика обеспечения безопасности содержит набор директив политики обеспечения безопасности, каждая из которых отвечает за объявление ограничений для определенного представления ресурса.

Например, веб-приложение может объявить, что оно ожидает загрузки скриптов из определенных, доверенных источников, если добавить в ответ следующий заголовок:

Пример Content Security Policy
Content-Security-Policy: script-src https://trustedscripts.example.com

Попытка загрузить скрипт из другого источника, отличного от того, что объявлен в директиве script-src, будет заблокирована пользовательским агентом. Кроме того, если в политике обеспечения безопасности объявлена директива report-uri, то о нарушении пользовательский агент сообщит по объявленному URL-адресу.

Например, если веб-приложение нарушает объявленную политику обеспечения безопасности, следующий заголовок ответа сообщит пользовательскому агенту, что необходимо отправить отчеты о нарушении по URL-адресу, заданному в директиве report-uri политики.

Content Security Policy с использованием report-uri
Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/

Сообщения о нарушениях представляют собой стандартные JSON-структуры, которые могут быть получены либо через собственный API веб-приложения, либо с помощью размещенного в открытом доступе сервиса передачи сообщений о нарушениях CSP, например, report-uri.com/.

Заголовок Content-Security-Policy-Report-Only предоставляет авторам и администраторам веб-приложений возможность отслеживать политику обеспечения безопасности, а не навязывать ее. Этот заголовок обычно используется при проведении экспериментов и/или разработке политики безопасности для сайта. Если политика считается эффективной, ее можно применить, используя вместо этого поле заголовка Content-Security-Policy.

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

Content Security Policy Report Only
Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; report-uri /csp-report-endpoint/

Если сайт нарушит эту политику, попытавшись загрузить скрипт с сайта evil.com, пользовательский агент отправит отчет о нарушении по URL-адресу, заданному директивой report-uri, но при этом позволит нарушающему ресурсу загрузиться.

Применение спецификации Content Security Policy к веб-приложению зачастую является не такой уж простой задачей. Следующие ресурсы могут принести дополнительную пользу при разработке эффективной политики безопасности для вашего сайта.

Введение в Content Security Policy

Руководство по CSP – Mozilla Developer Network

Рекомендация кандидата W3C

Спецификация Referrer Policy

Referrer Policy – это механизм, который веб-приложения могут использовать для управления полем реферера, содержащим последнюю страницу, на которой находился пользователь.

Подход Spring Security заключается в использовании заголовка Referrer Policy, который предусматривает различные политики:

Пример Referrer Policy
Referrer-Policy: same-origin

Заголовок ответа Referrer-Policy предписывает браузеру сообщить получателю источник, в котором ранее находился пользователь.

Спецификация Feature Policy

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

Пример Feature Policy
Feature-Policy: geolocation 'self'

С помощью Feature Policy разработчики могут выбирать набор "политик", которые браузер будет применять к определенным функциям, используемым на вашем сайте. Эти политики ограничивают доступ сайта к API или изменяют логику работы браузера по умолчанию для определенных функций.

Спецификация Permissions Policy

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

Пример Permissions Policy
Permissions-Policy: geolocation=(self)

С помощью Permissions Policy разработчики могут выбирать набор "политик", которые браузер будет применять к определенным функциям, используемым на вашем сайте. Эти политики ограничивают доступ сайта к API или изменяют логику работы браузера по умолчанию для определенных функций.

Заголовок Clear Site Data

Clear Site Data – это механизм, с помощью которого любые данные – cookies, локальное хранилище и тому подобное – можно удалить на стороне браузера, если HTTP-ответ будет содержать этот заголовок:

Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"

Это эффективная мера по очистке, выполняемая при выходе из системы.

Спецификация Cross-Origin Policies

Spring Security обеспечивает поддержку некоторых важных заголовков спецификации Cross-Origin Policies. Этими заголовками являются:

Заголовок Cross-Origin-Opener-Policy (COOP) позволяет документу верхнего уровня разорвать связь между своим окном и любыми другими в группе контекста просмотра (например, между всплывающим окном и его открывателем), предотвращая прямой доступ к DOM-модели между ними.

Активация Cross-Origin-Embedder-Policy (COEP) предотвращает загрузку в документ любых ресурсов из разных источников, которые явным образом не предоставляют документу разрешение на загрузку.

Заголовок Cross-Origin-Resource-Policy (CORP) позволяет управлять набором источников, которые имеют право добавлять ресурс. Это надежная защита от атак типа Spectre, поскольку она позволяет браузерам блокировать заданный ответ до того, как он попадет в процесс злоумышленника.

Кастомные заголовки

Spring Security содержит механизмы, позволяющие удобно добавлять наиболее распространенные заголовки безопасности в ваше приложение. Однако он также предусматривает перехватчики для добавления кастомных заголовков.

HTTP

Всё взаимодействие по протоколу HTTP, включая статические ресурсы, следует защитить с помощью TLS.

Как фреймворк, Spring Security не обрабатывает HTTP-соединения и поэтому не обеспечивает поддержку HTTPS напрямую. Тем не менее, он предусматривает ряд функций, которые помогают использовать HTTPS.

Переадресация на HTTPS

Если клиент использует HTTP, в конфигурацию Spring Security можно добавить переадресацию на HTTPS как в сервлетном окружении, так и в окружении WebFlux.

Strict Transport Security

Spring Security обеспечивает поддержку механизма Strict Transport Security и активирует его по умолчанию.

Конфигурация прокси-сервера

При использовании прокси-сервера важно убедиться, что приложение было сконфигурировано правильно. Например, многие приложения будут содержать балансировщик нагрузки, который отвечает на запрос example.com/, перенаправляя его на сервер приложений по адресу 192.168.1:8080. Без надлежащей конфигурации сервер приложений не будет знать о существовании балансировщика нагрузки и обработает запрос так, как будто клиент запросил 192.168.1:8080.

Это можно исправить, если использовать RFC 7239, чтобы задать использование балансировщика нагрузки. Чтобы обеспечить совместимость приложения, нужно сконфигурировать сервер приложений таким образом, чтобы он был совместим с заголовками X-Forwarded. Например, Tomcat использует RemoteIpValve, а Jetty – ForwardedRequestCustomizer. Кроме того, пользователи Spring могут использовать ForwardedHeaderFilter.

Пользователи Spring Boot могут использовать свойство server.use-forward-headers для конфигурирования приложения.