[Перевод] [в закладки] 23 рекомендации по защите Node.js-приложений
В наши дни веб-сервисы постоянно подвергаются самым разным атакам. Поэтому безопасность — это то, о чём стоит помнить на всех этапах жизненного цикла проектов. Авторы материала, перевод которого мы сегодня публикуем, поддерживают репозиторий на GitHub, содержащий около 80 рекомендаций по обеспечению безопасности приложений, работающих на платформе Node.js. В этом материале, базой для которого послужило множество публикаций, посвящённых безопасности, собрано более двух десятков рекомендаций, касающихся Node.js, и некоторые советы общего характера. При этом данный материал покрывает топ-10 уязвимостей из списка проекта OWASP.
1. Используйте правила для линтера, направленные на проверку безопасности кода
Рекомендации
В ходе разработки используйте плагин для линтера, ориентированный на безопасность, такой, как eslint-plugin-security. Это позволяет выявлять уязвимости и проблемы безопасности очень рано — в момент написания соответствующего кода. Такой подход помогает находить слабые места в безопасности программ. Среди них — использование команды eval
, вызов дочерних процессов, импорт модулей с передачей в соответствующую команду чего-то, отличающегося от строкового литерала (скажем, некоей строки, сформированной на основе данных, переданных на сервер пользователем).
→ Вот полезный материал о правилах линтера
Адам Болдуин говорит о линтинге следующее: «Линтер не должен быть всего лишь инструментом, педантично следящим за применением правил, касающихся количества используемых пробелов, расстановки точек с запятой и использования команды eval. ESLint даёт разработчику мощную платформу, позволяющую обнаруживать в коде широкий спектр потенциально опасных шаблонов и устранять их. Речь идёт, например, о регулярных выражениях, о проверке пользовательского ввода, и так далее. Я полагаю, что линтер даёт разработчикам, которых заботят вопросы безопасности, новый мощный инструмент, которому стоит уделить внимание».
Возможные проблемы
То, что во время разработки выглядит как мелкий недочёт в системе безопасности, в продакшне становится серьёзной уязвимостью. Кроме того, если все разработчики проекта не следуют единообразным правилам безопасности при работе с кодом, это может привести к появлению в нём уязвимостей, или, например, к попаданию конфиденциальных данных в общедоступные репозитории.
2. Ограничивайте количество одновременных запросов к серверу
Рекомендации
DoS-атаки весьма популярны среди злоумышленников, проводить их сравнительно просто. Реализовать систему ограничения количества запросов к приложению можно с использованием внешнего сервиса, такого, как облачный балансировщик нагрузки, облачный файрвол, nginx-сервер, или, для небольших приложений, не являющихся критически важными, с использованием промежуточного ПО для ограничения числа запросов вроде express-rate-limit.
→ Вот материал о реализации системы ограничения частоты запросов к серверу
Возможные проблемы
Приложение, в котором не предусмотрена система ограничения количества одновременных запросов, может быть подвергнуто атаке, которая приведёт к его отказу. Это выражается в том, что пользователи такого приложения либо будут испытывать затруднения при работе с ним, либо совсем не смогут с ним взаимодействовать.
3. Убирайте конфиденциальные сведения из конфигурационных файлов или шифруйте их
Рекомендации
Никогда не храните конфиденциальные данные в виде обычного текста в конфигурационных файлах или в коде. Вместо этого используйте системы управления конфиденциальными данными вроде продуктов Vault или систем Kubernetes/Docker Secrets, либо применяйте для хранения таких данных переменные среды. Конфиденциальные данные, сохранённые в системе контроля версий, должны быть зашифрованы, нужно принять меры по их безопасному хранению и использованию. Среди таких мер — применение динамических ключей, использование сроков действия паролей, аудит безопасности, и так далее. Используйте системы для проверки кода перед коммитом или перед отправкой в репозиторий для предотвращения случайной отправки в общедоступный репозиторий конфиденциальных данных.
→ Вот материал об управлении конфиденциальными данными
Возможные проблемы
Даже если код хранится в закрытом репозитории, однажды он, по ошибке, может стать общедоступным. В этот момент все хранящиеся в нём конфиденциальные данные превратятся во всеобщее достояние. В результате доступ третьих лиц к репозиторию с кодом непреднамеренно приведёт к тому, что они получат доступ и к связанным с ним системам (базы данных, API, сервисы, и так далее).
4. Предотвращайте уязвимости, связанные с внедрением кода
Рекомендации
Для того чтобы предотвратить SQL/NoSQL-инъекции и другие подобные атаки, всегда применяйте ORM/ODM-библиотеки или механизмы СУБД, направленные на очистку данных, или поддерживающие именованные или индексированные параметризованные запросы, и занимающиеся проверкой того, что поступает от пользователя. Никогда не пользуйтесь, для внедрения неких значений в тексты запросов, лишь шаблонными строками JavaScript, или конкатенацией строк, так как такой подход открывает ваше приложение для широкого спектра уязвимостей. Все достойные уважения библиотеки для Node.js, используемые для работы с данными (например, Sequelize, Knex, mongoose) содержат встроенную защиту от атак путём внедрения кода.
→ Вот материал о предотвращении инъекций с использованием ORM/ODM-библиотек
Возможные проблемы
Использование в запросах непроверенных и неочищенных данных, получаемых от пользователя, может привести к атаке путём внедрения оператора при работе с NoSQL-базой данных вроде MongoDB. Если не использовать систему очистки данных или ORM-библиотеку при работе с SQL-базой данных, это приведёт к возможности осуществления атаки путём SQL-инъекции, что создаёт огромную брешь в системе безопасности приложения.
5. Уклоняйтесь от DoS-атак, явно задавая условия аварийного завершения процесса
Рекомендации
Процесс Node аварийно завершает работу при возникновении необработанной ошибки. Но во многих рекомендациях, отражающих передовой опыт разработки для Node, рекомендуется завершать процессы даже тогда, когда возникшая ошибка была перехвачена и обработана. Express, например, аварийно завершится при возникновении любой асинхронной ошибки — если только маршруты не будут обёрнуты в выражения catch
. Этот факт открывает атакующим весьма привлекательную возможность. Они, обнаружив, что при поступлении определённого запроса процесс аварийно завершается, начинают отправлять ему именно такие запросы. Нет рекомендации, которая позволила бы одним махом решить эту проблему, однако, некоторые приёмы могут её смягчить. Так, при завершении процесса из-за необработанной ошибки, нужно уведомлять администратора, давая такому уведомлению высший приоритет важности. Надо проверять то, что приходит процессу в запросах и избегать ситуаций аварийного завершения процесса из-за запросов, которые, случайно или намеренно, сформированы неправильно. Все маршруты нужно оборачивать в выражения catch
и настроить систему так, чтобы, если причиной ошибки является запрос, процесс не завершался бы аварийно (в противоположность тому, что происходит на глобальном уровне приложения).
Возможные проблемы
Проанализируем следующую ситуацию. Имеется множество Node.js-приложений. Что произойдёт, если мы начнём отправлять им POST-запросы с пустым JSON в виде тела запроса? Это приведёт к тому, что многие из этих приложений аварийно завершатся.
Теперь, если бы мы играли роль злоумышленников, нам, для того, чтобы приложения продолжали давать сбои, достаточно было бы продолжать отправлять им подобные запросы.
6. Настройте заголовки HTTP-ответов для повышения уровня безопасности проекта
Рекомендации
Приложение должно использовать HTTP-заголовки, ориентированные на безопасность, для того, чтобы не дать злоумышленникам прибегнуть к таким распространённым приёмам совершения атак, как межсайтовый скриптинг (XSS), кликджекинг, и другие. Настроить заголовки несложно с использованием специальных модулей, таких, как helmet.
→ Вот материал об использовании безопасных заголовков
Возможные проблемы
Если безопасные HTTP-заголовки не используются, злоумышленники смогут выполнять атаки на пользователей ваших приложений, что ведёт к огромным уязвимостям.
7. Постоянно, в автоматическом режиме, проверяйте свои проекты на использование в них уязвимых зависимостей
Рекомендации
В экосистеме NPM проекты, имеющие множество зависимостей — это весьма распространённое явление. Зависимости всегда нужно контролировать, учитывая обнаружение новых уязвимостей. Используйте для обнаружения, мониторинга и исправления уязвимых зависимостей инструменты вроде npm audit, nsp или snyk. Встройте эти инструменты в свою систему непрерывной интеграции. Это позволит вам обнаруживать уязвимые зависимости до того, как они попадут в продакшн.
→ Вот материал о безопасности зависимостей проектов
Возможные проблемы
Злоумышленник может определить используемый в проекте веб-фреймворк и провести атаки на все его известные уязвимости.
8. Постарайтесь не использовать для обработки паролей стандартный модуль Node.js crypto, вместо этого применяйте Bcrypt
Рекомендации
Пароли или другие конфиденциальные данные (ключи API, например) нужно хранить, обрабатывая их криптографическими функциями с применением «соли», такими, как Bcrypt. Стоит использовать именно нечто подобное, а не стандартный модуль Node.js crypto
из соображений безопасности и производительности.
→ Вот материал о Bcrypt
Возможные проблемы
Пароли или некие конфиденциальные данные, которые хранятся без применения соответствующих мер их защиты, уязвимы к атакам методом грубой силы и к атакам по словарю, которые, в итоге, приводят к раскрытию таких данных.
9. Используйте системы экранирования символов в HTML, JS и CSS-данных, отправляемых пользователю
Рекомендации
Если в браузер пользователя отправляются некие данные из недоверенного источника, то, даже если они должны быть просто выведены на экран, такие данные могут представлять собой код, который может быть выполнен. Обычно подобное называют межсайтовым скриптингом (XSS). Снизить риск возможности проведения подобных атак можно, используя специальные библиотеки, которые обрабатывают данные так, что они не могут быть выполнены. Это называют кодированием или экранированием данных.
→ Вот материал об экранировании выходных данных
Возможные проблемы
Если не заботиться об экранировании данных, атакующий может, например, сохранить в вашей базе данных вредоносный JavaScript-код, который может быть передан клиентам в неизменном виде и запущен.
10. Проверяйте JSON-данные, поступающие на сервер
Рекомендации
Контролируйте содержимое тел входящих запросов, проверяя, соответствуют ли они тому, что вы ждёте увидеть в подобных запросах. Если запрос выглядит не так, как ожидается, быстро прекращайте его обработку. Для того чтобы избежать трудоёмкой операции написания кода проверки запросов для каждого маршрута, вы можете воспользоваться легковесными JSON-средствами для валидации данных, такими, как jsonschema или joi.
→ Вот материал о проверке входящих JSON-данных
Возможные проблемы
Если сервер радушно принимает любые запросы, не осуществляя их тщательной проверки, это значительно увеличивает поверхность атаки приложения и вдохновляет злоумышленников на то, чтобы, опробовав множество запросов, найти такие из них, которые приводят к «падению» системы.
11. Поддерживайте чёрные списки JWT-токенов
Рекомендации
При использовании JWT-токенов (например, если вы работаете с Passport.js), по умолчанию, нет стандартного механизма для отзыва привилегий на доступ к системе для уже выпущенных токенов. Если даже вы обнаружили, что некий пользователь делает что-то явно ненормальное, у вас нет способа, через механизм токенов, закрыть ему доступ в систему, до тех пор, пока у него имеется действительный токен. Смягчить эту проблему можно, реализовав чёрный список недоверенных токенов, валидация которых производится при каждом запросе.
→ Вот материал о чёрных списках JWT-токенов
Возможные проблемы
Попавшие не в те руки токены могут быть использованы злоумышленником. Он сможет получить доступ к приложению и выдать себя за обычного пользователя — владельца токена.
12. Ограничьте количество попыток входа в систему
Рекомендации
В приложениях, основанных на express, для защиты от атак методом грубой силы и от атак по словарю, стоит использовать соответствующее промежуточное ПО, такое, как express-brute. Подобным образом нужно защищать особо важные маршруты, такие, как /admin
или /login
. Защита должна быть основана на анализе свойств запросов, таких, как использованное в запросе имя пользователя или другие идентификаторы, такие, как параметры тела запроса.
→ Вот материал об ограничении количества попыток входа в систему
Возможные проблемы
Если приложение не ограничивает количество попыток входа в систему, то атакующий может, в автоматическом режиме, отправить вашей системе неограниченное количество запросов на вход, например, пытаясь получить доступ к привилегированной учётной записи.
13. Запускайте Node.js от имени пользователя, не обладающего root-привилегиями
Рекомендации
Чрезвычайно распространён сценарий, при использовании которого Node.js запускается от имени root-пользователя с неограниченными привилегиями. Например, именно так всё по умолчанию настроено в контейнерах Docker. Рекомендуется создавать пользователя, не обладающего root-правами, и либо встраивать его в образ Docker, либо запускать процесс от имени этого пользователя, вызывая контейнер с флагом -u username
.
→ Вот материал о запуске Node.js от имени пользователя, не обладающего root-правами
Возможные проблемы
Если Node.js выполняется под учётной записью root-пользователя, то атакующий, который смог запустить на сервере некий скрипт, получает неограниченные возможности на локальной машине. Скажем, он может поменять настройки iptable
и перенаправить трафик на собственный компьютер.
14. Ограничьте объёмы данных, передаваемых в запросах, с использованием обратного прокси-сервера или промежуточного ПО
Рекомендации
Чем больше объём данных, находящихся в теле запроса, тем сложнее однопоточному серверу такой запрос обработать. Использование запросов больших размеров даёт атакующему возможность завалить сервер ненужной работой и без отправки ему огромного числа запросов (то есть, не выполняя DoS/DDoS-атаку). Снизить риск подобных атак можно, ограничивая размер тел входящих запросов на некоей пограничной системе (на файрволе или на балансировщике нагрузки), или настроив body-parser express на приём только пакетов, содержащих небольшой объём данных.
→ Вот материал об ограничении объёмов данных, передаваемых в запросах
Возможные проблемы
Если не ограничивать объём данных, передаваемых в запросах, злоумышленник может загрузить приложение обработкой больших запросов. В это время оно не сможет решать те задачи, на которые оно рассчитано. Это ведёт к падению производительности и делает приложение уязвимым для DoS-атак.
15. Избегайте применения функции eval в JavaScript
Рекомендации
Функция eval
— это зло, так как она позволяет выполнять произвольный JS-код, переданный ей во время выполнения программы. Причём, речь тут далеко не только о том, что этот код может замедлить работу приложения. Эта функция представляет собой серьёзную угрозу безопасности, так как в неё может попасть вредоносный JS-код, отправленный на сервер злоумышленником.
Кроме того, стоит избегать конструктора new Function
. Функциям setTimeout
и setInterval
никогда не нужно передавать динамически сформированный JS-код.
→ Вот материал об eval
Возможные проблемы
Если некто найдёт способ передать вредоносный JS-код, в виде текста, функции eval
или какому-то другому подобному механизму JS, у него будет полный доступ к странице, ко всему тому, что можно делать с помощью JavaScript. Эту уязвимость часто связывают с XSS-атаками.
16. Не допускайте перегрузки однопоточных Node.js-приложений вредоносными регулярными выражениями
Рекомендации
Регулярные выражения, хотя они и удобны, несут в себе угрозу для JavaScript-приложений в целом, и, в частности, для платформы Node.js. Обработка с помощью регулярного выражения того, что пришло от пользователя, может создать огромную нагрузку на процессор. Например, обработка регулярных выражений может быть настолько неэффективной, что проверка десяти слов может заблокировать цикл событий на несколько секунд и сверх меры нагрузить процессор. Поэтому лучше всего пользоваться сторонними пакетами для проверки строковых данных, вроде validator.js, вместо написания собственных регулярных выражений. Можно воспользоваться пакетом safe-regex для обнаружения уязвимых шаблонов регулярных выражений.
→ Вот материал о борьбе с вредоносными регулярными выражениями
Возможные проблемы
Плохо написанные регулярные выражения могут быть подвержены особому виду DoS-атак, в ходе которых полностью блокируется цикл событий. Например, в ноябре 2017 было обнаружено, что популярный пакет moment
уязвим к подобным атакам.
17. Избегайте загрузки модулей с использованием переменных
Рекомендации
Избегайте импорта файлов, путь к которым задаётся в виде параметра, исходя из соображения, в соответствии с которым этот параметр может быть задан на основе данных, поступающих от пользователя. Это правило можно расширить, включив сюда и, в целом, доступ к файлам (с использованием fs.readFile()
), и доступ к любым другим важным ресурсам с использованием параметров, получаемых от пользователя. Использование eslint-plugin-security позволяет очень рано выявлять подобные небезопасные паттерны.
→ Вот материал о безопасной загрузке модулей
Возможные проблемы
Данные, отправленные на сервер злоумышленником, могут оказаться в параметрах, которые отвечают за импорт неких файлов, среди которых может оказаться вредоносный файл, заранее загруженный на сервер. Эти данные могут быть использованы и для доступа к уже имеющимся на компьютере системным файлам.
18. Выполняйте небезопасный код в песочнице
Рекомендации
При возникновении необходимости выполнения внешнего кода, попадающего в приложение во время его работы (например, некоего плагина), используйте «песочницу», которая изолирует и защищает основной код от кода плагина. Подобную среду можно создать, используя выделенный процесс (cluster.fork()
), бессерверное окружение или выделенный npm-пакет, действующий как песочница.
→ Вот материал о выполнении небезопасного кода в песочнице
Возможные проблемы
Вредоносный код, содержащийся в плагине, может атаковать приложение огромным множеством способов. Среди них — бесконечные циклы, перегрузка памяти, доступ к переменным среды, содержимое которых надо держать в секрете.
19. Будьте особенно осторожны при работе с дочерними процессами
Рекомендации
Избегайте, когда это возможно, использования дочерних процессов. Если же без этого не обойтись, то проверяйте и очищайте данные, переданные пользователем и используемые при запуске таких процессов для того, чтобы снизить риск атаки на командную оболочку. При этом отдавайте предпочтение команде child_process.execFile
, которая, по умолчанию, не запускает командную оболочку, создавая новый процесс который соответствует запущенному исполняемому файлу, переданному ей.
→ Вот материал о мерах безопасности, необходимых для работы с дочерними процессами
Возможные проблемы
Необдуманное использование дочерних процессов может привести к удалённому выполнению команд или к атаке на командную оболочку, когда данные, передаваемые пользователем на сервер, в неочищенном виде, передаются системным командам.
20. Скрывайте сведения об ошибках от клиентов
Рекомендации
Интегрированный обработчик ошибок express, по умолчанию, скрывает сведения об ошибках. Однако, велики шансы того, что вы реализовали собственную систему обработки ошибок, создали собственный объект Error
(многие рекомендуют именно такой подход). Если вы так поступили, проверьте, чтобы клиенту не отправлялся бы весь такой объект, так как он может содержать какие-либо данные приложения, которые не должны попадать в чужие руки.
→ Вот материал о сокрытии сведений об ошибках от клиентов
Возможные проблемы
Данные приложения, которые нельзя раскрывать посторонним, такие, как пути к файлам на сервере, сведения об используемых сторонних модулях и другие подробности о внутреннем устройстве приложения, которыми может воспользоваться атакующий, могут оказаться среди данных трассировки стека.
21. Настройте двухфакторную аутентификацию для npm или Yarn
Рекомендации
Каждый шаг в цепочке задач, из которых состоит процесс разработки веб-проекта, должен быть защищён многофакторной аутентификацией (MFA, multi-factor authentication). Доступ к npm или Yarn может быть весьма интересен атакующему, который может узнать учётные данные разработчика. Используя их, если речь идёт о внутренних ресурсах, он может встраивать вредоносный код в библиотеки, которые используются в проектах и сервисах компании. Если же подобные библиотеки публикуются в свободном доступе, то проблема становится гораздо более масштабной. Использование двухфакторной аутентификации, например, в npm, сводит практически к нулю шансы атакующего на перехват доступа к учётной записи разработчика и на изменение кода его пакетов.
Возможные проблемы
Что произойдёт, если атакующий получит доступ к учётной записи разработчика? Вот история про разработчика eslint, который попал в такую ситуацию.
22. Модифицируйте параметры сессии, используемые промежуточным ПО
Рекомендации
У каждого веб-фреймворка и у каждой технологии имеются известные слабости. Отличный способ помочь злоумышленнику, который хочет атаковать вашу систему — это сообщить ему о том, каким именно веб-фреймворком вы пользуетесь. Используя параметры сессии, заданные по умолчанию, что похоже на использование заголовка X-Powered-By
, промежуточное ПО может подвергнуть ваше приложению риску стать объектом атаки, нацеленной на определённый модуль или фреймворк. Попытайтесь скрыть всё, что позволяет идентифицировать используемый вами стек технологий (то есть, например, Node.js и express).
→ Вот материал о куки-файлах и безопасности сессии
Возможные проблемы
Куки-файлы могут передаваться по незащищённым соединениям. Перехватывая их, злоумышленник может использовать данные о сессии и идентифицировать фреймворк, используемый веб-приложением, а также — применяемые в нём модули. Это даст ему возможность организовать атаку на слабые места фреймворков и модулей.
23. Рекомендации по безопасности общего характера
Следующий список представляет собой перечень широко известных важных мер обеспечения безопасности, которые следует использовать в любом приложении. Это — универсальные рекомендации, которые касаются не только Node.js. Здесь можно найти расширенный перечень рекомендаций, сгруппированных в соответствии с классификацией OWASP.
- Для учётных записей пользователей с root-правами обязательно используйте двухфакторную или многофакторную аутентификацию.
- Организуйте частое изменение паролей и ключей доступа (включая SSH-ключи).
- Используйте правила, позволяющие формировать стойкие ко взлому пароли, применяемые и разработчиками, и пользователями приложений. Вот рекомендации OWASP, касающиеся паролей.
- Не разворачивайте приложения с использованием учётных данных, оставленных в значениях по умолчанию. В частности, это касается учётных данных администраторов систем.
- Используйте лишь стандартные методы аутентификации вроде OAuth, OpenID, и так далее. Старайтесь не применять механизм Basic Authentication.
- Ограничивайте частоту попыток аутентификации. Это выглядит как ограничение числа операций, связанных с аутентификацией (в том числе — операций по восстановлению пароля и прочих подобных), неким параметром X на отрезке времени, длительностью Y минут.
- Если попытка входа в систему оказалась неудачной — не сообщайте пользователю о том, что введено неверно — имя или пароль. Просто сообщите ему о том, что вход в систему не удался.
- Подумайте об использовании надёжной, проверенной временем, централизованной системы управления пользователями. Это позволит избежать необходимости в управлении множеством учётных записей для каждого сотрудника (например — это учётные записи в GitHub, AWS, Jenkins, и так далее).
Итоги
Безопасности никогда не бывает слишком много. Надеемся, этот материал поможет вам в защите ваших Node.js-проектов.
Уважаемые читатели! Приходилось ли вам сталкиваться с уязвимостями веб-проектов, которые приводили к реальным проблемам в области безопасности?