Безопасность JavaScript приложений: топ-10 уязвимостей для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Безопасность 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() и

Блоги

Рекомендаций по книгам