Безопасность JavaScript приложений: топ-10 уязвимостей
Современные JavaScript-приложения на Node.js обрабатывают огромные объемы чувствительных данных, что делает их привлекательной целью для злоумышленников. По данным OWASP, большинство взломов происходит из-за типовых ошибок, которые можно предотвратить. В этой статье мы разберем 10 самых распространенных уязвимостей, приведем реальные примеры кода и покажем, как защитить ваше приложение.
1. XSS (Cross-Site Scripting)
Межсайтовый скриптинг — одна из старейших, но до сих пор актуальных угроз. Злоумышленник внедряет вредоносный скрипт в веб-страницу, который выполняется в браузере жертвы.
// Уязвимый код
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h2>Результаты поиска для: ${query}</h2>`);
});
// Безопасная версия
const escapeHtml = (str) => {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q || '');
res.send(`<h2>Результаты поиска для: ${query}</h2>`);
});
2. SQL Injection
Внедрение SQL-кода происходит, когда пользовательский ввод напрямую конкатенируется в SQL-запрос. Это может привести к утечке или удалению данных.
// Уязвимый код
const userId = req.body.id;
const query = `SELECT * FROM users WHERE id = ${userId}`;
// Безопасная версия с параметризованными запросами
const userId = req.body.id;
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId], (err, results) => {
// обработка результатов
});
3. Небезопасная десериализация
Node.js часто использует JSON.parse(), но если вы работаете с другими форматами (например, Buffer), злоумышленник может внедрить вредоносные данные.
// Опасный подход
const data = JSON.parse(req.body.data); // может содержать __proto__
// Безопасный подход
const safeParse = (json) => {
const data = JSON.parse(json);
// Рекурсивно удаляем опасные свойства
return JSON.parse(JSON.stringify(data));
};
4. CSRF (Cross-Site Request Forgery)
Атака, при которой злоумышленник заставляет пользователя выполнить нежелательное действие на сайте, где тот авторизован.
// Установка защиты с помощью csurf
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.post('/transfer', csrfProtection, (req, res) => {
// безопасная обработка перевода
});
5. Небезопасная аутентификация
Слабые пароли, отсутствие ограничения попыток входа и хранение паролей в открытом виде — частые проблемы.
// Правильное хэширование паролей
const bcrypt = require('bcrypt');
async function createUser(password) {
const salt = await bcrypt.genSalt(12);
const hash = await bcrypt.hash(password, salt);
// сохраняем hash в БД
}
async function checkPassword(plainPassword, hash) {
return await bcrypt.compare(plainPassword, hash);
}
6. Утечка конфиденциальных данных
Отправка стека ошибок клиенту или логирование паролей — частая ошибка разработчиков.
// Неправильно
app.use((err, req, res, next) => {
res.status(500).json({ error: err.stack });
});
// Правильно
app.use((err, req, res, next) => {
console.error(err); // логируем на сервере
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
});
7. Небезопасная конфигурация
Использование стандартных портов, включенный режим отладки в продакшене и слабые CORS-политики.
// Безопасная конфигурация CORS
const cors = require('cors');
const corsOptions = {
origin: 'https://myapp.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
// Отключаем заголовок X-Powered-By
app.disable('x-powered-by');
8. Prototype Pollution
Уязвимость, характерная для JavaScript, когда злоумышленник может изменить прототип объекта.
// Опасный merge
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
target[key] = merge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// Безопасная версия
function safeMerge(target, source) {
const result = { ...target };
for (let key in source) {
if (key === '__proto__' || key === 'constructor') continue;
if (typeof source[key] === 'object' && source[key] !== null) {
result[key] = safeMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
9. Небезопасные зависимости
Использование устаревших пакетов с известными уязвимостями.
// Регулярная проверка зависимостей
// Установите npm audit
// В package.json добавьте:
{
"scripts": {
"audit": "npm audit",
"outdated": "npm outdated"
}
}
// Используйте Snyk для автоматического мониторинга
// npm install -g snyk
// snyk test
10. Отсутствие rate limiting
Без ограничения количества запросов злоумышленник может осуществить DDoS-атаку или перебор паролей.
// Установка rate limiting с express-rate-limit
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 100, // максимум 100 запросов
message: 'Слишком много запросов, попробуйте позже'
});
app.use('/api/', limiter);
// Для авторизации - более