Подделка межсайтовых запросов (или CSRF, или XSRF, или «морской серфинг») - одна из старейших атак на веб-приложения. Это означает, что, встроив форму или URL-адрес во вредоносный сайт, злоумышленник может получить запрос, выполненный на уязвимом сервере, как если бы посетитель его сайта сделал это. Есть много способов защиты ваших приложений с разным уровнем сложности. В одностраничных приложениях (или SPA) самые простые могут быть лучшими.

Вы можете подумать, что это не влияет на вас или что CSRF мертв, но вот как он может захватить вашу страницу WordPress (через плагин членства), ваш Jenkins (через плагин Slack) или ваш роутер .

Что такое CSRF?

CSRF - это атака на аутентификацию на основе файлов cookie. Сайт уязвим, если они проверяют состояние входа пользователя на основе файла cookie без дополнительных проверок (или их недостаточно), чтобы узнать, откуда исходит запрос. Обычно это работает так:

  1. Пользователь входит на уязвимый сайт в рамках обычного использования.
  2. Сайт устанавливает файлы cookie сеанса в браузере пользователя.
  3. Пользователь заходит на вредоносный сайт, в который встроена скрытая форма.
  4. Скрытая форма отправляет POST-запрос уязвимому сайту, или встроенный <img> отправляет ему GET-запрос.
  5. Браузер выполняет запрос, отправляя сохраненные учетные данные.
  6. Сервер видит, что запрос исходит от пользователя, предполагает, что он законный, и выполняет его, размещая фотографии кошек в ваших социальных сетях. (или что-то более злое)

Чем он отличается в СПА?

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

Важная часть CSRF заключается в том, что вы не выполняете навигацию верхнего уровня, не отправляете формы и обрабатываете свой серверный API с помощью XMLHttpRequst или fetch() (или какой-либо библиотеки, построенной на их основе). Это важно, потому что навигация верхнего уровня по-разному обрабатывается файлами cookie одного и того же сайта, и вы можете положиться на CORS, если решите переместить свой API в поддомен, что позволит вам более надежно защитить свой сайт от CSRF - я объясню это в следующий раздел.

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

Смягчение

Есть несколько способов предотвратить этот тип атаки: от простой настройки файлов cookie до генерации токенов и добавления их в заголовки каждого запроса.

1. Проверка заголовков, предоставленных браузером

В рамках поддержки CORS все браузеры поддерживают отправку заголовка источника для запроса на кросс-источник (хотя IE11 не согласен со всеми остальными в отношении того, что такое кросс-источник), это также включает субдомены. Этот заголовок всегда будет присутствовать, если запрос пришел из другого источника, что означает, что вы можете эффективно его проверить. Похожий заголовок - это Referer (sic!), Который вы можете проверить, но он ненадежен и вызывает проблемы с конфиденциальностью.

Проблема в том, что заголовок Origin не всегда присутствует для запросов из одного источника. Чтобы решить эту проблему, вы можете переместить свой API в поддомен и настроить CORS. Таким образом, все ваши запросы являются межсайтовыми, и вы готовы их обрабатывать.

2. Настройка файлов cookie более безопасным способом.

Вы можете настроить свои куки с помощью атрибута SameSite. Он принимает значения None, Lax и Strict, причем Lax является значением по умолчанию в Chrome после Chrome 80.

Нет в этом контексте означает небезопасно.

Слабый означает, что файлы cookie будут отправляться только для запросов из одного источника, включая навигацию верхнего уровня (например, щелчок по ссылке на ваш сайт или обновление). Поскольку файлы cookie отправляются с навигацией верхнего уровня, то, если злоумышленник может открыть окно и есть конечная точка, принимающая запросы GET, они могут попытаться злоупотребить ею. Lax может быть отличным решением, если у вас нет конечных точек GET, которыми они могут злоупотреблять.

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

Слабый + Строгий: если вы используете несколько файлов cookie, один установлен на Строгий, который будет использоваться для конфиденциальных операций или операций с изменением состояния (например, перевод денег или смена паролей), а другой - на Слабый, который позволит пользователям загружать не конфиденциальные данные. Для достижения наилучшего эффекта это объединяет два указанных выше, но вам нужно будет управлять несколькими файлами cookie.

Вы можете прочитать более подробное объяснение этого здесь.

3. Токены CSRF

Это «классический» способ работы с CSRF: вы добавляете скрытый ввод токена CSRF в формы со значением, установленным для токена, который вы сгенерировали и сохранили на сервере (или в файле cookie только для HTTP), чтобы вы могли позже проверить его. при подаче. Это решает проблемы CSRF, если хорошо реализовано, но это более сложно и более подвержено ошибкам, чем два вышеупомянутых.

Подробнее об этом методе и об атаке в целом вы можете прочитать в Шпаргалке по OWASP.

Что вам следует использовать?

Это три очень разных способа решения одной и той же проблемы: какой из них лучше? OWASP рекомендует использовать токены в качестве основных, а два других - в качестве мер глубокой защиты. Я бы сказал, что это можно (а может и нужно) сделать проще в SPA.

Мои рекомендации:

  1. Переместите свой API в поддомен и используйте CORS для всех запросов, проверяя исходные заголовки. Это гарантирует наличие заголовка Origin и в любом случае отлично подходит для будущего вашего приложения.
  2. Если вы можете позволить себе не поддерживать некоторые браузеры, просто используйте SameSite: Strict cookie и блокируйте запросы от неподдерживаемых браузеров.

Как настроить поддомен API и CORS

К счастью для SPA, очень просто отделить серверы API и переместить их в другой субдомен. Вероятно, вы уже делаете какой-то префикс для своих конечных точек API, чтобы отделить их от статического содержимого, поэтому это будет так же просто, как переместить префикс API с пути в часть домена и настроить CORS на вашем сервере.

Таким образом, заголовок Origin всегда присутствует, и вы можете с уверенностью проверить его, поскольку ни один запрос не будет исходить из того же источника, что и API. Это также дает дополнительное преимущество, так как упрощает перемещение статического контента в CDN и дает возможность масштабировать серверную часть независимо от внешнего интерфейса. Это поддерживается во всех браузерах, поддерживаемых SPA, и это в основном вопрос конфигурации, которая также будет служить надежной защитой вашего веб-приложения в будущем, если оно станет большим.

  1. Настройте субдомен у поставщика услуг домена: в большинстве случаев это бесплатно, и вы можете сделать это за несколько кликов.
  2. Настройте свое приложение для прослушивания нового поддомена. Обычно это довольно просто, и несколько простых поисков приведут вас туда. Например, в node.js с express вы можете использовать express-subdomain. Этот вариант использования является их фактическим примером кода для пакета.
  3. Настройте CORS для своего приложения. В node.js есть пакет под названием cors, который также проверяет заголовок за вас.
// Add CORS to your API
const cors = require('cors');
const corsOptions = {
  origin: 'https://example.com',
  optionsSuccessStatus: 200 // for some legacy browsers
}
apiRouter.use(cors(corsOptions));
// Make the API listen on the subdomain
const subdomain = require('express-subdomain');
app.use(subdomain('api', apiRouter));
app.listen(3000);

Как настроить строгие файлы cookie

Поскольку большинство SPA не используют навигацию верхнего уровня, которая усложняет файлы cookie SameSite, вы можете просто использовать «SameSite: Strict» и покончить с этим… Единственная проблема - поддержка браузера. Его использование не нарушит работу старых браузеров, но и не защитит их. Он не поддерживается IE11 в Windows 7 или более ранней версии, браузером UC и некоторыми другими, с совокупной долей рынка менее 10%.

Если это нормально, вы можете просто отказаться от обслуживания старых браузеров, используя простую проверку пользовательского агента. Это может быть ненадежным, но если ваши пользователи обходят его, они, по крайней мере, знают о проблеме. Парсинг пользовательских агентов - это совсем другая тема, которая немного выходит за рамки этого. Для быстрого примера используйте express-useragent или правильно настроенное regexp из списка браузеров.

const useragent = require('express-useragent');
// For setting the cookie
res.cookie('session', sessionToken, { maxAge: 900000, httpOnly: true, secure: true, sameSite: true });
// For refusing old browsers
app.use(useragent.express());
app.get('/', function(req, res){
  if (!req.isChrome && !req.isFirefox) {
    res.status(400).json({error: "BrowserNotSupported"});
  }
});

Особенности метода токена

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

1. Войдите в CSRF

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

См. Зарегистрированный, но все еще активный CSRF входа в систему в New Relic.

2. Уязвимые субдомены

Ваш сайт может быть неуязвимым, но может быть субдомен (например, где вы обслуживаете пользовательский контент). Если это так, они могут устанавливать файлы cookie и обходить токены, если это реализация файлов cookie с двойной отправкой.

Прочтите, как OWASP описывает, как он побеждает файлы cookie двойной отправки.

3. Утечка токенов

Токены могут вытекать из вашего приложения из-за неправильной настройки ведения журнала, внедрения злонамеренного пользовательского контента, той или иной ошибки.

Посмотрите, как Facebook слил токены.

4. Отсутствующие чеки

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

Посмотрите, как WooCommerce пропустил проверки для последующих шагов импорта.

5. Токены, не привязанные к сеансу / пользователю

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

Посмотрите, как пошло не так с PayPal.

TL;DR

Есть более простые способы защиты вашего SPA от атак CSRF, чем обычно рекомендуемые токены. Я рекомендую два решения:

  1. Переместите свой API в поддомен и настройте CORS. Таким образом, все запросы относятся к разным источникам, поэтому, правильно настроив его, вы также справляетесь с атаками CSRF.
  2. Установите файлы cookie с помощью SameSite: Strict и откажитесь от обслуживания старых браузеров. Это теряет около 10% пользователей по всему миру, но для вас это может быть нормально.

На мой взгляд, оба решения работают лучше, чем «классическое» решение на основе токенов: вместо того, чтобы строить сложное решение для обхода давней проблемы, вы либо согласовываете свое приложение с тем, как работают браузеры, либо делаете все запросы межсайтовыми (например, атака будет) и обработает это правильно, или вы попросите браузер исправить их поведение, что новые браузеры будут уважать.

В идеале вы должны сделать оба из вышеперечисленных (и, возможно, пропустить отказ от старых браузеров) и, возможно, реализовать токены CSRF на всякий случай и разрешать только запросы, прошедшие все ваши проверки. Это то, что называется глубокоэшелонированной защитой, и это всегда лучшее решение, но это не идеальный мир, где у вас есть все время, необходимое для реализации всех мер безопасности. Используйте хотя бы один, а затем делайте столько, сколько сможете.