Безопасность JavaScript приложений: топ-10 уязвимостей для начинающих
Разработка на JavaScript, особенно в среде Node.js, открывает широкие возможности для создания современных веб-приложений. Однако вместе с мощью приходит и ответственность. Игнорирование вопросов безопасности может привести к утечке данных, финансовым потерям и репутационному ущербу. Для начинающих разработчиков понимание основных угроз — первый шаг к созданию защищённого кода. В этой статье мы разберём топ-10 уязвимостей JavaScript (Node.js) на основе списка OWASP, с примерами кода и практическими советами по защите.
1. Межсайтовый скриптинг (XSS)
XSS — одна из самых распространённых атак. Злоумышленник внедряет вредоносный скрипт в веб-страницу, который выполняется в браузере жертвы. В Node.js это часто происходит через неэкранированный вывод данных.
Пример уязвимого кода (Express):
app.get('/user/:name', (req, res) => {
res.send('<h1>Привет, ' + req.params.name + '</h1>');
});
Как защититься: Используйте шаблонизаторы с автоэкранированием (например, Handlebars, EJS) или библиотеки вроде helmet. Никогда не вставляйте пользовательский ввод напрямую в HTML.
app.get('/user/:name', (req, res) => {
const safeName = encodeURIComponent(req.params.name);
res.send(`<h1>Привет, ${safeName}</h1>`);
});
2. SQL-инъекции
Если ваше приложение использует SQL-базу данных (например, PostgreSQL или MySQL), неправильная обработка запросов может привести к выполнению произвольного SQL-кода.
Уязвимый код:
const query = `SELECT * FROM users WHERE email = '${userInput}'`;
db.query(query, callback);
Решение: Всегда используйте параметризованные запросы или ORM (например, Sequelize, Prisma).
const query = 'SELECT * FROM users WHERE email = $1';
db.query(query, [userInput], callback);
3. Подделка межсайтовых запросов (CSRF)
CSRF-атака заставляет пользователя выполнить нежелательное действие на сайте, где он аутентифицирован. Например, перевести деньги или изменить пароль.
Защита: Используйте CSRF-токены. В Express популярна библиотека csurf (устарела, но заменена на csrf-csrf или встроенные решения фреймворков).
const csrf = require('csrf-csrf');
const { generateToken } = csrf();
app.post('/transfer', (req, res) => {
const token = req.body._csrf;
if (!validateToken(token)) {
return res.status(403).send('CSRF атака');
}
// Выполнение перевода
});
4. Незащищённые зависимости
Node.js приложения используют тысячи пакетов из npm. Устаревшие или уязвимые зависимости — частая причина взломов.
Рекомендации:
- Регулярно запускайте
npm auditдля проверки уязвимостей. - Используйте
snykилиDependabotдля автоматического обновления. - Удаляйте неиспользуемые пакеты.
5. Небезопасная аутентификация и управление сессиями
Слабые пароли, отсутствие ограничения на количество попыток входа, неправильное хранение сессионных токенов — всё это ведёт к компрометации аккаунтов.
Лучшие практики:
- Храните пароли в хэшированном виде (bcrypt, argon2).
- Используйте JWT с коротким сроком жизни и refresh-токенами.
- Установите лимит на попытки входа (rate limiting).
const bcrypt = require('bcrypt');
const saltRounds = 10;
// Регистрация
const hash = await bcrypt.hash(password, saltRounds);
// Вход
const match = await bcrypt.compare(inputPassword, hash);
6. Недостатки контроля доступа
Пользователь может получить доступ к данным или функциям, для которых у него нет прав. Например, изменение чужого профиля по ID.
Пример:
// Уязвимо: пользователь может передать чужой ID
app.put('/user/:id', (req, res) => {
// Обновление без проверки прав
});
// Безопасно: проверяем, что ID принадлежит текущему пользователю
if (req.user.id !== parseInt(req.params.id)) {
return res.status(403).send('Доступ запрещён');
}
7. Неправильная обработка ошибок и логирование
Утечка технической информации через сообщения об ошибках (стектрейсы, версии библиотек) помогает злоумышленникам.
Решение: В продакшене отключайте детальные ошибки. Используйте централизованное логирование с маскировкой чувствительных данных.
app.use((err, req, res, next) => {
console.error(err.stack); // Логируем для себя
res.status(500).send('Внутренняя ошибка сервера'); // Пользователю — общее сообщение
});
8. Небезопасная десериализация
Если вы используете JSON.parse() или eval() на данных от пользователя, это может привести к удалённому выполнению кода (RCE).
Опасный код:
const data = eval(req.body.code); // Никогда так не делайте!
Защита: Избегайте eval() и new Function(). Используйте безопасные парсеры (например, JSON.parse() с проверкой схемы через joi или zod).
9. Использование компонентов с известными уязвимостями
Даже если вы не пишете уязвимый код, сторонние библиотеки могут содержать бреши. Например, извест