Я разрабатываю API для группы сайтов. Сайты очень похожи (вроде StackOverflow, SuperUser и ServerFault), и для них имеет смысл иметь общий бэкэнд. Поэтому мы решили попробовать использовать хороший REST API в качестве бэкэнда и множество очень похожих, но разных внешних интерфейсов, потребляющих указанный API. Желательно, чтобы все интерфейсы были статичными, но это не является жестким требованием, если оно оказывается невозможным.
Сейчас я работаю над созданием этого API и беспокоюсь о последствиях для безопасности, особенно о CSRF. Исходя из моего базового понимания атак CSRF, они состоят из двух важных компонентов:
Возможность назвать ресурс и тело запроса.
Обман пользователя / браузера с помощью ambient auth (например, сессий), чтобы сделать запрос к тому ресурсу, который выглядит аутентифицированным.
Многие классические подходы к исправлению CSRF-атак основаны на сеансе. Поскольку мой REST API на самом деле не выполняет сессий, это предотвращает множество векторов, а также практически все способы их устранения. Например, двойная отправка не имеет смысла, потому что нечего дважды отправлять.
Мой первоначальный подход включал атаку второй части атаки CSRF. Если я аутентифицирую все запросы (скажем, с использованием HTTP Basic Auth), и браузер не сохраняет эти учетные данные сохраненными (например, некоторые JS сделали запрос), только JS, имеющий учетные данные, может выполнить запрос, и мы закончили , Очевидным недостатком является то, что приложение должно знать учетные данные пользователя. Другой чуть менее очевидный недостаток - то, что если я хочу надежно хранить учетные данные на стороне API, то проверка пароля должна занять фиксированное, нетривиальное время. Если для безопасной проверки пароля требуется 100 мс, то для каждого другого запроса потребуется не менее 100 мс + eps, и потребуется несколько хитроумных хитрых хитростей на стороне клиента, чтобы это не казалось медленным. Я мог бы кешировать это (так как учетные данные всегда будут одинаковыми), и если я буду очень осторожен, мне удастся сделать это без введения временной уязвимости, но это звучит как гнездо шершня.
OAuth 2.0 кажется немного непосильным, но я думаю, что это может быть лучшим решением в конце концов, иначе я в итоге плохо его реализовал. Я полагаю, что на данный момент могу выполнить базовую аутентификацию HTTP и перейти к OAuth, когда у нас есть сторонние разработчики приложений.
Есть небольшое несоответствие импеданса с OAuth. OAuth действительно хочет помочь приложениям получить доступ к материалам в другом приложении. Я хочу, чтобы пользователи зарегистрировались на одном из интерфейсов еще до того, как такая учетная запись будет существовать.
Я также рассмотрел возможность атаки на точку 1, сделав URL-адреса рандомизированными, то есть добавив токены в строку запроса. Это, безусловно, сработает, и это очень близко к тому, как работает традиционный рандомизированный токен в форме, и, учитывая HATEOAS, он даже должен быть довольно RESTful, хотя возникает два вопроса: 1) с чего начать? Есть ли обязательная точка запуска API, в которой вы входите в систему с использованием HTTP Basic Auth? 2) Сколько бы обрадовали разработчики приложений, если они не могут заранее предсказать URL, черт побери HATEOAS?
Я видел Как предотвратить CSRF в приложении RESTful? , но я не согласен с предположением, что рандомизированные URI обязательно не являются RESTEST. Кроме того, этот вопрос на самом деле не имеет удовлетворительных ответов и не упоминает OAuth. Кроме того, решение для двойной отправки сеанса недопустимо, как я упоминал выше (другой домен для статического внешнего интерфейса, чем конечная точка API).
Я понимаю, что в сущности я пытаюсь разрешить межсайтовые запросы из одного домена и запретить их из другого, а это нелегко. Конечно, должно быть какое-то разумное решение?