Безопасность TypeScript приложений: топ-10 уязвимостей для начинающих
TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию, но не спасает от классических веб-уязвимостей. Начинающие разработчики часто полагаются на строгость типов, забывая о безопасности на уровне логики и данных. В этой статье мы разберем 10 самых распространенных уязвимостей в TypeScript-приложениях с примерами кода и методами защиты.
1. XSS (Cross-Site Scripting) через dangerouslySetInnerHTML
Одна из самых частых проблем — вставка пользовательского HTML без санитизации. В TypeScript это часто встречается при работе с React или Angular.
// ❌ Уязвимый код
function UserComment({ comment }: { comment: string }) {
return <div dangerouslySetInnerHTML={{ __html: comment }} />;
}
// ✅ Безопасный вариант
import DOMPurify from 'dompurify';
function SafeComment({ comment }: { comment: string }) {
const sanitized = DOMPurify.sanitize(comment);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
2. SQL-инъекции через строковые запросы
TypeScript не защищает от SQL-инъекций, если вы используете сырые строки. Всегда используйте параметризованные запросы.
// ❌ Опасно
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
// ✅ Безопасно (пример с Prisma)
const user = await prisma.user.findUnique({
where: { email: userEmail }
});
3. Небезопасное хранение секретов в коде
API-ключи, токены и пароли не должны попадать в исходный код. TypeScript компилируется в JavaScript, и все строки остаются читаемыми.
// ❌ Никогда так не делайте
const API_KEY = 'sk-1234567890abcdef';
// ✅ Используйте переменные окружения
const API_KEY = process.env.API_KEY;
if (!API_KEY) throw new Error('API_KEY is missing');
4. NoSQL-инъекции (MongoDB)
При работе с MongoDB через Mongoose или драйвер можно случайно передать объект запроса, который манипулирует логикой.
// ❌ Уязвимый код
app.post('/login', async (req, res) => {
const user = await User.findOne({
email: req.body.email,
password: req.body.password
});
});
// ✅ Безопасный вариант
app.post('/login', async (req: Request, res: Response) => {
const { email, password } = req.body;
if (typeof email !== 'string' || typeof password !== 'string') {
return res.status(400).send('Invalid input');
}
const user = await User.findOne({ email, password });
});
5. CSRF (Cross-Site Request Forgery)
Если ваше приложение использует cookie-аутентификацию, злоумышленник может выполнить действия от имени пользователя.
// ❌ Отсутствие защиты
app.post('/transfer', (req, res) => {
// Выполнение перевода без проверки источника
});
// ✅ Защита через CSRF-токен (пример с csurf)
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
app.post('/transfer', csrfProtection, (req, res) => {
// Безопасное выполнение
});
6. Prototype Pollution (загрязнение прототипов)
При мерже объектов без проверки можно перезаписать свойства прототипа, что приведёт к уязвимостям.
// ❌ Опасно
function merge(target: any, source: any) {
for (const key in source) {
target[key] = source[key];
}
}
// ✅ Безопасно
function safeMerge(target: Record<string, any>, source: Record<string, any>) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
7. Утечка информации через ошибки
Стек-трейсы и детали базы данных не должны показываться пользователю. TypeScript может усыпить бдительность из-за строгих типов.
// ❌ Плохо
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message, stack: err.stack });
});
// ✅ Хорошо
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
});
8. Небезопасное использование eval() и Function()
TypeScript не запрещает eval, но это открывает дверь для выполнения произвольного кода.
// ❌ Опасно
const result = eval(userInput);
// ✅ Альтернатива
// Используйте безопасные парсеры или JSON.parse() для данных
const safeData = JSON.parse(userInput);
9. Отсутствие валидации входных данных (типы не спасают)
TypeScript проверяет типы на этапе компиляции, но во время выполнения данные могут быть любыми. Всегда валидируйте входные данные.
// ❌ Ложное чувство безопасности
function createUser(data: { name: string; age: number }) {
// data может быть чем угодно в runtime
}
// ✅ Валидация через Zod или Yup
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(1),
age: z.number().positive()
});
function createUser(data: unknown) {
const validated = UserSchema.parse(data); // Бросит ошибку при невалидных данных
}
10. Небезопасные зависимости (Supply Chain Attacks)
Даже если ваш код безопасен, npm-пакеты могут содержать уязвимости. TypeScript проекты особенно уязвимы из-за большого количества зависимостей.
// Проверяйте зависимости регулярно
// npm audit
// Используйте Snyk или Dependabot
// В package.json добавляйте overrides для фиксации версий
{
"overrides": {
"lodash": "4.17.21"
}
}
Заключение
TypeScript — мощный инструмент, но он не панацея от всех уязвимостей. Безопасность приложения зависит от разработчика, а не от языка. Помните три главных правила:
- Никогда не доверяйте пользовательским данным — валидируйте и санитизируйте всё на сервере.
- Используйте проверенные библиотеки — DOMPurify для HTML, Zod для валидации,