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

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

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

Начать курс

Безопасность 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 для валидации,

Блоги

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