Как предотвратить CSRF в приложении RESTful?

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

  • Проверить референт - RESTful, но ненадежный
  • добавить токен в форму и сохранить токен в сеансе сервера - не действительно RESTful
  • загадочные одноразовые URI - не RESTful по той же причине, что и токены
  • отправить пароль вручную для этого запроса (не кешированный пароль, используемый с HTTP-аутентификацией) - RESTful, но не удобно

Моя идея - использовать секрет пользователя, загадочный, но статический идентификатор формы и JavaScript для генерации токенов.

<form method="POST" action="/someresource" id="7099879082361234103">
    <input type="hidden" name="token" value="generateToken(...)">
    ...
</form>
  • GET /usersecret/john_doe, полученный JavaScript от пользователя, прошедшего проверку подлинности.
  • Ответ: OK 89070135420357234586534346 Этот секрет концептуально статичен, но может меняться каждый день/час... для повышения безопасности. Это единственная конфиденциальная вещь.
  • Прочитайте криптовальный (но статический для всех пользователей!) идентификатор формы с JavaScript, обработайте его вместе с секретом пользователя: generateToken(7099879082361234103, 89070135420357234586534346)
  • Отправьте форму вместе с созданным токеном на сервер.
  • Поскольку сервер знает секрет пользователя и идентификатор формы, можно запустить ту же функцию generateToken, что и клиент, перед отправкой и сравнением обоих результатов. Только когда оба значения равны, действие будет разрешено.

Что-то не так с этим подходом, несмотря на то, что он не работает без JavaScript?

Добавление:

Ответ 1

Здесь много ответов, и проблем с довольно многими из них.

То, что вы НЕ должны делать:

  1. Если вам нужно прочитать токен сеанса из JavaScript, вы делаете что-то ужасно неправильно. Ваш cookie файл идентификатора сеанса должен ВСЕГДА иметь только HTTPOn, поэтому он не доступен для сценариев.

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

  2. Идентификатор сеанса не должен быть записан в содержимое страницы. Это по тем же причинам, что и HTTPOnly. Это означает, что ваш токен csrf не может быть идентификатором сеанса. Они должны быть разных ценностей.

Что вы должны сделать:

  1. Следуйте инструкциям OWASP:

  2. В частности, если это приложение REST, вам может потребоваться двойная передача токенов CSRF. Если вы сделаете это, просто убедитесь, что вы определили его для определенного полного домена (www.mydomain.com), а не родительского домена (example.com), и что вы также используете атрибут cookie "samesite", который получает популярность.

Просто создайте что-то криптографически случайное, сохраните его в кодировке ASCII Hex или Base64 и добавьте его в виде файла cookie и в ваши формы, когда сервер вернет страницу. На стороне сервера убедитесь, что значение cookie соответствует значению формы. Вуаля, вы убили CSRF, избегали лишних запросов для своих пользователей и не открывали себя для новых уязвимостей.

ПРИМЕЧАНИЕ. Поскольку @krubo заявляет, что ниже метод двойного представления обнаружил некоторые недостатки (см. Двойное представление). Поскольку эта слабость требует, чтобы:

  1. Вы определяете куки, относящиеся к родительскому домену.
  2. Вы не можете установить HSTS.
  3. Злоумышленник контролирует некоторое сетевое расположение между пользователем и сервером.

Я думаю, что слабость больше относится к категории "Прохладных разговоров по защите", а не к "угрозе безопасности в реальном мире". В любом случае, если вы собираетесь использовать двойное представление, не повредит сделать несколько дополнительных шагов, чтобы полностью защитить себя.

Ответ 2

Я правильно понимаю:

  • Вам нужна защита от CSRF для пользователей, которые вошли в систему через файлы cookie.
  • И в то же время вам нужен интерфейс RESTful для аутентифицированных запросов Basic, OAuth и Digest от приложений.

Итак, почему бы не проверить , зарегистрировались ли пользователи через cookie и применить CSRF только тогда?

Я не уверен, но возможно, что другой сайт может создавать такие вещи, как Basic auth или headers?

Насколько я знаю, CSRF имеет все о файлах cookie? RESTful auth не происходит с файлами cookie.

Ответ 3

Для аутентификации/авторизации вам определенно нужно определенное состояние на сервере. Это не обязательно будет сеанс http, но вы можете хранить его в распределенном кеше (например, memcached) или базе данных.

Если вы используете cookie для аутентификации, самым простым решением является двойное представление значения cookie. Прежде чем отправить форму, прочитайте идентификатор сеанса из файла cookie, сохраните его в скрытом поле и затем отправьте. На стороне сервера убедитесь, что значение в запросе совпадает с значением идентификатора сеанса (полученного вами из файла cookie). Evil script из другого домена не сможет прочитать идентификатор сеанса из файла cookie, тем самым предотвращая CSRF.

В этой схеме используется один идентификатор в сеансе. Забастовкa >

Если вам нужна дополнительная защита, создайте уникальный идентификатор для каждого сеанса для каждой формы.

Кроме того, НЕ создавайте токены в JS. Любой может скопировать код и запустить его из другого домена, чтобы атаковать ваш сайт.

Ответ 4

Идентификатор статической формы не обеспечивает никакой защиты; злоумышленник может получить его сам. Помните, что злоумышленник не ограничен использованием JavaScript на клиенте; он может получить статическую форму ID-сервера.

Я не уверен, что полностью понимаю предлагаемую защиту; откуда берется GET /usersecret/john_doe? Это часть страницы JavaScript? Это буквальный URL? Если это так, я предполагаю, что username не является секретом, а это значит, что evil.ru может восстанавливать секреты пользователей, если ошибка браузера или плагина допускает междоменные запросы GET. Почему бы не хранить секрет пользователя в cookie после аутентификации, а не позволять любому, кто может выполнять кросс-доменные GET, получить его?

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

Ответ 5

Есть несколько методов в CryptForms Cheat Sheet, которые могут использоваться в качестве обслуживания. В большинстве RESTful антивирусных CSRF-смягчений используется источник Origin или HTTP-референт, чтобы убедиться, что запросы исходят из домена, которому вы доверяете.

Ответ 6

Что-то не так с этим подходом, несмотря на то, что он не работает без JavaScript?

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

Если вы хотите быть RESTful, запрос должен содержать всю информацию о том, как его обрабатывать. Как вы можете это сделать:

  • Добавить кэш файл csrf token с вашим клиентом REST и отправить тот же токен в скрытый ввод с вашими формами. Если служба и клиент находятся в разных доменах, вам необходимо предоставить доступ к учетным данным. В сервисе вам нужно сравнить 2 токена, и если они совпадают, запрос действителен...

  • Вы можете добавить cookie csrf token в свою службу REST и отправить тот же токен с представлениями ваших ресурсов (скрытые входы и т.д.). Все остальное совпадает с концом предыдущего решения. Это решение находится на грани RESTfulness. (Это нормально, пока клиент не вызовет службу, чтобы изменить файл cookie. Если cookie - только HTTP, клиент не должен знать об этом, если это не так, то клиент должен его установить.) Вы можете сделать больше сложное решение, если вы добавляете разные токены в каждую форму и добавляете время истечения срока действия в файлы cookie. Вы также можете отправить время истечения срока действия с помощью форм, так что вы узнаете причину, когда проверка маркера не удалась.

  • У вас может быть секрет пользователя (по-разному для каждого пользователя) в состоянии ресурса на вашем сервисе. Создавая представления, вы можете создать токен (и время истечения) для каждой формы. Вы можете генерировать хэш из фактического токена (и времени истечения, метода, url и т.д.) И секрет пользователя, а также отправить этот хэш вместе с формой. Конечно, вы скрываете "секрет пользователя" в секрете, поэтому вы никогда не отправляете это с помощью формы. После этого, если ваша служба получает запрос, вы можете снова генерировать хэш из параметров запроса и секретности пользователя и сравнивать их. Если они не совпадают, запрос недействителен...

Ни один из них не защитит вас, если ваш клиент REST будет использоваться для javascript, поэтому вы должны проверить весь свой контент пользователя на объекты HTML и удалить все из них или использовать TextNodes всегда вместо innerHTML. Вы также должны защитить себя от инъекций SQL и HTTP-заголовка. Никогда не используйте простой FTP для обновления вашего сайта. И так далее... Есть много способов внедрить злой код на ваш сайт...

Я почти забыл упомянуть, что запросы GET всегда читаются службой и клиентом. По этой услуге это очевидно, что клиент устанавливает, что любой URL-адрес в браузере должен приводить к представлению ресурса или нескольких ресурсов, поэтому он никогда не должен вызывать метод POST/PUT/DELETE на ресурсе. Например, GET http://my.client.com/resource/delete -> DELETE http://my.api.com/resource - очень-очень плохое решение. Но это очень простой навык, если вы хотите помешать CSRF.