- PVSM.RU - https://www.pvsm.ru -
Умение реализовать грамотное REST API — полезный навык в наше время, т.к. все больше сервисов предоставляют свои возможности с помощью API. Но разработка REST API не ограничивается реализацией HTTP запросов в определенном стиле и формированием ответов в соответствии со спецификацией. Задача обеспечения безопасности REST API не так очевидна, как, например, обеспечение безопасности баз данных, но ее необходимость не менее важна.
В настоящее время многие онлайн системы с помощью API передают приватные данные пользователей, такие как медицинские или финансовые. Текущая же ситуация с безопасностью в веб-приложениях весьма печальна: по данным Comnews [1] порядка 70% содержат критические уязвимости. Поэтому всем, кто участвует в проектировании, реализации и тестировании онлайн систем, важно иметь общую картину по существующим угрозам и способам обеспечения безопасности как всей системы, так и используемого REST API.
В статье я попытался обобщить информацию о существующих уязвимостях REST API, чтобы у читателей сложилась общая картина. На схемах представлена современная архитектура клиент-сервер и обобщенный REST API запрос с потенциальными угрозами безопасности. Далее я подробнее расскажу об этих угрозах, и как технически реализовать защиту от них.
Начнем со стандартов. Существует несколько стандартов, которые помогут нам сформулировать список требований к безопасности API:
OWASP [2] (Open Web Application Security Project) известна своими списками рисков в разных программных технологиях. Нам интересен список «10 наиболее опасных уязвимостей при разработке API» [3]:
Добавлю пункты, которые не вошли в Top10 [4], но относятся к нашей теме:
А также уязвимости из списка другой организации Common Weakness Enumeration (CWE): CWE Top 25 Most Dangerous Software Errors [8]:
И несколько пунктов из других найденных списков:
В результате получился список, который, на мой взгляд, достаточно полно отражает современные проблемы безопасности API. Для проверки того, что список получился общим и применимым для всех технологий я использовал рекомендации по безопасности API, найденные на просторах Интернета (ссылки приведены в конце статьи). Далее рассмотрим все перечисленные пункты.
Тема аутентификации пользователей идет на втором месте в списке OWASP, но я ее поставил на первое, т.к. с этого все начинается. Современные стандарты аутентификации и авторизации я уже рассматривал в своей статье про OAuth 2.0, OpenID Connect, WebAuthn [9]. Здесь кратко опишу основные схемы безопасности и рассмотрим более подробно наиболее надежную на данный момент схему, основанную на токенах.
API Key — это строка символов, которую передает клиент в запросах к серверу. Для успешной аутентификации строка должна совпадать у клиента и у сервера. Данная схема обеспечивает защиту от несанкционированного использования API и позволяет осуществлять, например, проверку лимитов использования API.
В Basic Authentication используется аутентификация по двум строкам, например логину/паролю.
Для передачи информации используется HTTP заголовок 'Authorization' с ключевым словом Basic далее пробел и base64 закодированная строка username:password. Например:
Authorization: "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
Cookie-Based Authentication использует механизм передачи Cookies в HTTP запросах. В ответ на запрос клиента сервер посылает заголовок Set-Cookie, который содержит имя и значение cookie, а также дополнительные атрибуты: expires, domain, path, secure, httponly. Пример отправки cookie:
Authorization: Set-Cookie: JSESSIONID=123456789; Path=/; HttpOnly
После этого клиент автоматически будет посылать заголовок Cookie при каждом запросе:
Cookie: JSESSIONID=123456789
Для реализации этого механизма необходимо на сервере организовать хранение и проверку сессий пользователей. Подробнее использование Cookies рассмотрено в разделе «Insecure Cookies and Local Storage»
Также называют Bearer Authentication.
Token-Based Authentication использует подписанный сервером токен (bearer token), который клиент передает на сервер в заголовке Authorization HTTP с ключевым словом Bearer или в теле запроса. Например:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjI4Y
При получении токена сервер должен проверять его на валидность — что пользователь существует, время использования не прошло и т.д. Token-Based Authentication может использоваться как часть OAuth 2.0 или OpenID Connect протоколов, так и сервер сам может сформировать токен.
При любом способе аутентификации для безопасного использования должен использоваться протокол, который обеспечивает шифрование данных, HTTP заголовков и URL, например HTTPS.
Разберем подробнее последнюю из описанных схем. На схеме представлен упрощенный алгоритм Token-Based Authentication на примере реализации возможности «Зайти с помощью Google аккаунта»
Какие плюсы Token-Based Authentication для сервера приложений:
Минус видится один:
В случае перехвата токена, злоумышленник может какое-то время выдавать себя за владельца токена. Поэтому для передачи токена надо обязательно использовать HTTPS.
Другое название этого риска: Insecure Direct Object References (Небезопасные прямые ссылки на объекты). Это самая распространенная проблема с API в настоящее время. Для иллюстрации приведу API, которое в дальнейшем использую еще для нескольких примеров уязвимостей.
Получить одного пользователя с userID:
GET /users/{userID}
Получить всех пользователей (может только администратор):
GET /users
Удалить пользователя c userID: DELETE /users/{userID}
DELETE /users/1
Итак, если вызывается команда удаления пользователя:
DELETE /users/1
То необходима проверка, что эту команду может вызвать только сам пользователь 1 или администратор, а не, например, пользователь 2 от своего имени, просто изменив значение ID в вызове команды. Чтобы избежать подобных проблем нужно:
Должна быть разработана четкая система разграничения доступа между ролями пользователей API. Например, есть роль: обычные пользователи и роль: администраторы. Команду по просмотру всех пользователей может вызвать только администратор:
GET /users/all
При каждом вызове команды необходима проверка прав доступа, чтобы обычный пользователь не мог вызвать команду, только изменив формат.
На самом деле пункт называется — предоставление излишних данных, но при этом как раз и может происходить разглашение конфиденциальных или персональных данных. Как такое получается? На запрос клиента сервер, как правило, формирует запрос к базе данных, которая возвращает запись или список записей. Эти данные зачастую сериализируются в JSON без проверок и отправляется клиенту с предположением, что клиент сам отфильтрует нужные данные. Но проблема в том, что запрос может отправить не только клиент, а может сформировать злоумышленник напрямую к серверу и получить конфиденциальные данные. Например, безобидный запрос данных по пользователю с ID 1:
GET /users/1
может вернуть не только имя / возраст, но и ответ на секретный вопрос, который пользователь задал во время регистрации:
{"userName":"Alex","age":25,"secretAnswer":"HelloWorld"}
Это и называется излишняя передача данных. Проблема усугубляется тем, что лишних данных может быть еще и просто много по объёму. При больших нагрузках это приведет к сетевым проблемам. Соответственно, при разработке API нельзя полагаться на фильтрацию данных в клиенте — все данные должны фильтроваться на сервере.
В данном случае ситуация обратная предыдущему пункту Excessive Data Exposure — лишние данные передаются на сервер с целью несанкционированной замены значений. Как это понимать? Предположим у нас есть пользователь-хакер с ID 1 со следующими данными:
{"userName":"Alex","age":25,"balance":"150"}
Некоторые поля записей пользователь может легитимно менять сам, например, свой возраст. А поля, такие как balance должны устанавливать внешние системы.
Но наш сервер подвержен атаке Mass Assignment и без проверок источника записывает все пришедшие данные. Наш пользователь-хакер может отправить на сервер запрос на изменение возраста, в который добавляет дополнительный атрибут balance:
POST /users/1
{"userName":"Alex","age":26,"balance":"1000000"}
После этого баланс увеличится без внесения реальных денег. Чтобы предотвратить данную атаку необходимо:
Необходимо защитить сервер от атак по подбору пароля (brute force attack). Для этого нужно реализовать следующие ограничения:
Для JS существуют средства, позволяющие делать такие проверки автоматически (например, Rate limiter [10]) и сразу посылать ответ «429 Too Many Requests», не нагружая сервер.
Необходимо защитить сервер и от отказа в обслуживании (DoS-атаки)
Например, сервер ожидает в параметре size число записей:
/api/users?page=1&size=100
Если на сервере отсутствует проверка size на максимальное значение, то передача в параметре злоумышленником, например, 1 000 000 может привести к исчерпанию памяти на сервере и отказу в обслуживании. Поэтому нужно проверять на сервере все значения параметров на допустимые, даже если на нашем клиенте есть такие проверки. Ведь никто не помешает вызвать API напрямую.
Следующие действия могут привести к проблемам с безопасностью, соответственно, их надо избегать:
Также необходимо уделять внимание конфигурации облачных сервисов:
Внедрение — это выполнение программного кода, не предусмотренного системой. Разделяют внедрения:
Атака будет успешна, если сервер выполняет полученные команды без проверки. Чем-то напоминает «небезопасную десериализацию», только используются не дополнительные атрибуты, а SQL код или команды OS. В результате SQL инъекции можно получить несанкционированный доступ к данным. С помощью инъекции команд OS можно получить доступ к серверу или вывести его из строя. Например, в строке ввода пользователь ввел имя каталога «name» и на сервер посылается команда:
GET /run
{"mkdir":"name"}
Если сервер выполняет команды без проверки, то злоумышленник может послать следующую команду с большой вероятностью вывода сервера из строя:
GET /run
{"mkdir":"name && format C:/"}
Для предотвращения подобных атак:
API может иметь несколько точек входа (endpoints) с разными версиями и функциональными назначениями. Например:
http://localhost:5000/myAPI/v1
http://localhost:5000/myAPI/v2
http://localhost:5000/myTestAPI/v1
Необходимо обеспечить учет и контроль версий API:
Если не обеспечить подобный контроль, то возможно использование API в непредусмотренных целях. Например, злоумышленник обнаружит рабочий endpoint с версией API, которая имела уязвимости в безопасности, но была оставлена на время перехода на новую безопасную версию, но так и осталась в эксплуатации.
Чтобы выявить атаку или подозрительное поведение пользователей, систему надо мониторить, а события логировать с достаточным уровнем подробности:
Если не шифровать трафик между клиентом и сервером, то все HTTP данные и заголовки будут передаваться в открытом виде. Чтобы предотвратить утечку данных, надо использовать протокол HTTPS (Hyper Text Transfer Protocol Secure) или реализовывать шифрование самостоятельно. Для использования HTTPS нужен SSL-сертификат. Сайты в интернете должны получать такие сертификаты в доверительных центрах выдачи сертификатов CA (Certificate Authority). Но для целей шифрования данных между нашим клиентом и сервером можно поступить проще:
Браузер, конечно, будет ругаться, что сертификат сервера подписан не известно кем, но мы-то можем сами себе доверять).
Обеспечить поддержку HTTPS можно также средствами Apache, Nginx или других веб-серверов.
С этой темой все просто:
Cookies должны использоваться безопасно:
secure - браузер будет отправлять cookies только по HTTPS протоколу.
httpOnly - браузер будет отправлять cookies только по HTTP или HTTPS и не отправлять при запросах из JavaScript, что предотвратит атаки Cross-site Scripting (XSS).
domain - определяет domain cookie.
path - определяет path cookie.
expires - определяет дату устаревания cookies.
SameSite - браузер будет отправлять cookies только тому сайту, который их установил.
Общие правила безопасности для Cookies и Local Storage:
Компоненты, такие как библиотеки и framework-и выполняются с теми же привилегиями, что и приложение. Поэтому если среди используемых библиотек окажется небезопасный компонент, то это может привести к захвату или выводу из строя сервера. Для проверки безопасности компонент используются специальные приложения, например, для JavaScript можно использовать Retire [15].
Межсайтовое выполнение скриптов считается самой опасной web-атакой. Суть ее в том, что вредоносный скрипт может быть внедрен в нашу страницу, а результат выполнения может привести к утечке конфиденциальных данных или к повреждению сервера. Чтобы защититься от атаки в запрос надо включить HTTP заголовок, который включает Cross-site scripting (XSS) фильтр:
X-XSS-Protection : 1; mode=block
или
Content-Security-Policy: script-src 'self'
Для понимания сути атаки приведу пример: предположим, есть финансовая организация с онлайн кабинетом. В Cookies запоминается пользователь, чтобы при входе ему не надо было каждый раз вводить свой логин/пароль. Пользователь случайно заходит на сайт злоумышленника, который отправляет в финансовую организацию транзакцию на перевод денег, в которую браузер автоматически помещает данные из запомненных Cookies.
Финансовый сайт успешно проверяет валидность Cookies и выполняет несанкционированную транзакцию. Для защиты от атак CSRF надо:
CORS — это механизм безопасности, который позволяет серверу задать правила доступа к его API. Например, если на сервере установить заголовок:
Access-Control-Allow-Origin: *
то это позволит использовать API без ограничения. Если это не публичное API, то для безопасности надо явно устанавливать Origin-ы, с которых разрешен доступ к API, например:
Access-Control-Allow-Origin: https://example.com:8080
Также можно ограничивать HTTP методы, которые могут быть использованы для доступа к API:
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
И задать список заголовков, которые сервер может принимать:
Access-Control-Allow-Headers: Origin, Content-Type, Authorization
HTTP протокол включает в себя большое число заголовков, которые можно использовать в HTTP запросах/ответах. Для того, чтобы определить наиболее важные заголовки с точки зрения обеспечения безопасности, я использовал несколько списков:
Далее рассмотрим основные заголовки:
Этот заголовок автоматически вставляется некоторыми серверами, что дает понять злоумышленнику, с каким сервером он имеет дело, например:
X-Powered-By: Express
Отсутствие этого заголовка, конечно, никого не остановит, но сразу давать такую подсказку не стоит. Поэтому передачу этого заголовка надо запретить.
Strict-Transport-Security заголовок запрещает браузеру обращаться к ресурсам по HTTP протоколу, только HTTPS:
Strict-Transport-Security: max-age=31536000
max-age=31536000 — это год в секундах. Рекомендуется выcтавлять этот заголовок, т.к. он предотвратит атаки, связанные с принуждением браузера перейти на HTTP протокол и начать передавать информацию (например cookies) в открытом виде, которую может перехватить злоумышленник. Запрос к серверу по HTTP и атака возможна только при первом обращении к серверу, при последующих браузер запомнит настройку Strict-Transport-Security и будет обращаться только по HTTPS.
Позволяет защититься от атаки Clickjacking. Так называется технология, когда злоумышленник помещает кнопку или поле ввода в прозрачный фрейм и пользователь думает, что он нажимает нужную кнопку или безопасно вводит данные, а на самом деле идет перенаправление на другой ресурс, полезный атакующему, например, на сайт с навязчивой рекламой. Для защиты от Clickjacking сервер должен посылать запрет использовать страницу во фрейме вообще:
X-Frame-Options: deny
или разрешить использование только в нашем домене:
X-Frame-Options: sameorigin
А лучше для предотвращения атаки Clickjacking использовать более современный механизм и установить правильную политику безопасности Content-Security-Policy
Позволяет защититься от атаки Cross-site scripting и других кросс-сайтовых инъекций, в том числе Clickjacking. Требует вдумчивого конфигурирования, т.к. параметров много. Но надо хотя бы поставить дефолтную политику, что предотвратит возможность атаки Cross-site Scripting:
Content-Security-Policy: default-src 'self'
Подробно значения заголовка Content-Security-Policy разбираются, например, по ссылке [18].
Установка данного заголовка запрещает браузеру самому интерпретировать тип присланных файлов и принуждает использовать только тот, что был прислан в заголовке Content-Type. Без этого возможна ситуация, когда, например, посылается безобидный на вид txt файл, внутри которого вредоносный скрипт и браузер его выполняет как скрипт, а не как текстовой файл. Поэтому устанавливаем:
X-Content-Type-Options: nosniff
Cache-Control позволяет управлять кешом на стороне клиента, рекомендуется запретить кеширование, чтобы в кеше случайно не оставались приватные данные:
Cache-Control: no-store
В статье мы рассмотрели угрозы, которые подстерегают API при его эксплуатации и способы борьбы с ними. В заключении приведу несклько общих выводов:
Желаю всем легкодоступных, но безопасных API! )
It's only the beginning!
Автор: Алексей Сушков
Источник [22]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/353354
Ссылки в тексте:
[1] по данным Comnews: https://www.comnews.ru/content/207166/2020-05-19/2020-w21/veb-prilozheniya-pod-udarom
[2] OWASP: https://owasp.org/
[3] «10 наиболее опасных уязвимостей при разработке API»: https://owasp.org/www-project-api-security/
[4] не вошли в Top10: https://owasp.org/www-community/vulnerabilities/
[5] Insecure Transport: https://owasp.org/www-community/vulnerabilities/Insecure_Transport
[6] Insecure Passwords: https://owasp.org/www-community/vulnerabilities/Password_Plaintext_Storage
[7] Using Components with Known Vulnerabilities: https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A9-Using_Components_with_Known_Vulnerabilities
[8] CWE Top 25 Most Dangerous Software Errors: https://cwe.mitre.org/top25/archive/2019/2019_cwe_top25.html
[9] OAuth 2.0, OpenID Connect, WebAuthn: https://habr.com/ru/post/491116/
[10] Rate limiter: https://www.npmjs.com/package/ratelimiter
[11] Regular expression Denial of Service: https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
[12] Monitoring Done Right: https://medium.com/hackernoon/node-js-monitoring-done-right-70418ecbbff9
[13] Argon2: https://en.wikipedia.org/wiki/Argon2
[14] должны явно спрашивать: https://www.privacypolicies.com/blog/eu-cookies-directive/
[15] Retire: https://www.npmjs.com/package/retire
[16] OWASP Secure Headers Project: https://owasp.org/www-project-secure-headers/
[17] Production Best Practices: https://expressjs.com/en/advanced/best-practice-security.html
[18] по ссылке: https://content-security-policy.com/
[19] API Security Top 10: https://owasp.org/www-project-api-security
[20] Рекомендации RedHat: API security: https://www.redhat.com/en/topics/security/api-security
[21] Рекомендации Node.js: Security Checklist: https://blog.risingstack.com/node-js-security-checklist
[22] Источник: https://habr.com/ru/post/503284/?utm_source=habrahabr&utm_medium=rss&utm_campaign=503284
Нажмите здесь для печати.