Node.js — это среда выполнения JavaScript, то есть в ней есть все необходимое для выполнения программы, написанной на JavaScript. Хотя сам по себе он довольно безопасен, вам все же следует подумать о дополнительных способах защиты ваших приложений Node.js.

Число уязвимостей увеличивается по мере того, как злоумышленники находят новые хитрые способы взлома ваших приложений. Важно защитить конфиденциальные данные ваших клиентов.

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

Вот что мы сегодня рассмотрим:

  • Популярные атаки
  • Как защититься от SQL-инъекций
  • Добавить или удалить заголовки HTTP
  • Шифрование паролей в Node.js
  • Безопасные значения по умолчанию и динамическая типизация
  • Защита от условий гонки
  • Что изучать дальше

Популярные атаки

Важно знать, как хакеры/злоумышленники пытаются взломать ваше приложение Node.js, прежде чем вы сможете начать его защищать. В этом разделе кратко рассматриваются некоторые популярные атаки и угрозы и объясняется, как они работают.

Межсайтовый скриптинг (XSS)

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

Запросы на межсайтовую подделку (CSFR)

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

Атаки грубой силы

В атаках методом грубой силы злоумышленник использует автоматизированное программное обеспечение, которое может угадывать данные с течением времени. Атаки грубой силы могут взломать зашифрованные пароли и PIN-коды. Имя сеанса файлов cookie по умолчанию Ваши действия на веб-сайте сохраняются в виде файлов cookie. Использование имен файлов cookie по умолчанию представляет собой угрозу, поскольку хакеры могут легко их идентифицировать.

NPM и потенциальные риски безопасности

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

Атаки типа «отказ в обслуживании» (DoS)

DoS-атаки либо замедляют работу вашего сервиса, либо полностью его ломают. Злоумышленники достигают этого, постоянно создавая трафик и отправляя запросы.

SQL-инъекции

Распространенный метод взлома, который может разрушить вашу базу данных, SQL-инъекции происходят, когда вы запрашиваете у пользователей ввод, например, их идентификатор или имя пользователя, а вместо этого они вводят оператор SQL. Злоумышленники могут обходить аутентификацию, извлекать, добавлять или изменять вашу базу данных.

Как защититься от SQL-инъекций

SQL-инъекции — довольно распространенная стратегия для злоумышленников, поэтому важно научиться защищаться от них. Начнем с примера.

textuserID = getRequestString("userID");
textSQL = "SELECT * FROM Users WHERE userID = " + textuserID;

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

SQL-инъекция 1=1

Если ваш пользователь вводит что-то вроде 100 ИЛИ 1=1 для своего идентификатора пользователя, оператор SQL выглядит следующим образом:

SELECT * FROM Users WHERE userID = 100 OR 1=1;

Поскольку 1=1 всегда истинно, приведенный выше оператор вернет все строки таблицы Users. Это может быть потенциально опасно, если ваша таблица содержит такую ​​информацию, как имена пользователей и пароли.

SQL-инъекция «=»

Давайте посмотрим на другой пример:

userName = getRequestString("username");

userPass = getRequestString("userpwd");

sql = 'SELECT * FROM Users WHERE Name ="' + userName + '" AND Pass ="' + userPass + '"'

Приведенный выше код получает имя пользователя и пароль пользователя и выбирает данные на их основе. Если ваш пользователь вводит:

  • Имя пользователя: 'или''='
  • Пароль: 'или''='

Оператор SQL становится:

SELECT * FROM Users WHERE Name ="" or ""="" AND Pass ="" or ""=""

Это вернет все строки из таблицы Users, потому что OR “”=”” всегда верно.

Внедрение SQL и пакетные операторы SQL

Пакетные операторы SQL — это, по сути, несколько операторов SQL, разделенных точкой с запятой. Давайте посмотрим, как это работает.

SELECT * FROM Users; DROP TABLE Orders

В приведенном выше примере возвращаются все строки из таблицы «Пользователи», а затем удаляется таблица «Заказы». Если ваш пользователь вводит что-то вроде 100; DROP TABLE Orders; для своего идентификатора пользователя, оператор SQL становится:

SELECT * FROM Users WHERE userID = 105; DROP TABLE Orders;

Рекомендации по защите от SQL-инъекций

  • Параметризированные операторы. Параметризованные операторы обеспечивают безопасную обработку ваших входных данных. Они позволяют вашей базе данных различать данные и код независимо от ввода.

Если ваш пользователь вводит 100 OR 1=1 в качестве своего идентификатора пользователя, параметризованный оператор будет искать идентификатор пользователя = 100 OR 1=1.

  • Проверка ввода.Проверка ввода добавляет дополнительный уровень защиты. Вы можете сравнить ввод со списком разрешенных параметров при написании логики проверки. Проверка ввода также защищает вас от XSS-атак, DoS-атак и других инъекционных атак.
  • Используйте учетные записи базы данных с минимальными правами. Использование учетных записей базы данных с минимальными необходимыми правами защищает ваше приложение от SQL-инъекций. Не используйте учетные записи базы данных с правами администратора в своем приложении Node.js. Создайте разных пользователей с разрешениями на чтение-запись и только на чтение. Наконец, используйте представления SQL, чтобы ограничить доступ к определенным столбцам таблицы или соединениям.
  • Дезинфицируйте ввод: вам необходимо избавиться от подозрительного ввода. Вы можете сделать это, проверив, что поля, такие как адреса электронной почты, соответствуют регулярному выражению, буквенно-цифровые или числовые поля не содержат специальных символов и удаляют любые символы новой строки или пробелы.

Добавить или удалить заголовки HTTP

Заголовки HTTP позволяют как серверу, так и клиенту отправлять и получать данные об устанавливаемом соединении, запрашиваемом ресурсе и возвращаемом ресурсе. Возможно, вы игнорируете заголовки HTTP, потому что пользователи их не видят. Это не лучшая идея, так как они могут утечь конфиденциальную информацию.

Экспресс-приложения пропускают информацию через заголовок X-Powered-By. Этот заголовок позволяет браузеру узнать версию сервера и используемого поставщика. Затем хакеры могут сопоставить это с общедоступными уязвимостями, что позволяет легко атаковать ваше приложение.

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

Шифрование паролей в Node.js

Шифрование паролей позволяет преобразовать ваши пароли в нечитаемое сообщение с помощью ключа шифрования. Ключ шифрования — это математическое значение, известное и вам, и получателю.

Получатель использует ключ шифрования, чтобы превратить случайный текст обратно в читаемое сообщение. Шифрование является двусторонней функцией. Итак, если вы что-то зашифровали, вам нужно будет расшифровать это позже.

Шифрование бывает двух типов:

  • Симметричный: используется один и тот же ключ для шифрования и дешифрования.
  • Асимметричный: используются разные ключи для шифрования и дешифрования.

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

Пароли и хеширование

Хеширование — это одностороннее шифрование. Он отличается от шифрования тем, что не имеет ключа дешифрования. Вот как это работает, предположим, ваш пароль Pa55word. После прохождения через алгоритм хэширования он будет выглядеть примерно так: dhkqhuhdhudhuh.

Каждый раз, когда вы вводите `Pa55word, он хэшируется, и если он соответствует dhkqhuhdhudhuh, вы можете войти в систему.

Вы можете использовать библиотеку bcryptjs для защиты паролей в Node.js. bcryptjs берет пароль и соль, то есть количество раз, которое он должен выполнить алгоритм хеширования.

В приведенном ниже примере используются разные вызовы функций для генерации хэша и соли.

bcrypt.genSalt(saltRounds, function(err, salt) {
    bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
        // Store hashed password in database
    });
});

Или вы можете автоматически сгенерировать хэш и соль, используя:

bcrypt.hash(PlainTextPassword, saltRounds, function(err, hash) {
        // Store hashed password in database
});

Вы также можете сравнить свой пароль с хешированным паролем, хранящимся в базе данных, используя метод сравнения. Если пароли совпадают, сравнение возвращает true; если нет, то ложь.

bcrypt.compare(PlainTextPassword, hash, function(err, result) {
    // result == true
});

Безопасные значения по умолчанию и динамическая типизация

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

Например, если вы проверяете правильность пароля, вы идете в положительном направлении. Если это не так, то результат должен быть неположительным.

Вот простой пример проверки формы:

app.post('/signup', function() {

  //the default logic is failure
  var message = 'Invalid Form Data';


  //check for a valid form
  if (form.validate('signup') === true) {

        //process the form
        var message = 'Form Valid';

  }
});

Node.js — это язык с динамической типизацией. Языки с динамической типизацией проверяют тип переменной во время выполнения кода; это означает, что тип переменной будет изменяться в течение ее жизни.

Давайте посмотрим на этот пример:

var isActive = 'false';

if (isActive) {
    console.log("we shouldn't be here, yet we are");
}
else {
    console.log("we should be here");
}

Вы пытаетесь выяснить, является ли isActive истинным или ложным, но оно возвращается в виде строки. Динамическая типизация может облегчить вам разработку; вы, скорее всего, столкнетесь с ошибками, связанными с типом переменной, который иногда может быть трудно найти.

Не полагайтесь полностью на динамическую типизацию!

Защита от условий гонки

Состояние гонки возникает, когда у вас есть несколько процессов, одновременно обращающихся к общему ресурсу, и один или несколько из них хотят изменить его. Это приводит к неожиданному логическому потоку.

Вот пример. Предположим, у вас есть переменная a=5. У вас есть два процесса, X и Y. X хочет добавить 6 к a, а Y хочет добавить 3, только если a меньше 6. В зависимости от того, какой процесс выполняется первым, вы получите очень разные результаты.

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

Обратные вызовы

Обратные вызовы позволяют передавать одну функцию в качестве параметра другой функции. Затем функция вызывается, когда процесс завершается. Давайте посмотрим на этот пример. Ниже мы не будем использовать никаких обратных вызовов:

function generateCsrf(req, res, next) {

 //safe default!
 req.session.csrfToken = null;
 var token = null

 //generate a new token
 var crypto = require('crypto');
 crypto.randomBytes(48, function(err, buffer) {

  //this will run after the below
  //code has been run
  token = buffer.toString('hex');

 });

 //save the token in the session and proceed
 req.session.csrfToken = token;

 //let's try to print the token for this demo
 console.log(token)

 next();
};

Токен является нулевым к моменту вызова функции next(), так как токен не закончил генерацию. Чтобы этого не произошло, мы сначала ждем завершения генерации токена.

function generateCsrf(req, res, next) {

 //safe default!
 req.session.csrfToken = null;

 //generate a new token
 var crypto = require('crypto');
 crypto.randomBytes(48, function(err, buffer) {

  var token = buffer.toString('hex');

  //save the token in the session and proceed
  req.session.csrfToken = token;

  //let's try to print the token for this demo
  console.log(token)

  next();

 });
};

Эта концепция также важна при написании баз данных. Вам нужны транзакции, чтобы применять определенные изменения только в том случае, если все ваши операторы успешны.

Что изучать дальше

Поздравляю, вы дошли до конца. Надеюсь, теперь вы знакомы с некоторыми методами защиты приложений Node.js. Еще многое предстоит узнать о защите приложений, особенно с учетом всех существующих требований.

Вот что мы рекомендуем для следующих шагов обучения:

  • Аутентификация
  • Контроль доступа
  • Безопасная обработка файлов
  • Приведение типов
  • Инструменты для сканирования приложений Node.js (Acutinex, NodeJsScan и т. д.)

Чтобы начать работу с этими концепциями и попрактиковаться в том, что мы узнали сегодня, ознакомьтесь с курсом Educative Защита приложений Node.js. Этот курс научит вас всему, что вам нужно знать, от очистки ввода до защиты от XSS. и CSRF с практической средой кодирования. К концу вы будете знать, как защитить приложение Node.js.

Удачного обучения!

Продолжить чтение о Node.js и безопасности на Educative

Начать обсуждение

Какие еще есть ценные методы защиты приложений Node.js? Была ли эта статья полезна? Дайте нам знать в комментариях ниже!