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

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

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

Начать курс

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

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

1. XSS (Межсайтовый скриптинг)

XSS — одна из самых распространенных атак. Злоумышленник внедряет вредоносный скрипт на страницу, который выполняется в браузере пользователя.

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

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

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

Как исправить:

Всегда экранируйте пользовательский ввод. Используйте библиотеки вроде DOMPurify или встроенные методы экранирования:

import DOMPurify from 'dompurify';

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

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

Если вы формируете SQL-запросы путем конкатенации строк с пользовательским вводом, вы открываете дверь для SQL-инъекций.

Уязвимый код:

function getUser(username: string): Promise<User> {
    const query = `SELECT * FROM users WHERE username = '${username}'`;
    return db.execute(query);
}

Безопасный вариант:

function getUserSafe(username: string): Promise<User> {
    const query = 'SELECT * FROM users WHERE username = $1';
    return db.execute(query, [username]); // параметризованный запрос
}

3. Небезопасное хранение паролей

Никогда не храните пароли в открытом виде. Используйте bcrypt или argon2 для хеширования.

import bcrypt from 'bcrypt';

async function hashPassword(password: string): Promise<string> {
    const saltRounds = 12;
    return bcrypt.hash(password, saltRounds);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
}

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

Когда пользователь может получить доступ к чужим данным, просто изменив ID в URL.

Пример:

// Уязвимо: любой пользователь может получить любой заказ
app.get('/api/order/:id', (req, res) => {
    const order = getOrderById(req.params.id);
    res.json(order);
});

// Безопасно: проверяем принадлежность
app.get('/api/order/:id', (req, res) => {
    const order = getOrderById(req.params.id);
    if (order.userId !== req.user.id) {
        return res.status(403).json({ error: 'Forbidden' });
    }
    res.json(order);
});

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

TypeScript проекты часто используют npm-пакеты. Устаревшие версии могут содержать известные уязвимости.

  • Регулярно запускайте npm audit
  • Используйте npm update для обновления пакетов
  • Настройте автоматические проверки в CI/CD

6. Недостаточная валидация входных данных

TypeScript типы не проверяются в рантайме. Всегда валидируйте данные на сервере.

interface UserInput {
    email: string;
    age: number;
}

// Недостаточно: типы TypeScript не защищают в рантайме
function processUser(input: UserInput) {
    // ...
}

// Правильно: используем Zod или Joi для валидации
import { z } from 'zod';

const UserSchema = z.object({
    email: z.string().email(),
    age: z.number().min(18).max(120)
});

function processUserSafe(input: unknown) {
    const validated = UserSchema.parse(input);
    // validated имеет тип UserInput
}

7. Утечка информации через ошибки

Не показывайте пользователю детали внутренних ошибок — это может раскрыть структуру вашего приложения.

// Плохо: показывает стектрейс
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: 'Внутренняя ошибка сервера' });
});

8. Небезопасная настройка CORS

Слишком широкая политика CORS позволяет любому сайту делать запросы к вашему API.

// Плохо: разрешаем все
app.use(cors({ origin: '*' }));

// Хорошо: указываем конкретные домены
app.use(cors({ 
    origin: ['https://mysite.com', 'https://admin.mysite.com']
}));

9. Prototype Pollution (Загрязнение прототипа)

Атака, при которой злоумышленник изменяет свойства прототипа Object.

// Уязвимый код
function merge(target: any, source: any) {
    for (const key in source) {
        target[key] = source[key];
    }
}

// Злоумышленник передает: { "__proto__": { "polluted": true } }

// Защита: используйте Object.create(null) для создания чистых объектов
function safeMerge(target: Record<string, any>, source: Record<string, any>) {
    for (const key of Object.keys(source)) {
        if (key !== '__proto__' && key !== 'constructor') {
            target[key] = source[key];
        }
    }
}

10. Неправильная обработка асинхронных ошибок

Необработанные промисы могут привести к утечкам памяти и неожиданному поведению.

// Плохо: не обрабатываем ошибки
async function fetchData() {
    const data = await riskyOperation();
    return data;
}

// Хорошо: используем try-catch и глобальный обработчик
process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhand                

Блоги

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