Атаку, при которой хакер пытается выполнить авторизованный запрос на вашем сайте, используя доступ, предоставленный пользователем, называют CSRF-атакой (cross-site request forgery – межсетевая подделка запроса). Это огромная проблема для любой платформы (и особенно финтех) с многотысячной аудиторией.  Меня зовут Алексей, я разработчик команды Платформа Банки.ру Я занимаюсь, в частности, разработкой новой платформы на node.js, на которую у нас сейчас переезжают многие сервисы. Ниже я подробно расскажу о том, как мы искали самый надежный способ защиты от CSRF-атак, чем руководствовались при выборе решения и как его реализовали. Итак, при csrf-атаке хакер пытается выполнить авторизованный запрос через доступ пользователя. Например, пользователь переходит по ссылке из письма, а злоумышленник в это время отправляет запрос на наш сервер, применяя пользовательские куки. Это запрос интерпретируется как действие пользователя. Есть несколько способов защиты от CSRF. Вот что рекомендует OWASP (The Open Web Application Security Project) – организация, специализирующаяся на исследованиях веб-безопасности: Инициализация CSRF-токенов. Этот метод считается наиболее рекомендуемым. CSRF-токен - это ключ, выдаваемый пользователю для определенных действий. Ограничение кук. Это означает использование специальных атрибутов кук, чтобы браузер мог отправлять их только на наш домен и поддомены. Использование нестандартных заголовков. Этот подход предполагает ограничение использования доменов, с которых могут быть отправлены запросы. Валидация заголовков origin. Здесь проверяется совпадение между источником (source) и целью (target) нашего заголовка origin. У каждого из перечисленных методов есть свои ограничения. Поэтому наиболее универсальным и эффективным способом защиты все-таки является первый -  инициализация CSRF-токена - то есть подписание  пользователя и предоставление прав на действия только подписанному пользователю. Выглядит это так: когда пользователь посещает сайт, мы создаем уникальный CSRF-токен, привязанный к этому пользователю. Токен можно получить через заголовок при входе на сайт или внутри HTML, если HTML генерируется на сервере. Этот токен верифицирует пользователя. Мы подписываем этим токеном действия, которые могут изменить сервер (например, POST, PUT, DELETE), и рассматриваем их как доверенные.   Если злоумышленник попытается сделать запрос на сервер без этого токена, его действия будут заблокированы, и он не сможет выполнить нежелательные операции. Стоит учитывать, что методы GET/HEAD/OPTIONS не должны изменять серверную часть. В противном случае мы сами закладываем уязвимости, от которых одним наличием CSRF-токена не защититься.             Итак, мы выбрали способ защиты от злоумышленников. Пришло время приступать к реализации. Первый шаг на этом пути - поиск подходящей библиотеки, проверенной, надежной, которой уже пользуются другие компании. Согласно результатам поиска, наиболее популярным вариантом реализации защиты от CSRF-атак является использование пакета csurf. Практически каждая статья на эту тему рекомендует этот пакет. Что такое csurf? Это библиотека, разработанная Express.js для защиты от CSRF-атак. В тот день, когда мы приступили к поиску решения, выяснилось, что 5 дней назад библиотека csurf  была заархивирована.  А случилось это по той  причине, что сама библиотека  csurf содержит уязвимости к CSRF-атакам. Это создает абсурдную ситуацию, когда инструмент, разработанный для защиты от CSRF, оказывается несостоятельным и не может соответствовать требованиям безопасности, которые он предполагает обеспечивать пользователям. В статье по ссылке можно почитать об этом подробнее. Мы вынуждены были искать какие-то другие решения.  На картинке ниже видно, что сам пакет csurf нам предлагал попробовать поискать реализации для  express.js CSRF  - защиты самостоятельно в менеджере пакетов npm. На этом этапе мы поняли, что помимо архивированной библиотеки csurf по сути не существует ни одного адекватного решения. Самый популярный пакет в этой выборке отмечен максимум 10-20 звёздами на гитхабе.   Альтернатив по сути не оказалось.  Итак, мы убедились, что готового решения для нашей задачи нет. Значит, нам надо будет самостоятельно придумывать эту защиту на уровне нашего сервера. Организация OWASP, которую мы выше упоминали, разработала определенные рекомендации по защите от CSRF. Среди них есть требования, выписанные к токену. Токен должен быть: Уникальным Единоразовым  Обладать устойчивым к подбору размером. Сгенерированным криптостойким генератором чисел.  Ограниченным по времени жизни. Это базовые принципы для нашего CSRF-токена, по которым мы стали его создавать.  Дальше расскажу, каким образом у нас действует CSRF-защита.  Начнем с инициализации. Тут логика достаточно понятная. Так как у нас есть SSR, то первичную загрузку странички нашего сайта пользователем мы можем использовать как механизм начала сессии. После загрузки страницы, Node-приложение генерирует ключ, передает пользователю открытую часть, а закрытую часть превращает в соленый хэш-токен и сохраняет его.  Следующий шаг после инициализации токена и передачи его пользователю - это валидация. Когда пользователь отправляет запросы, наш сервер проверяет переданный токен. Схема такова: клиент передает токен, сервер проверяет его, хэширует и сравнивает с сохраненным. Если токены совпадают, действие разрешено. В противном случае запрос отклоняется как невалидный. Теперь возникает вопрос: где можно сохранить токен? Существует два варианта  реализации защиты от CSRF на сервере: Первый подход - это Synchronizer Token Pattern, состоящий из сохранения токена в хранилище по ключам после его генерации. Этот токен в будущем можно будет валидировать в сессии клиента. Это Statefull решение. Плюсы этого подхода в том, что токен хранится безопасно и удобно, и его сложно скомпрометировать. Однако есть и недостатки: необходимо следить за временем жизни токена. Также этот подход требует поддержания сервиса, что может быть не всегда удобно. Второй подход, популярный в мире JavaScript, - это Stateless решение, известное как паттерн Double Submit Cookie. Здесь пользователь сохраняет хешированный и соленый токен в куки после его генерации. Пользователь, обладая как куками, так и самим токеном, может выполнять действия на сервере. При проверке мы извлекаем куку, берем токен, хешируем и солим, а затем сравниваем его с переданным кукой. Этот метод удобен для хранения. Переходим к реализации, и, рассматривая второй вариант, замечаем, что он технически больше подходит с точки зрения инфраструктуры, простоты реализации и обеспечивает достаточный уровень безопасности. Он не идеальный, но подробнее о критических моментах расскажу позднее. Начнем с инициализации токена. Наш сервер создает токен, затем мы его хешируем, солим и сохраняем в cookie. На сервере контроллер генерирует страницу и инициализирует сессию. Эта инициализация, по сути, равна размещению хэшированного соленого токена в куки.  После создания токена нам нужно передать его клиенту. На этапе рендера страницы, генерируемого шаблона HTML, открытый токен помещается в мета-тег страницы. Это более безопасный способ, чем включение токена в форму. Мы вставили токен в страницу, передали его клиенту, и далее клиент отправляет запросы с этим токеном. Когда клиент отправляет запрос, наша утилита извлекает токен из мета-тега страницы, который клиент получил. Затем мы отправляем запрос через proxy-сервер, выполняя валидацию этого токена. Отправляем запрос с CSRF-токеном из meta-тега в HTTP-заголовке. На последнем этапе валидации, прокси-сервер (нода) выполняет блок валидации, о котором мы говорили ранее. Мы проверяем наличие активной сессии, наличие правильного заголовка от пользователя, а также совпадение хэша полученного токена с хэшом сохраненного токена. Ну и в заключение расскажу о возможных проблемах или узких местах. 1. Работа с WebView и <iframe>. Если мы вставляем наш сайт в мобильное приложение или на страницу другого сайта, одной из проблем может быть ограничение передачи куков нашего сайта к другому сайту. Для решения этой проблемы, нам может потребоваться разрешить проброс куков не только на нашем домене. Куки должны быть установлены с флагом http-only и передаваться только через безопасное соединение. Если мы инжектируем контент только в доверенные клиенты, мы можем ожидать, что это уровень безопасности будет удовлетворительным. 2. Защита от XSS атак. XSS (Cross Site Scripting) - это атаки, при которых злоумышленники внедряют на наш сайт вредоносные скрипты, выполняющие действия от имени пользователя. Эта атака считается одной из самых опасных, но практически все современные фреймворки по умолчанию предоставляют защиту от нее. Наличие React на фронтенде вашего приложения в значительной степени обеспечивает эту защиту. Однако, хотя React и другие фреймворки и предоставляют базовую защиту, важно помнить о возможных узких местах в безопасности и следить за обновлениями для обеспечения надежной защиты. Конечно, существуют также дополнительные политики безопасности, связанные с конфигурацией нашего сайта, которые способствуют защите от CSRF и XSS-атак.  Но для подробного рассмотрения этих аспектов требуется более глубокое погружение в область DevOps, что выходит за рамки данной статьи.  Здесь же я хотел углубиться в такой аспект нашей политики безопасности как использование токенов CSRF. Важно понимать, что CSRF-атаки не являются абстрактной угрозой; они могут иметь серьезные последствия для конфиденциальности и целостности данных.  Надеюсь, что эта статья была интересной для вас, и вы нашли это решение полезным для разработки и обеспечения безопасности своих собственных приложений.