REST — чрезвычайно популярная архитектура веб-приложений. Для вызова функций на сервере используются обычные HTTP-запросы с задаваемыми параметрами (для структуризации параметров обычно используют JSON или XML), при этом, строгого стандарта для REST-архитектуры не существует, что добавляет ей гибкости (и, конечно, немного хаоса).
REST позволяет гибко подойти к вопросу безопасности или, чем грешат многие, не подойти к вопросу совсем. Основываясь на OWASP, мы подготовили список советов, которые помогут вам улучшить безопасность вашего REST-приложения.
В качестве отправной точки в тех редких случаях, когда это тут нужно, мы используем python и Django.
Правило 0
HTTPS
Просто настройте. Пожалуйста. Защита передаваемых данных еще никому не вредила. Даже если вы думаете, что защищать в данный момент нечего, это не всегда будет так и вам все равно придется настраивать HTTPS. Так что настройте его лучше сразу, и всем будет хорошо.
Правило 1
Аутентификация
Казалось бы, очевидный совет, которым, однако, многие предпочитают пренебрегать. На приложении всегда должна быть аутентификация, даже если вы сейчас думаете, что будете им пользоваться только внутри компании и нет разницы, кто из сотрудников это делает. А если дополнить еще и журналами, то расследование инцидентов и анализ активности станет несоизмеримо проще.
В качестве токенов доступа в данный момент считается хорошим тоном использовать JWT (JSON web tokens). Не используйте токены со значением {«alg»:«none»} для контроля за целостностью, токены всегда должны содержать подпись или MAC! Подпись предпочтительнее в силу того, что ключи генерации и верификации у MAC совпадают (или могут быть легко вычислены друг из друга), то есть если сервер в состоянии проверить значение MAC, то он может и генерировать его значения. Подпись к тому же подтверждает не только целостность сообщения, но и личность отправителя.
Правило 2
Запретите те HTTP методы, которыми вы не пользуетесь
Сконфигурируйте на сервере белый список тех методов, с которыми вы работаете (GET, POST, PUT и т. д.) и отклоняйте все, что под этот список не подпадает. Вряд ли на продакшене вашему серверу нужно обрабатывать запросы типа TRACE (данный метод является составляющей XST-атаки (Cross-Site Tracing), которая состоит из XSS-атаки и метода TRACE или TRACK. Эта атака позволяет, например, украсть куки пользователя, даже если они помечены как HttpOnly). Чем меньше информации о вашей инфраструктуре доступно снаружи — тем лучше.
Правило 3
Разграничьте доступ
Всем ли вашим пользователям нужны все методы, например, DELETE? Если вы не хотите, чтобы какие-то пользователи имели возможность проводить определенные операции — настройте разграничение доступа в вашем приложении. Например, обычному пользователю доступен только метод GET на часть функций, менеджеру — GET и POST и т. д. Для этого стоит задать роли в БД, которые можно будет выдавать пользователям для удобства управления доступом.
В итоге у каждой функции будет блок проверки приблизительно такого типа:
if request.POST and user.is_manager:
do_stuff()
Правило 4
Задумайтесь об ограничении количества запросов
Если вы думаете, что ваши пользователи не должны отправлять вам сто тысяч запросов в секунду, то следует это число ограничить. Используйте API-ключи или любой другой удобный для вас механизм с целью отслеживания и ограничения количества запросов, которые будут обрабатываться в определенный период времени от одного пользователя. Для Django данную функциональность, например, предоставляет django-ratelimit, где в качестве идентификатора для ограничения можно задать различные параметры, не обязательно API-ключи, а можно IP-адрес.
Правило 5
Обязательно производите валидацию/санитизацию входных данных
Никогда не доверяйте передаваемым входным параметрам, всегда проводите валидацию/санитизацию:
- Если это возможно (и там, где это возможно), поставьте ограничение на длину/тип/формат входных данных и самого запроса. Отклоняйте все запросы/передаваемые данные, превышающие заданную вами длину или не совпадающие с типом/форматом.
- При обработке строк всегда экранируйте все спецсимволы.
- Если вы используете фреймворк, то большинство из них содержат собственные встроенные инструменты валидации и санитизации (навскидку из популярных — Django (python) и Yii2 (php)), так что важно изучить их возможности и, если какой-то необходимый вам аспект не покрыт — найти библиотеку, которая закрывает это, либо написать таковой функционал самостоятельно.
- Ведите учет ошибок валидации. Если запросы каких-то пользователей постоянно проваливают валидацию — задумайтесь над автоматической блокировкой подобных пользователей.
- Убедитесь, что ваш парсер входных параметров (или его текущая версия) не подвержен каким-либо атакам сам по себе.
Правило 6
Не отдавайте больше информации, чем необходимо
Если какой-то запрос вызвал ошибку в приложении, или оно просто по какой-то причине в данный момент недоступно — не сообщайте в ответе подробности, возвращайте максимально абстрактное сообщение об ошибке. Некоторые сервера по умолчанию возвращают stacktrace после ошибки, так что убедитесь, что вы перенастроили данную логику.
Правило 7
Всегда ведите журналы
Пусть каждое событие (аутентификация, ошибка, запрос и т.д.) заносится в логи максимально подробно. Разнесите их логически для более удобного поиска по ним в случае необходимости. На всякий случай перед записью в журналы проводите санитизацию записываемых данных.
Правило 8
Правильно указывайте Content-Type — это важно!
Чтобы браузер (или клиент) корректно считывал предоставляемые данные, важен правильно указанный Content-Type, соответствующий предоставляемым данным. В случае с браузерами стоит также выставить заголовок X-Content-Type-Options: nosniff, дабы предотвратить попытки браузера обнаружить другие Content-Type помимо того, который был послан в действительности (мера противодействия XSS-атакам).
Правило 9
Проводите валидацию Content-Type
- Стоит настроить отклонение запросов, если их Content-Type отличается от их содержимого. Это позволит снизить риск неверной обработки данных, которая может привести к инъекции или исполнению кода у сервера/клиента.
- Также стоит отклонять запросы, Content-Type которых вы не поддерживаете, или у которых данный заголовок вообще отсутствует. Избегайте также прямых ответов о том, какой Content-Type функция принимает/выдает, если это не является необходимым (это поможет избежать XXE-атак).
- В REST сервисах является обычным делом поддерживать несколько типов ответов (например, json и xml), и клиент указывает предпочтительный тип ответа в заголовке Accept при запросе. Избегайте копирования содержимого заголовка Accept в Content-Type ответа в качестве механизма выставления данного заголовка. Отклоняйте также запросы, у которых заголовок Accept прямо не содержит хотя бы один из поддерживаемых типов.
Правило 10
Не забывайте про настройку Cross-Origin Resource Sharing (CORS)
CORS — это стандарт, описывающий работу с кросс-доменными запросами. Если ваше приложение не поддерживает CORS, отключите работу с этим типом заголовков. Если же его использование у вас необходимо, политика доступа должна быть максимально конкретной и строгой.
Правило 11
Не раскрывайте параметры в URL
Все критичные данные (пароли, ключи, токены и логины желательно тоже) должны находиться внутри тела запроса или в заголовках, но ни в коем случае не появляться в URL. Если вам необходимо передать конфиденциальные данные через метод GET, то поместите их в заголовок.
Нельзя:
example .com/controller/123/action?apiKey=a53f435643de32
Можно:
example .com/controller/123/action
Заголовки:
Host: example.com
User-Agent: Mozilla …
X-APIkey: a53f435643de32
Правило 12
Задумайтесь над защитой от CSRF атак
Подробнее про все виды защиты вы можете прочитать здесь, а так, самым популярным способ снижения риска проведения атак данного типа считается использование CSRF-токенов.
Правило 13
Изучите свой фреймворк
Если для реализации REST в своем приложении вы используете какой-либо фреймворк, то стоит изучить его, а не слепо использовать, как это часто делают. Прочтите внимательно документацию, особенно ту часть, которая касается безопасности. Не оставляйте его работать на настройках по умолчанию! У каждого фреймворка есть свои нюансы, особенно когда это касается безопасности, так что знать их очень важно.
catanfa
По поводу правила 11 про пароли в урле — это делается для того, чтобы чувствительная информация не осела в истории браузера, или каких-нибудь логах на промежуточных узлах? Или есть другие причины?
mayorovp
Ещё чтобы её никто не скопировал и не выложил в интернете. Например, задавая вопрос на SO.
BOM
Как пример, один из кейсов — если какие-то параметры передаются в урле, то можно легко заставить пользователя пройти по такому урлу, совершив нежелательное действие. Причем, необязательно даже заставлять кликать по ссылке, можно просто вставить этот урл в качестве src атрибута тега img. Мало того, что браузер сделает ненужный пользователю GET запрос, так еще и любезно отправит все пользовательские куки.
А вот заставить браузер сделать запрос на какой-то урл определенными методом, заголовками и телом запроса задача намного более сложная и нетривиальная. Обычной ссылкой тут не обойдешься.
Leozaur
Если GET делает какое-то действие (не говоря уж о «нежелательное») — то руки отрывать надо за такой REST. И не забываем ограничивать CORS только известными нам доменами (собственными или зарегистрированных сторонних приложений), если есть авторизация пользователя.
dehimb
Как обычно все зависит от задачи. Есть случаи, когда твоим апи пользуются люди без технических навыков, и они не могут подставить свой ключ в заголовок. Для них и делают параметры в гете, чтобы из браузера можно было дернуть.
Небезопасно — да. Можно сделать форму — да. Но в быстро развивающихся продуктах задачи могут меняться каждый день и безопасность жертвуют в пользу скорости. Тут уже клиент сам отвечает за сохранность своего ключа
mayorovp
Людям без технических навыков нужны не параметры в гете, а официальный клиент...
dehimb
Какой клиент, вы о чем? Перейти по ссылке и увидеть ответ — вот что хотят пользователи. 11 правило спорное, так как не всегда решает бизнес задачи. Вот взять к примеру shodan — их api поддерживает передачу api ключа в гет параметре
mayorovp
… в формате json? Странные какие-то пользователи.
dehimb
На практике поставить JSONView в хром проще чем объяснять как сделать POST запрос или добавить заголовок.
Разрабатывать отдельного клиента/страницу для демонстрации работы запросов бывает нецелесообразно
Бывает пока апишка дойдет до технических специалистов ее хотят подергать юристы/бухгалтеры/seo/ceo/уборщицы. Там же можно дать тестовые ключи для демо. Сценариев было много, когда простая ссылка решала много проблем.
Вобщем я просто не согласен с тем, что данные типа ключей надо обязательно выпиливать из параметров. На практике это может быть излишне. Shodan тоже так думает )