В своих проектах я крайне редко использую защиту от CSRF-атак, предоставляемую Spring Security, ввиду того, что подавляющая часть этих проектов — REST-сервисы без хранения состояния. Но один из проектов использует файлы cookies на стороне браузера для хранения состояния, а в этом случае для защиты от CSRF-атак нужно использовать средства Spring Security. И при переводе проекта на Spring Boot 3 я обнаружил, что Spring Security начал игнорировать передаваемый в заголовке CSRF-токен.
Причиной такого поведения, как выяснилось, стало использование по умолчанию XorCsrfTokenRequestAttributeHandler
вместо CsrfTokenRequestAttributeHandler
в Spring Security 6. В качестве быстрого решения я указал использование CsrfTokenRequestAttributeHandler
в настройках CSRF, что решило проблему, но я захотел разобраться в ситуации более подробно, и понять как можно использовать XorCsrfTokenRequestAttributeHandler
.
BREACH-атака
Причиной таких изменений в валидации CSRF-токенов стала защита от BREACH-атак. Если вкратце, то BREACH-атака — это подвид CRIME-атаки, целью которой является механизмы сжатия HTTP такие, как gzip и DEFLATE. Дело в том, что TLS 1.2 и его более ранние версии могут шифровать сжатые данные без корректной обфускации размера незашифрованных данных, что делает возможным получение HTTP-заголовков при MitM-атаках.
Как несложно догадаться, при такой уязвимости значение заголовка Cookie может утечь достаточно быстро, так как меняется редко. То же самое можно сказать и про постоянный или редко изменяющийся CSRF-токен, а получение сессионной cookie и CSRF-токена — это уже полноценный угон пользовательской сессии.
Собственно, для защиты от BREACH-атак в Spring Security и была введена маскировка CSRF-токена.
Маскировка CSRF-токена
Основной проблемой при использовании XorCsrfTokenRequestAttributeHandler
, на мой взгляд, является банальное отсутствие документации, из-за чего приходится читать исходный код, чтобы понять, как работает маскировка CSRF-токена в данном случае.
В качестве исходного CSRF-токена всё также используется UUID. Замаскированный CSRF-токен представляет собой строку Base64, состоящую из случайного набора байт, размер которого может быть больше или равен размеру исходного CSRF-токена (36 байт), и результата выполнения строгой дизъюнкции (XOR) между байтами случайного набора и исходного CSRF-токена. При этом если размер случайного набора байт больше CSRF-токена, то для строгой дизъюнкции будут использованы только первые 36 байт из набора.
Всё вышесказанное можно описать следующими строками кода:
Результат выполнения будет примерно следующим:
От себя могу лишь добавить, что если вы намерены использовать XorCsrfTokenRequestAttributeHandler
, то для каждого запроса, в котором будет передаваться CSRF-токен, вы должны маскировать его заново, чтобы не использовалось повторяющееся значение, иначе все старания будут напрасными. Так же могу посоветовать использовать маску размером больше CSRF-токена и не делать этот размер фиксированным, чтобы маскированный CSRF-токен не был одного размера.