Безопасность JavaScript приложений: топ-10 уязвимостей для начинающих
Безопасность веб-приложений — одна из самых важных тем для любого разработчика. JavaScript (особенно на серверной стороне Node.js) часто становится мишенью злоумышленников из-за своей популярности. В этой статье мы разберем 10 самых распространенных уязвимостей, с которыми сталкиваются начинающие разработчики, и покажем, как их избежать.
1. XSS (Межсайтовый скриптинг)
XSS — одна из самых частых атак, когда злоумышленник внедряет вредоносный скрипт в веб-страницу. Это происходит, когда данные от пользователя вставляются в HTML без экранирования.
Опасный код (уязвим):
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Результаты поиска: ${query}</h1>`);
});
Безопасный код (защита):
const escapeHtml = require('escape-html');
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(`<h1>Результаты поиска: ${query}</h1>`);
});
2. SQL-инъекции
Если вы используете SQL базы данных (например, SQLite или PostgreSQL), неэкранированные запросы могут привести к утечке данных.
Опасный код:
const user = req.body.username;
db.query(`SELECT * FROM users WHERE name = '${user}'`);
Безопасный код (используем параметризованные запросы):
const user = req.body.username;
db.query('SELECT * FROM users WHERE name = $1', [user]);
3. Небезопасная десериализация
Использование JSON.parse() на непроверенных данных может привести к атакам типа "prototype pollution".
Пример уязвимости:
const data = JSON.parse(req.body.jsonData);
// Если data содержит __proto__, это может изменить прототип Object
Защита: всегда валидируйте данные перед парсингом или используйте библиотеки вроде fast-json-stable-stringify.
4. Отсутствие проверки ввода (Input Validation)
Никогда не доверяйте данным от пользователя. Всегда проверяйте тип, длину и формат данных.
// Плохо: принимаем все подряд
app.post('/profile', (req, res) => {
const { name, age } = req.body;
// ...
});
// Хорошо: валидируем
const schema = { name: 'string', age: 'number' };
if (typeof req.body.name !== 'string') throw new Error('Invalid name');
5. CSRF (Межсайтовая подделка запроса)
Атака, при которой злоумышленник заставляет пользователя выполнить нежелательное действие на сайте, где он авторизован.
Защита: используйте CSRF-токены.
const csrf = require('csurf');
app.use(csrf({ cookie: true }));
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
6. Утечка информации через ошибки
Никогда не показывайте пользователю подробные сообщения об ошибках — это дает хакерам информацию о структуре вашего приложения.
Плохо:
app.use((err, req, res, next) => {
res.status(500).send(err.stack); // Утечка стека вызовов!
});
Хорошо:
app.use((err, req, res, next) => {
console.error(err.stack); // Логируем для себя
res.status(500).send('Внутренняя ошибка сервера');
});
7. Небезопасное использование eval() и Function()
Функции eval() и new Function() выполняют строку как код — это крайне опасно, если в строке есть пользовательские данные.
// Опасно! Никогда так не делайте
const userInput = req.body.code;
eval(userInput); // Злоумышленник может выполнить любой код
Альтернатива: используйте JSON.parse() или специализированные парсеры.
8. Недостаточная защита паролей
Хранение паролей в открытом виде — грубейшая ошибка. Всегда используйте хеширование с солью.
const bcrypt = require('bcrypt');
// Регистрация
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(password, salt);
// Проверка
const isValid = await bcrypt.compare(inputPassword, hash);
9. Отсутствие rate limiting (ограничения запросов)
Без ограничения частоты запросов злоумышленник может брутфорсить пароли или вызывать DoS-атаку.
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 100 // максимум 100 запросов с одного IP
});
app.use('/api/', limiter);
10. Уязвимости в зависимостях (npm-пакетах)
Используя чужие библиотеки, вы берете на себя ответственность за их безопасность. Регулярно проверяйте уязвимости.
# Проверка уязвимостей в проекте
npm audit
# Автоматическое исправление
npm audit fix
Также рекомендую использовать package-lock.json и обновлять зависимости хотя бы раз в месяц.
Заключение
Безопасность — это не разовое действие, а постоянный процесс. Вот краткий чек-лист для начинающего разработчика:
- Всегда экранируйте пользовательский вывод (анти-XSS)
- Используйте параметризованные запросы к БД (анти-SQL инъекции)
- Валидируйте и санируйте все входные данные
- Не используйте
eval()и