Урок по треку Node.js и бэкенд 8 мин

Безопасность бэкенда на Node.js

Инъекции, хранение паролей, rate limiting, секреты, заголовки безопасности и зависимости.

Большинство взломов бэкенда происходит через несколько классических дыр. На собесе ждут, что ты назовёшь их, покажешь уязвимый код и исправление.

Инъекции

SQL-инъекция появляется, когда пользовательские данные склеиваются со строкой запроса:

// уязвимо: id = "1 OR 1=1" вернёт всех
db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);

// правильно: параметризованный запрос
db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);

При параметризации значение уходит в базу отдельно от текста запроса и не может стать частью SQL. ORM (Prisma, TypeORM) параметризуют сами, но «сырые» методы вроде $queryRawUnsafe снова открывают дыру.

NoSQL-инъекция: в MongoDB объект { "password": { "$ne": null } } из JSON-тела совпадёт с любым паролем. Лечится валидацией типов: пароль должен быть строкой.

Command injection: строка с пользовательским вводом в exec. Аргументы передают массивом в execFile.

Валидация входа

Всё, что пришло извне (тело, query, заголовки, файлы), валидируй схемой на границе: zod, class-validator, JSON Schema. Это заодно защищает от mass assignment: если передать req.body целиком в user.update, злоумышленник допишет "role": "admin". Бери только разрешённые поля.

Пароли

Пароли не шифруют и не хешируют быстрыми функциями (MD5, SHA-256): видеокарта перебирает миллиарды таких хешей в секунду. Нужна медленная функция с солью: argon2id, scrypt (есть в node:crypto) или bcrypt. Соль уникальна для каждого пользователя и хранится рядом с хешем, она ломает радужные таблицы.

Секреты и токены сравнивай crypto.timingSafeEqual, а не ===: обычное сравнение выходит на первом несовпавшем байте, и по времени ответа можно подбирать значение.

Ограничение частоты

Rate limiting защищает вход, восстановление пароля, отправку SMS и дорогие эндпоинты от перебора и злоупотреблений. При нескольких экземплярах счётчики хранят в Redis, иначе лимит умножается на число экземпляров. Ответ при превышении: 429 Too Many Requests.

Пути и файлы

Path traversal: path.join('/srv/uploads', '../../etc/passwd') даёт /etc/passwd. Проверяй, что итоговый path.resolve(...) начинается с разрешённой папки, а лучше храни файлы под сгенерированными именами.

Prototype pollution

Рекурсивный merge пользовательского JSON может записать свойства в Object.prototype через ключ __proto__. После этого у всех объектов появится, например, isAdmin: true. Защита: валидация схемой, Object.create(null) или Map для словарей, отказ от самописного глубокого слияния, пропуск ключей __proto__, constructor, prototype.

Другое

  • ReDoS: регулярка с вложенными квантификаторами вроде /(a+)+$/ на специально подобранной строке работает экспоненциально и блокирует event loop.
  • SSRF: сервер скачивает URL, присланный пользователем, и ходит во внутреннюю сеть или к метаданным облака. Разрешай только список доменов и проверяй IP.
  • Секреты: в переменных окружения или менеджере секретов, не в git. Утёкший ключ меняют (ротация), а не только удаляют из истории.
  • Заголовки: helmet выставляет HSTS, X-Content-Type-Options, CSP и другие.
  • Зависимости: lock-файл, npm audit в CI, обновления. Каждый пакет выполняет код с правами твоего процесса.
  • Ошибки: клиенту не отдают стек и текст внутренних исключений.
  • Лимит тела запроса: без него JSON на гигабайт съест память.

Что спросят на собесе

  • Как защититься от SQL-инъекции и почему экранирования вручную мало.
  • Как хранить пароли.
  • Что такое rate limiting и как сделать его на нескольких экземплярах.
  • Что такое prototype pollution или path traversal.
  • Где хранить секреты.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.