Введение: почему безопасность JavaScript приложений — это не опция, а необходимость
JavaScript (особенно на платформе Node.js) стал доминирующим языком для создания как клиентских, так и серверных приложений. Однако с ростом популярности растет и количество атак. По данным OWASP, большинство взломов происходят из-за типовых ошибок, которые можно предотвратить. В этой статье мы разберем топ-10 уязвимостей JavaScript (Node.js) с реальными примерами кода и методами защиты. Материал будет полезен как начинающим, так и опытным разработчикам, желающим укрепить безопасность своих проектов.
1. Инъекции (SQL, NoSQL, Command Injection)
Инъекции остаются самой опасной категорией уязвимостей. В Node.js часто страдают MongoDB и PostgreSQL.
Пример уязвимого кода (NoSQL-инъекция):
// Уязвимый код
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await db.collection('users').findOne({
username: username,
password: password
});
// Если передать { "username": { "$ne": null }, "password": { "$ne": null } }, то вернется первый пользователь
});
Как исправить:
- Используйте ORM/ODM с параметризацией (Mongoose, Sequelize).
- Валидируйте и санируйте входные данные (библиотека
validatorилиjoi). - Для команд используйте
execFileвместоexec.
2. Межсайтовый скриптинг (XSS)
XSS позволяет злоумышленнику внедрить вредоносный скрипт в страницу, которую просматривают другие пользователи.
Пример уязвимого кода (Reflected XSS):
// Уязвимый Express-маршрут
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Результаты поиска для: ${query}</h1>`);
// Если передать ?q=<script>alert('XSS')</script>, скрипт выполнится
});
Защита:
- Экранируйте вывод: используйте
res.renderс шаблонизаторами (EJS, Pug) илиencodeURIComponent. - Установите HTTP-заголовок
Content-Security-Policy. - Не используйте
innerHTMLна клиенте — применяйтеtextContent.
3. Небезопасная аутентификация и управление сессиями
Слабая аутентификация — одна из главных причин утечек данных. Часто разработчики хардкодят ключи или используют слабые пароли.
Типичные ошибки:
- Хранение паролей в открытом виде (вместо bcrypt/argon2).
- Использование предсказуемых JWT-секретов (
secret123). - Отсутствие ограничения на количество попыток входа.
Пример правильной реализации:
const bcrypt = require('bcrypt');
const saltRounds = 12;
app.post('/register', async (req, res) => {
const hash = await bcrypt.hash(req.body.password, saltRounds);
// Сохраняем hash в БД
});
app.post('/login', async (req, res) => {
const user = await db.getUser(req.body.username);
const match = await bcrypt.compare(req.body.password, user.passwordHash);
if (match) {
// Создаем JWT с коротким сроком действия
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '15m' });
res.json({ token });
}
});
4. SSRF (Server-Side Request Forgery)
Уязвимость SSRF позволяет атакующему заставить сервер выполнять запросы к внутренним ресурсам (например, к метаданным облачного провайдера).
Пример:
// Уязвимый код: загрузка аватара по URL
app.post('/avatar', async (req, res) => {
const { url } = req.body;
const response = await fetch(url); // Можно передать http://169.254.169.254/latest/meta-data/
// Обработка ответа
});
Защита:
- Создайте белый список разрешенных доменов.
- Блокируйте запросы к внутренним IP-адресам (127.0.0.1, 10.x.x.x, 172.16.x.x, 192.168.x.x).
- Используйте библиотеку
ssrf-req-filter.
5. Небезопасная десериализация
Node.js уязвим к атакам через eval, JSON.parse с опасными данными или использование node-serialize.
Опасный пример:
const serialize = require('node-serialize');
app.post('/state', (req, res) => {
const obj = serialize.unserialize(req.body.data);
// Если передать сконструированный объект с __proto__, можно изменить прототип
});
Решение:
- Используйте только безопасные форматы (JSON без функций).
- Никогда не применяйте
eval()илиnew Function()для пользовательских данных. - Для сложных объектов используйте библиотеки с проверкой типов (например,
class-validator).
6. Недостатки логирования и мониторинга
Отсутствие логирования критических событий (неудачные попытки входа, изменение прав) делает невозможным обнаружение атаки.
Что логировать:
- Все попытки аутентификации (успешные и неуспешные).
- Изменения ролей и прав доступа.
- Доступ к конфиденциальным данным.
- Ошибки валидации и исключения.
Лучшие практики:
- Используйте структурированное логирование (Winston, Pino).
- Никогда не логи