Як запобігти CSRF у додатку RESTful?


78

Підробку міжсайтових запитів (CSRF), як правило, запобігають одним із таких методів:

  • Реферер чека - НАДІЙНИЙ, але ненадійний
  • вставити маркер у форму та зберегти маркер у сеансі сервера - насправді RESTful
  • загадкові одноразові URI - не RESTful з тієї ж причини, що і маркери
  • надсилати пароль вручну для цього запиту (не кешований пароль, який використовується з HTTP-аутентифікацією) - RESTful, але не зручний

Моя ідея полягає у використанні секрету користувача, загадкового, але статичного ідентифікатора форми та JavaScript для генерації токенів.

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

Щось не так із цим підходом, незважаючи на те, що він не працює без JavaScript?

Додаток:


Ваш секрет користувача не є унікальним для користувача, зловмисникові просто потрібно отримати це число та налаштувати свої сценарії, щоб використовувати новий розрахунок. Як ви проходите аутентифікацію користувачів, якщо у вас взагалі немає штату?
Mike

Секрет користувача унікальний для кожного користувача і отримати його можна лише після автентифікації (базова HTTP або дайджест-автентифікація або автентифікація сертифіката)
Дімон,

Відповіді:


28

Тут є багато відповідей, і з багатьма із них є проблеми.

Що ви НЕ повинні робити:

  1. Якщо вам потрібно прочитати маркер сеансу з JavaScript, ви робите щось жахливо неправильно. У файлі cookie вашого ідентифікатора сеансу ЗАВЖДИ повинен бути встановлений лише HTTP, тому він не доступний сценаріям.

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

  2. Ідентифікатор сеансу не слід записувати до вмісту сторінки. Це з тих самих причин, що і ви встановили HTTPOnly. Це означає, що ваш маркер csrf не може бути вашим ідентифікатором сесії. Вони повинні мати різні цінності.

Що потрібно зробити:

  1. Дотримуйтесь вказівок OWASP :

  2. Зокрема, якщо це програма REST, вам може знадобитися подвійна подача токенів CSRF . Якщо ви це зробите, просто переконайтеся, що ви визначили його для певного повного домену ( www.mydomain.com ), а не батьківського домену (example.com), і що ви також використовуєте атрибут cookie "samesite", який набуває популярність.

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

ПРИМІТКА. Як зазначено нижче @krubo, метод подвійного подання виявив деякі слабкі сторони (див. Подвійне подання) . Оскільки ця слабкість вимагає, щоб:

  1. Ви визначаєте файл cookie, обмежений батьківським доменом.
  2. Не вдається встановити HSTS .
  3. Зловмисник контролює деяке розташування мережі між користувачем та сервером

Я думаю, що слабкість більше відноситься до категорії "прохолодної розмови Defcon", а не "ризику безпеки в реальному світі". У будь-якому випадку, якщо ви збираєтеся використовувати подвійне подання, не завадить зробити кілька додаткових кроків, щоб повністю захиститися.


Нове оновлення 07/06/2020

Мій новий улюблений спосіб подвійного подання - це створення та передача криптографічного випадкового рядка в тілі запиту, як і раніше; але замість того, щоб файл cookie мав однакове точне значення, нехай файл cookie має закодоване значення рядка, підписаного сертифікатом. Це все так само легко перевірити на стороні сервера, але набагато складніше імітувати зловмиснику. Ви все одно повинні використовувати атрибут cookie самого сайту та інші засоби захисту, описані раніше в моєму дописі.


XSS із файлом cookie сеансу настільки ж вразливий, як і XSS з маркером, який можна прочитати з JavaScript. Якщо ви все ще можете створити запит AJAX, який переказує гроші з облікового запису користувача на мій рахунок, і сервер із задоволенням прийме його.
ghayes

4
@ghayes Я не згоден. Токен вашого сеансу набагато чутливіший, ніж ваш CSRF. За допомогою вашого маркера сесії я можу отримати повний доступ до програми, як і ви, зі своєї машини. За допомогою токена CSRF я потенційно можу отримати список попередньо сценарій чутливих дій, які виконуються у вашому браузері. Другий сценарій набагато складніше здійснити, вимагає знання програми, вимагає більше часу для виконання, і дії обмежуються тим, що ви запланували заздалегідь. Перший сценарій займає один рядок коду для будь-якого веб-сайту та додаток Cookie Manager для зловмисника для використання на його машині.
Даг

Тут варто згадати. Маючи суворі cross-origin HTTP requestперевірки на сервері та http повертає заголовки з API, можна обмежити велику кількість автоматизованих збитків, які зловмисник може завдати зареєстрованому користувачеві.
LessQuesar

1
Чи не оновлене OWASP керівництво більше не приймає подвійне підпорядкування CSRF токенов в якості первинного захисту, але переніс його на захист в глибині. Проблема полягає в тому, що його можна перемогти, якщо зловмисник може написати файл cookie, що він може зробити, наприклад, якщо контролює інший субдомен.
krubo

1
Тепер посилання
OWASP

10

Чи правильно я це розумію:

  • Вам потрібен захист від CSRF для користувачів, які увійшли в систему за допомогою файлів cookie.
  • І одночасно вам потрібен інтерфейс RESTful для автентифікованих запитів Basic, OAuth та Digest від додатків.

То чому б не перевірити, чи входять користувачі в систему за допомогою файлів cookie та застосовувати CSRF лише тоді ?

Я не впевнений, але чи можливо, щоб інший сайт підробляв такі речі, як Basic auth або заголовки?

Наскільки мені відомо, CSRF - це все про файли cookie ? RESTful автентифікація не відбувається з файлами cookie.


Мені теж це було цікаво! Згідно з цією статтею mathieu.fenniak.net/... Має бути можливо ввімкнути перевірку CSRF, якщо хтось надходить через cookie / сеанс, і вимкнути його, якщо запит надходить за якоюсь схемою автентифікації без громадянства Basic .. тощо
CMCDragonkai

4
Будьте обережні з базовою автентифікацією - вона фактично еквівалентна користувачу, який увійшов у систему за допомогою файлу cookie, оскільки браузери надсилатимуть наданий заголовок авторизації на наступні запити як зручність користувача.
Саймон Лішке,

@SimonLieschke, як буде інтегровано windows / ntlm / kerberos. Якщо встановлено, веб-переглядач просто отримає маркер від DC.
stuartm9999

9

Вам обов’язково потрібен певний стан на сервері для автентифікації / авторизації. Це не обов'язково повинен бути сеансом http, хоча ви можете зберігати його у розподіленому кеші (наприклад, memcached) або базі даних.

Якщо ви використовуєте файли cookie для автентифікації, найпростішим рішенням є подвійне надсилання значення файлу cookie. Перш ніж подавати форму, прочитайте ідентифікатор сеансу з файлу cookie, збережіть його у прихованому полі, а потім надішліть. На стороні сервера переконайтеся, що значення в запиті збігається з ідентифікатором сеансу (який ви отримали з файлу cookie). Злий сценарій з іншого домену не зможе прочитати ідентифікатор сеансу з файлу cookie, тим самим запобігаючи CSRF.

Ця схема використовує єдиний ідентифікатор протягом сеансу.

Якщо ви хочете отримати більший захист, створіть унікальний ідентифікатор за сеанс для кожної форми.

Крім того, НЕ генеруйте маркери в JS. Будь-хто може скопіювати код і запустити його з іншого домену, щоб атакувати ваш сайт.


1
Сеанси не потрібні для автентифікації, як продемонструвала автентифікація HTTP. Код JavaScript для генерування маркера не є секретним - лише секрет користувача повинен бути секретним.
Дімон

3
@Sri, хоча я погоджуюсь, що сеанс - найкращий спосіб вирішити це з точки зору безпеки та продуктивності. Це не RESTful, оскільки воно вимагає від сервера відстеження стану кожного користувача, що може спричинити проблеми з масштабованістю.
ладья

1
Чи правильно говорити, що подвійна подача файлів cookie не буде працювати, якщо сторінка вразлива до атак XSS? Оскільки тоді ви зможете надіслати форму безпосередньо з самого домену, а значення буде надіслано як через cookie, так і через форму.
Gabriele Cirulli

1
@GabrieleCirulli Так, це справедливе твердження. XSS перевершує більшість засобів захисту CSRF. Капчі - це, мабуть, єдина форма CSRF, яка досі діє.
Sripathi Krishnan

Ви маєте на увазі захист CSRF? : P Але так, я згоден.
Gabriele Cirulli

6

Статичний ідентифікатор форми взагалі не забезпечує захисту; зловмисник може забрати його сам. Пам’ятайте, що зловмисник не обмежується використанням JavaScript на клієнті; він може отримати статичну форму ідентифікатора на стороні сервера.

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

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


1
Ідентифікатор форми - це щось на зразок відкритого ключа. Ви маєте рацію, GET /usersecret/john_doeє частиною JavaScript. Ім'я користувача не є секретом, а ідентифікатор, отриманий із цим запитом аутентифікованим (!) Користувачем. Дякую за посилання.
Дімон

3

У шпаргалці з профілактики CSRF є кілька методів, які можуть бути використані спокійною службою. Найбільш RESTful пом'якшення CSRF без використання - використання джерела посилання або HTTP, щоб переконатися, що запити походять із домену, якому ви довіряєте.


2
Це небезпечна порада, хоча важко підробити реферала HTTP, це неможливо, але також не гарантується заголовок реферера (а якщо не надіслати заголовок реферера, програма зламається).
Хлопчик Баукема

3
@RelaXNow Перевірте цей фреймворк CSRF, який я писав: github.com/TheRook/CSRF-Request-Builder . Це дозволяє вказати довільні заголовки http, а також тіло. Однак він не може змінити референт http, оскільки це заборонено Flash. Шпаргалка щодо профілактики CSRF дуже хороша, ви повинні прочитати посилання в моєму дописі.
грак

Справедливим моментом є те, що в контексті CSRF зловмисник не зможе (наскільки мені відомо) підробити заголовок Referer жертви, однак заголовок все одно не гарантується, і вимагати його для вашого API слід лише якщо ви можете гарантуємо, що його завжди буде надіслано (наприклад, для внутрішньої заявки компанії).
Хлопчик Баукема

3
@RelaXNow Якщо запит походить зі сторінки HTTPS, реферер буде зафіксовано із запиту. що слід розглядати як збій (згаданий у посиланні вище). Люди працюють над цією проблемою, Mozilla представила заголовок http "Origin", який приголомшливий і вартий уваги, і його можна використовувати не тільки для вирішення проблеми захисту RESTful csrf, але й багатьох інших зловживань, таких як атаки включення JSON і клацання. Проблема в тому, що не кожен браузер підтримує це :(. Також я відредагував свою публікацію, на той випадок, якщо ви хочете позбутися від -1.
грак

1
s / комітується / опускається / :). Хороші моменти / інформація, але я давно зняв свій -1 і підтримав ваші коментарі за корисну інформацію.
Boy Baukema

0

Щось не так із цим підходом, незважаючи на те, що він не працює без JavaScript?

Ваш секрет користувача не є секретом, якщо ви надсилаєте його клієнту. Зазвичай ми використовуємо такі секрети, щоб генерувати хеші та надсилати їх із формою, і чекати їх для порівняння.

Якщо ви хочете бути RESTful, запит повинен містити всю інформацію про те, як його обробити. Способи, як це зробити:

  • Додайте файл cookie маркера csrf до вашого клієнта REST і надішліть той самий маркер у прихований ввід разом із вашими формами. Якщо служба та клієнт перебувають під різними доменами, вам доведеться поділитися обліковими даними. На сервісі ви повинні порівняти 2 маркери, і якщо вони однакові, запит дійсний ...

  • Ви можете додати файл cookie маркера csrf за допомогою служби 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, дуже-дуже погане рішення. Але це дуже елементарна навичка, якщо ви хочете перешкодити КСВФ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.