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

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

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

Начать курс

Введение: почему безопасность TypeScript приложений критична

TypeScript — это надстройка над JavaScript, которая добавляет статическую типизацию и современные возможности. Однако типизация не гарантирует безопасность. Ошибки валидации, неправильная обработка пользовательского ввода и уязвимости в зависимостях могут привести к серьёзным атакам. В этой статье мы разберём топ-10 наиболее частых уязвимостей TypeScript-приложений, приведём примеры кода и покажем, как их избежать.

1. Cross-Site Scripting (XSS)

XSS-атаки возникают, когда злоумышленник внедряет вредоносный скрипт в веб-страницу. TypeScript не защищает от этого автоматически.

Пример уязвимого кода

function renderUserInput(input: string): string {
  return `<div>${input}</div>`;
}

// Злоумышленник передаёт: <script>alert('XSS')</script>

Решение

Используйте экранирование HTML-символов или библиотеки вроде DOMPurify:

import DOMPurify from 'dompurify';

function renderUserInputSafe(input: string): string {
  const sanitized = DOMPurify.sanitize(input);
  return `<div>${sanitized}</div>`;
}

2. SQL-инъекции

Если вы используете TypeScript с базами данных, неконтролируемая конкатенация строк в SQL-запросах может привести к утечке данных.

Пример уязвимого кода

async function getUser(id: string): Promise<User | null> {
  const query = `SELECT * FROM users WHERE id = '${id}'`;
  return await db.query(query);
}

Решение

Используйте параметризованные запросы (prepared statements):

async function getUserSafe(id: string): Promise<User | null> {
  const query = 'SELECT * FROM users WHERE id = $1';
  return await db.query(query, [id]);
}

3. Prototype Pollution

Эта уязвимость характерна для JavaScript/TypeScript. Злоумышленник может изменить прототип объекта, что повлияет на все объекты в runtime.

Пример уязвимого кода

function merge(target: any, source: any) {
  for (const key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = source[key];
    }
  }
}

const user = { name: 'Alice' };
merge(user, JSON.parse('{"__proto__": {"isAdmin": true}}'));
// Теперь все объекты имеют isAdmin: true

Решение

Проверяйте ключи на опасные свойства и используйте безопасные утилиты:

function safeMerge(target: Record<string, any>, source: Record<string, any>) {
  for (const key in source) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
    target[key] = source[key];
  }
}

4. Cross-Site Request Forgery (CSRF)

CSRF-атаки заставляют пользователя выполнить нежелательные действия на сайте, где он аутентифицирован.

Решение на TypeScript

// Генерация CSRF-токена
import crypto from 'crypto';

function generateCsrfToken(): string {
  return crypto.randomBytes(32).toString('hex');
}

// Проверка в middleware
function csrfMiddleware(req: Request, res: Response, next: NextFunction) {
  const token = req.headers['x-csrf-token'];
  if (token !== req.session.csrfToken) {
    return res.status(403).json({ error: 'CSRF validation failed' });
  }
  next();
}

5. Небезопасная десериализация

Использование JSON.parse() или eval() на непроверенных данных может привести к выполнению произвольного кода.

Уязвимый пример

function processData(data: string) {
  const obj = eval('(' + data + ')');
  // ...
}

Безопасный подход

function processDataSafe(data: string) {
  try {
    const obj = JSON.parse(data);
    // Дополнительная валидация типа
    if (typeof obj !== 'object' || obj === null) throw new Error('Invalid input');
  } catch {
    throw new Error('Invalid JSON');
  }
}

6. Инъекции через шаблонизаторы

Многие шаблонизаторы (например, Handlebars, EJS) могут выполнять код, если не экранировать ввод.

Решение

import Handlebars from 'handlebars';

// Включите экранирование по умолчанию
const template = Handlebars.compile('Hello {{name}}');
const output = template({ name: '<script>alert("XSS")</script>' });
// Handlebars автоматически экранирует: Hello &lt;script&gt;...

7. Утечка информации через стек-трейсы

В production-режиме стек-трейсы ошибок не должны содержать пути файлов или конфиденциальные данные.

// Плохо: глобальный обработчик ошибок выводит стек
process.on('uncaughtException', (err) => {
  console.error(err.stack); // Утечка путей к файлам
});

// Хорошо: логируем только сообщение, стек — в защищённый лог
process.on('uncaughtException', (err) => {
  logger.error(err.message);
  // стек отправляется в защищённое хранилище
});

8. Небезопасные прямые ссылки на объекты (IDOR)

Если пользователь может получить доступ к чужим данным через изменение идентификатора в запросе.

Защита

// Вместо /api/users/:id — проверяйте права
app.get('/api/users/:id', async (req, res) => {
  const userId = req.params.id;
  const currentUser = req.user; // из аутентификации

  if (currentUser.role !== 'admin' && currentUser.id !== userId) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  // ...
});

9. Использование устаревших зависимостей

Многие уязвимости возникают из-за старых версий пакетов. Регулярно обновляйте зависимости.

// Проверка уязвимостей
npm audit

// Автоматическое обновление
npm update                

Блоги

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