Топ-10 лучших практик безопасности Node.js

Node.js чрезвычайно популярен в наши дни, в первую очередь в качестве бэкенд-сервера для веб-приложений. Однако в мире микросервисов его можно встретить практически везде, играя различные и важные роли в большом стеке приложений.

Одним из преимуществ Node.js является возможность установки дополнительных модулей, что с точки зрения безопасности дает больше возможностей для открытия «черных ходов». Кроме того, чем популярнее фреймворк, тем больше шансов, что хакеры попытаются найти уязвимости. Поэтому всегда следует серьезно относиться к безопасности Node.js. В этом посте вы узнаете 10 лучших практик для обеспечения безопасности вашего приложения Node.js.

1. Валидация пользовательского ввода для ограничения SQL-инъекций и XSS-атак

Начнем с одной из самых популярных атак — SQL-инъекции. Как следует из названия, атака SQL-инъекции происходит, когда хакеру удается выполнить SQL-запросы в вашей базе данных. Это становится возможным, если вы не санируете входные данные с фронтенда. Другими словами, если ваш бэкенд Node.js берет параметр из данных, предоставленных пользователем, и использует его непосредственно как часть SQL-запроса. Например:

connection.query('SELECT * FROM orders WHERE id = ' + id, function (error, results, fields) {
  if (error) throw error;
  // ...
});
Войти в полноэкранный режим Выйти из полноэкранного режима

Приведенный выше запрос уязвим для SQL-инъекций. Почему? Потому что параметр id берется непосредственно из фронтенда. Вместо того чтобы отправить только id, злоумышленник может манипулировать запросом и отправлять SQL-команды вместе с ним. Вместо того чтобы отправить просто 4564 (идентификатор заказа), злоумышленник может отправить 4564; DROP TABLE ORDERS; и Node.js сотрет вашу базу данных.

Как избежать этого? Есть несколько способов, но основная идея заключается в том, чтобы не передавать вслепую параметры из фронтенда в запрос к базе данных. Вместо этого необходимо проверить или исключить значения, предоставленные пользователем. Как именно это сделать, зависит от используемой базы данных и способа, который вы предпочитаете. Некоторые библиотеки баз данных для Node.js выполняют экранирование автоматически (например, node-mysql и mongoose). Но вы также можете использовать более универсальные библиотеки, такие как Sequelize или knex.

XSS-атаки

Атаки межсайтового скриптинга (XSS) работают аналогично SQL-инъекциям. Разница в том, что вместо отправки вредоносного SQL злоумышленник может выполнить код JavaScript. Причина та же, что и раньше — отсутствие валидации ввода от пользователя.

app.get('/find_product', (req, res) => {
  ...
  if (products.length === 0) {
    return res.send('<p>No products found for "' + req.query.product + '"</p>');
  }
  ...
});
Вход в полноэкранный режим Выход из полноэкранного режима

Как видно из приведенного фрагмента, что бы пользователь ни ввел в поле поиска, если оно не найдено в базе данных, оно будет отправлено обратно пользователю в неизменном виде. Это означает, что если злоумышленник поместит код JavaScript вместо названия продукта в строку поиска, будет выполнен тот же код JavaScript.

Как это исправить? Опять же, проверять вводимые пользователем данные! Для этого можно использовать validatorjs или xss-фильтры.

2. Внедрите надежную аутентификацию

Неработающий, слабый или неполный механизм аутентификации занимает второе место среди наиболее распространенных уязвимостей. Возможно, это связано с тем, что многие разработчики думают об аутентификации как о «у нас это есть, значит, мы в безопасности». В действительности слабую или непоследовательную аутентификацию легко обойти. Одним из решений является использование существующих решений аутентификации, таких как Okta или OAuth.

Если вы предпочитаете придерживаться собственных решений аутентификации Node.js, вам нужно помнить несколько вещей. При создании паролей не используйте встроенную криптобиблиотеку Node.js; используйте Bcrypt или Scrypt. Обязательно ограничьте количество неудачных попыток входа в систему и не сообщайте пользователю, что именно — имя пользователя или пароль — неверно. Вместо этого возвращайте общую ошибку «неверные учетные данные». Вам также необходимы соответствующие политики управления сеансами. И не забудьте внедрить аутентификацию 2FA. Если все сделано правильно, это может значительно повысить безопасность вашего приложения. Это можно сделать с помощью таких модулей, как node-2fa или speakeasy.

3. Избегайте ошибок, которые раскрывают слишком много

Следующий пункт в списке — обработка ошибок. Здесь нужно учесть несколько моментов. Во-первых, не позволяйте пользователю узнать подробности, то есть не возвращайте клиенту полный объект ошибки. Он может содержать информацию, которую вы не хотите раскрывать, например, пути, другую используемую библиотеку или, возможно, даже секреты. Во-вторых, оборачивайте маршруты с помощью условия catch и не позволяйте Node.js аварийно завершать работу, если ошибка была вызвана запросом. Это не позволит злоумышленникам находить вредоносные запросы, которые приведут к краху вашего приложения, и отправлять их снова и снова, заставляя ваше приложение постоянно падать.

Говоря о наводнении вашего приложения Node.js вредоносными запросами, не выставляйте ваше приложение Node.js напрямую в Интернет. Используйте какой-нибудь компонент перед ним, например, балансировщик нагрузки, облачный брандмауэр или шлюз, или старый добрый nginx. Это позволит вам ограничить скорость DoS-атак за один шаг до того, как они достигнут вашего приложения Node.js.

4. Запустите автоматическое сканирование уязвимостей

До сих пор я описал несколько очевидных обязательных действий. Однако экосистема Node.js состоит из множества различных модулей и библиотек, которые вы можете установить. Очень часто в ваших проектах используется большое их количество. Это создает проблему безопасности; при использовании кода, написанного кем-то другим, вы не можете быть на 100% уверены в его безопасности. Чтобы решить эту проблему, необходимо часто проводить автоматическое сканирование на уязвимости. Они помогут вам найти зависимости с известными уязвимостями. Для базовой проверки можно использовать npm audit, но лучше воспользоваться одним из инструментов, описанных здесь.

5. Избегайте утечек данных

Помните, что мы говорили ранее о недоверии к фронтенду? Вы не должны доверять не только тому, что приходит с фронтенда, но и тому, что вы отправляете на него. Проще отправить все данные для конкретного объекта на фронтенд и отфильтровать только то, что нужно показать там. Однако для злоумышленника очень легко получить скрытые данные, отправленные из бэкенда.

Например, представьте, что вы хотите показать список пользователей, которые зарегистрировались на какое-либо мероприятие. Вы выполняете SQL-запрос, чтобы получить всех пользователей для этого конкретного события, и отправляете эти данные на фронтенд, а там фильтруете их, чтобы показать только имя и фамилию. Но все данные, которые вы не хотите показывать (например, даты рождения пользователей, номера телефонов, адреса электронной почты и т.д.), легко доступны через консоль разработчика браузера. Это приводит к утечке данных.

Как решить эту проблему? Отправляйте только те данные, которые необходимы. Если вам нужны только имя и фамилия, извлекайте из базы данных только их. Это создает немного больше работы, но оно того стоит.

6. Настройка протоколирования и мониторинга

Вы можете подумать, что протоколирование и мониторинг, хотя и важны, но не имеют отношения к безопасности, но это не так. Конечно, цель состоит в том, чтобы сделать системы безопасными с самого начала, но в действительности это требует постоянного процесса. А для этого необходимы протоколирование и мониторинг. Некоторые хакеры могут быть заинтересованы в том, чтобы сделать ваше приложение недоступным, что можно выяснить и без протоколирования. Но некоторые хакеры предпочтут оставаться незамеченными в течение более длительного периода времени. В таких случаях мониторинг журналов и метрик поможет вам заметить, что что-то не так. При использовании только базового протоколирования вы не получите достаточно информации, чтобы понять, исходят ли странно выглядящие запросы от вашего собственного приложения, стороннего API или от хакера.

7. Используйте линтеры безопасности

Мы уже говорили об автоматическом сканировании уязвимостей, но вы можете пойти еще дальше и ловить распространенные уязвимости безопасности даже во время написания кода. Как? С помощью плагинов-линтеров, таких как eslint-plugin-security. Плагин безопасности будет уведомлять вас каждый раз, когда вы используете небезопасные методы работы с кодом (например, использование eval или нелитеральных выражений regex).

8. Избегайте секретов в конфигурационных файлах

Написание безопасного кода с самого начала, безусловно, поможет, но это не сделает ваше приложение пуленепробиваемым, если в итоге вы будете хранить секреты в виде обычного текста в конфигурационных файлах. Такая практика неприемлема, даже если вы храните код в закрытом репозитории. Импорт секретов из переменных окружения — это первый шаг, но и он не является идеальным решением. Чтобы быть более уверенным в том, что ваши секреты нельзя легко прочитать, используйте решения для управления секретами, такие как Vault. Если использование Vault невозможно, шифруйте свои секреты при их хранении и обязательно регулярно меняйте их. Многие решения CI/CD позволяют безопасно хранить секреты и безопасно развертывать их.

9. Внедряйте заголовки HTTP-ответов

Многих менее распространенных атак можно избежать, добавив в приложение дополнительные HTTP-заголовки, связанные с безопасностью. Самые базовые механизмы, такие как CORS, повысят безопасность вашего API, но рассмотрите возможность использования таких модулей, как Helmet, которые добавят еще больше заголовков для обеспечения безопасности вашего приложения. Helmet может реализовать одиннадцать различных механизмов безопасности на основе заголовков с помощью одной строки кода:

app.use(helmet());

10. Не запускайте Node.js от имени root

В мире Docker и микросервисов мы часто забываем о том, как на самом деле выполняется Node.js. Легко просто запустить контейнер Docker и считать, что он изолирован от основной машины, поэтому он безопасен. Но использование Docker не означает, что запуск Node.js от имени root больше не является проблемой. Объедините возможность запуска любого кода JavaScript с помощью XSS-атаки с запуском Node.js от имени root, и вы получите неограниченные возможности для взлома.

Резюме

Обеспечение безопасности веб-приложений очень важно, но сжатые сроки иногда не позволяют нам должным образом выполнить работу на любом этапе. Вот почему важно уделять внимание безопасности на каждом этапе жизненного цикла разработки программного обеспечения, начиная с замысла и заканчивая производством.

Спасибо, что дочитали до этого места. Тем временем вы можете ознакомиться с другими моими статьями в блоге и посетить мой Github.

В настоящее время я также работаю над Stone CSS (Github).

Оцените статью
devanswers.ru
Добавить комментарий