Полное руководство 📝 по обеспечению безопасности 👮‍♂️ серверной части 🔡 с использованием Express.js Pt. 1

Введение:

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

Предпосылки:

Чтобы следовать этой статье, вы должны иметь базовое представление о JavaScript, Node.js и Express.js. Знакомство с концепциями разработки веб-приложений и протоколами HTTP также полезно.

Оглавление:

  1. Введение
  2. Предпосылки
  3. Настройка Express.js для безопасной серверной разработки
  4. Внедрение проверки и очистки данных
  5. Защита передачи данных с помощью HTTPS
  6. Методы аутентификации и авторизации
  7. Предотвращение распространенных уязвимостей безопасности
    7.1. Межсайтовый скриптинг (XSS)
    7.2. Подделка межсайтовых запросов (CSRF)
    7.3. SQL-инъекция
    7.4. Управление сеансом
  8. Заключение
  9. Рекомендации

Настройка Express.js для безопасной серверной разработки:

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

$ mkdir myapp
$ cd myapp

Используйте команду npm init, чтобы создать файл package.json для вашего приложения.

$ npm init

Теперь установите Express в каталог myapp и сохраните его в списке зависимостей. Например:

$ npm install express

Внедрение проверки и очистки данных

Вот пример кода, демонстрирующий, как реализовать проверку и очистку данных в Express.js:

const express = require('express');
const { body, validationResult } = require('express-validator');

const app = express();

// Middleware for validating user input
app.use(express.json());

// Validation rules for the request body
const userValidationRules = [
  body('name').isLength({ min: 3 }).withMessage('Name must be at least 3 characters long'),
  body('email').isEmail().withMessage('Invalid email address'),
  body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters long'),
];

// Custom middleware for handling validation errors
const validate = (req, res, next) => {
  const errors = validationResult(req);
  if (errors.isEmpty()) {
    return next();
  }
  const errorMessages = errors.array().map((error) => error.msg);
  return res.status(400).json({ errors: errorMessages });
};

// Route for user registration
app.post('/register', userValidationRules, validate, (req, res) => {
  // Process the validated user data and save to the database
  // ...
  res.status(201).json({ message: 'User registered successfully' });
});

// Start the Express.js server
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

В приведенном выше фрагменте кода мы сначала импортируем необходимые модули, в том числе express для создания приложения Express.js и express-validator для проверки данных. Затем мы определяем правила проверки с помощью функции body из express-validator. В этом примере мы проверяем поля имя, электронная почта и пароль.

Затем мы создаем пользовательскую промежуточную функцию с именем validate, которая проверяет наличие ошибок проверки, используя validationResult из express-validator. Если ошибок нет, промежуточное ПО вызывает следующую функцию, чтобы перейти к следующему промежуточному ПО или обработчику маршрута. Если есть ошибки, он отправляет ответ с сообщениями об ошибках.

Наконец, мы определяем маршрут для регистрации пользователя (/register). Мы применяем промежуточное ПО userValidationRules и промежуточное ПО validate для проверки ввода пользователя. Если ввод проходит проверку, мы можем обрабатывать данные по мере необходимости.

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

Защита передачи данных с помощью HTTPS:

Вот фрагмент кода, демонстрирующий, как защитить передачу данных с помощью HTTPS на сервере Express.js:

const express = require('express');
const https = require('https');
const fs = require('fs');

const app = express();

// Path to SSL/TLS certificate and private key files
const sslKeyPath = '/path/to/private/key.pem';
const sslCertPath = '/path/to/certificate.pem';

// Read the SSL/TLS certificate and private key files
const privateKey = fs.readFileSync(sslKeyPath, 'utf8');
const certificate = fs.readFileSync(sslCertPath, 'utf8');
const credentials = { key: privateKey, cert: certificate };

// Enable HTTPS on the Express.js server
const httpsServer = https.createServer(credentials, app);

// Redirect HTTP to HTTPS
app.use((req, res, next) => {
  if (req.secure) {
    // Request is already using HTTPS, proceed to the next middleware or route handler
    next();
  } else {
    // Redirect HTTP to HTTPS
    res.redirect(`https://${req.headers.host}${req.url}`);
  }
});

// Define your routes and middleware below
// ...

// Start the HTTPS server
httpsServer.listen(443, () => {
  console.log('Server is running on HTTPS (port 443)');
});

В приведенном выше фрагменте кода мы сначала импортируем необходимые модули, включая express, https и fs для чтения файлов из файловой системы. Затем мы определяем пути к сертификату SSL/TLS и файлам закрытого ключа.

Далее мы читаем содержимое сертификата и файлов ключей с помощью fs.readFileSync. Мы храним закрытый ключ и сертификат в объекте credentials.

После этого мы создаем HTTPS-сервер с помощью https.createServer и передаем объект credentials вместе с приложением, чтобы включить HTTPS на Express.js. сервер.

Чтобы перенаправить HTTP на HTTPS, мы используем промежуточную функцию, которая проверяет, использует ли запрос HTTPS (req.secure). Если да, промежуточное ПО переходит к следующему промежуточному ПО или обработчику маршрута. Если это не так, промежуточное ПО перенаправляет запрос на соответствующий URL-адрес HTTPS, используя res.redirect.

Наконец, мы определяем ваши маршруты и промежуточное ПО ниже промежуточного ПО перенаправления, а затем запускаем HTTPS-сервер, вызывая httpsServer.listen через порт 443.

Не забудьте заменить /path/to/private/key.pem и /path/to/certificate.pem фактическими путями к вашему сертификату SSL/TLS и файлам закрытого ключа. . Кроме того, вам может потребоваться изменить код в соответствии с вашей конкретной конфигурацией сервера или средой.

Методы аутентификации и авторизации:

Вот фрагмент кода, демонстрирующий, как реализовать аутентификацию пользователя с помощью JWT (веб-токены JSON) и обеспечить авторизацию с помощью управления доступом на основе ролей (RBAC) на сервере Express.js:

const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();

// Secret key for JWT
const secretKey = 'your-secret-key';

// Middleware to authenticate user using JWT
const authenticateUser = (req, res, next) => {
  // Get the token from the request headers or query parameters
  const token = req.headers.authorization || req.query.token;

  if (token) {
    try {
      // Verify the token using the secret key
      const decoded = jwt.verify(token, secretKey);

      // Attach the user object to the request
      req.user = decoded.user;

      next();
    } catch (error) {
      return res.status(401).json({ message: 'Invalid token' });
    }
  } else {
    return res.status(401).json({ message: 'Token not provided' });
  }
};

// Middleware to authorize user based on role
const authorizeUser = (role) => {
  return (req, res, next) => {
    // Check if the user has the required role
    if (req.user && req.user.role === role) {
      next();
    } else {
      return res.status(403).json({ message: 'Unauthorized' });
    }
  };
};

// Example route requiring authentication and authorization
app.get('/admin/dashboard', authenticateUser, authorizeUser('admin'), (req, res) => {
  // Handle the protected route logic
  res.json({ message: 'Welcome to the admin dashboard!' });
});

// Example route requiring authentication only
app.get('/user/profile', authenticateUser, (req, res) => {
  // Handle the protected route logic
  res.json({ message: 'User profile page' });
});

// Define your routes and middleware below
// ...

// Start the server
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

В приведенном выше фрагменте кода мы сначала импортируем необходимые модули, включая express и jsonwebtoken. Затем мы определяем секретный ключ для аутентификации JWT.

Затем мы создаем промежуточную функцию authenticateUser, которая извлекает токен из заголовков или параметров запроса. Он проверяет токен с помощью jsonwebtoken.verify и прикрепляет декодированный объект пользователя к запросу, если токен действителен.

Мы также создаем промежуточную функцию более высокого порядка authorizeUser, которая принимает роль в качестве аргумента. Эта функция проверяет наличие у пользователя требуемой роли на основе объекта req.user. Если у пользователя правильная роль, он переходит к следующему промежуточному ПО или обработчику маршрута; в противном случае возвращается ответ 403 Unauthorized.

Код включает два примера маршрутов: /admin/dashboard и /user/profile. Маршрут /admin/dashboard требует как аутентификации, так и авторизации с ролью 'admin', а маршрут /user/profile требует только аутентификации. Вы можете определить свои собственные маршруты и промежуточное ПО ниже этих примеров.

Наконец, мы запускаем сервер, вызывая app.listen для нужного порта (в данном случае это порт 3000). Вам может потребоваться изменить код, чтобы он соответствовал вашим конкретным требованиям аутентификации и авторизации, или интегрировать его с вашей базой данных или системой управления пользователями.

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

Предотвращение общих уязвимостей безопасности:

Вот фрагмент кода, демонстрирующий, как реализовать меры по предотвращению распространенных уязвимостей безопасности, таких как XSS (межсайтовый скриптинг), CSRF (подделка межсайтовых запросов), внедрение SQL и уязвимости управления сеансами на сервере Express.js:

const express = require('express');
const helmet = require('helmet');
const bodyParser = require('body-parser');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const MySQLStore = require('express-mysql-session')(session);
const mysql = require('mysql');

const app = express();

// Configure MySQL session store
const sessionStore = new MySQLStore({
  host: 'localhost',
  user: 'your-username',
  password: 'your-password',
  database: 'your-database',
});

// Configure MySQL database connection
const dbConnection = mysql.createConnection({
  host: 'localhost',
  user: 'your-username',
  password: 'your-password',
  database: 'your-database',
});

// Middleware for parsing request bodies
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Middleware for setting secure response headers
app.use(helmet());

// Middleware for CSRF protection
const csrfProtection = csrf({ cookie: true });
app.use(cookieParser());
app.use(csrfProtection);

// Middleware for session management
app.use(
  session({
    secret: 'your-secret-key',
    resave: false,
    saveUninitialized: true,
    store: sessionStore,
  })
);

// Middleware to prevent SQL injection
const sanitizeInput = (req, res, next) => {
  for (let key in req.body) {
    if (typeof req.body[key] === 'string') {
      req.body[key] = mysql.escape(req.body[key]);
    }
  }
  next();
};

// Example route demonstrating the usage of CSRF token
app.get('/contact-form', (req, res) => {
  const csrfToken = req.csrfToken();
  res.json({ csrfToken });
});

// Example route demonstrating the usage of a parameterized SQL query
app.post('/user', sanitizeInput, (req, res) => {
  const { username, password } = req.body;
  const sql = `INSERT INTO users (username, password) VALUES (${username}, ${password})`;
  dbConnection.query(sql, (error, results) => {
    if (error) {
      res.status(500).json({ message: 'Error creating user' });
    } else {
      res.json({ message: 'User created successfully' });
    }
  });
});

// Define your routes and middleware below
// ...

// Start the server
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

В приведенном выше фрагменте кода мы сначала импортируем необходимые модули, включая Express, Helmet, body-parser, csurf, cookie-parser, express-session и mysql. Мы также настраиваем хранилище сеансов с помощью express-mysql-session и настраиваем соединение с базой данных MySQL.

Код включает в себя различные функции промежуточного программного обеспечения для повышения безопасности. Мы используем body-parser для разбора тела запроса, шлем для установки безопасных заголовков ответа, cookie-parser для защиты от CSRF и экспресс -session для управления сеансом.

Чтобы предотвратить SQL-инъекцию, мы реализуем пользовательскую промежуточную функцию sanitizeInput, которая выполняет итерацию по телу запроса и использует mysql.escape для экранирования любых строковых значений перед выполнением SQL-запросов.

Код включает два примера маршрута. Маршрут /contact-form демонстрирует использование защиты CSRF, создавая и возвращая токен CSRF. Затем клиент может включить этот токен в последующие запросы для подтверждения подлинности.

Маршрут `/user демонстрирует использование очистки ввода и параметризованных запросов SQL. Функция промежуточного программного обеспечения sanitizeInput очищает пользовательский ввод, экранируя любые строковые значения, обеспечивая их безопасное использование в запросах SQL. Запрос SQL для создания пользователя использует параметризованные запросы, вставляя экранированные значения непосредственно в запрос.

Реализуя эти меры, фрагмент кода устраняет распространенные уязвимости безопасности. Атаки XSS смягчаются с помощью надлежащей очистки запросов и ответов. Атаки CSRF предотвращаются путем создания и проверки токенов CSRF. Атаки путем внедрения SQL-кода смягчаются за счет использования параметризованных запросов и экранирования пользовательского ввода. Кроме того, управление сеансом безопасно осуществляется с помощью модуля express-session.

Заключение:

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

Использованная литература:

  • Документация по Express.js: https://expressjs.com/
  • Документация по шлему: https://helmetjs.github.io/
  • документация csurf: https://www.npmjs.com/package/csurf
  • документация по экспресс-сессии: https://www.npmjs.com/package/express-session
  • документация пакета mysql: https://www.npmjs.com/package/mysql

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

Будет вторая часть про

  • Внедрение ограничения скорости и защиты от DoS.
  • Ведение журнала и обработка ошибок для усиленной защиты.
  • Аудит безопасности и постоянное улучшение

А пока удачного кодирования 🧑‍💻

Находите эту статью полезной? Ставь лайк и комментируй.

Грасиас 🙏