Безопасность TypeScript: SQL-инъекции и XSS под контролем

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

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

Начать курс

Ваш код под прицелом: почему безопасность TypeScript — это не опция, а необходимость

Вы когда-нибудь задумывались, что происходит, когда пользователь вводит в поле поиска на вашем сайте не просто «книги по Python», а нечто вроде '; DROP TABLE users; --? Если ваш бэкенд на TypeScript не готов к такому повороту, вы рискуете не просто получить ошибку, а потерять всю базу данных. Звучит как сценарий фильма ужасов? Добро пожаловать в реальный мир веб-разработки, где безопасность TypeScript — это не модная фишка, а базовая гигиена кода.

В этой статье мы не будем читать скучные лекции. Мы разберем двух главных врагов любого веб-приложения: SQL-инъекции и XSS (межсайтовый скриптинг). Вы узнаете, как хакеры атакуют ваши проекты, и, самое главное, как превратить ваш TypeScript-код в неприступную крепость с помощью реальных примеров и лучших практик.

SQL-инъекции: как злоумышленники «разговаривают» с вашей базой данных

Представьте, что ваше приложение — это вежливый консьерж, который выполняет любую просьбу гостя. Если гость просит: «Покажите комнату №5», консьерж показывает. Но что, если гость просит: «Покажите все комнаты и дайте мне ключи от сейфа»? Без проверки консьерж выполнит и это. SQL-инъекция работает именно так: злоумышленник «встраивает» свои команды в обычный пользовательский ввод.

Допустим, у вас есть простой запрос для поиска пользователя по имени:

// ❌ ОПАСНО: уязвимый код
const userName = req.body.username; // пользователь ввел "admin"
const query = `SELECT * FROM users WHERE name = '${userName}'`;
db.execute(query);

Выглядит безобидно? А теперь представьте, что пользователь вводит: admin' OR '1'='1. Запрос превращается в:

SELECT * FROM users WHERE name = 'admin' OR '1'='1'

Поздравляю! Вы только что отдали злоумышленнику список всех пользователей системы. А если он захочет что-то удалить? Легко: admin'; DROP TABLE users; -- — и вашей базы больше нет.

Как защититься: параметризованные запросы — ваш щит

Главное правило для защиты от SQL-инъекций в TypeScript: никогда не вставляйте пользовательские данные напрямую в строку запроса. Используйте параметризованные запросы (prepared statements). Они разделяют код SQL и данные, и база данных воспринимает пользовательский ввод исключительно как данные, а не как команды.

Пример с библиотекой pg (для PostgreSQL):

// ✅ БЕЗОПАСНО: параметризованный запрос
import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

async function getUserByName(userName: string): Promise<any> {
  const query = 'SELECT * FROM users WHERE name = $1';
  const result = await pool.query(query, [userName]);
  return result.rows;
}

// Использование
const userInput = req.body.username; // даже если это "admin' OR '1'='1"
const user = await getUserByName(userInput);
// База данных воспримет это как поиск пользователя с таким странным именем
console.log(user); // [] — ничего не найдено

Тот же принцип работает и с другими ORM, например, с Prisma или TypeORM. Они по умолчанию используют параметризованные запросы, что делает безопасность TypeScript встроенной функцией.

// ✅ БЕЗОПАСНО: использование Prisma
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function findUser(name: string) {
  return prisma.user.findUnique({
    where: { name: name } // Prisma сам экранирует входные данные
  });
}

XSS-атаки: когда ваш сайт начинает транслировать чужой код

Если SQL-инъекции бьют по базе данных, то XSS (Cross-Site Scripting) атакует ваших пользователей. Представьте, что злоумышленник оставляет комментарий на форуме, но вместо текста вставляет JavaScript-код. Когда другой пользователь открывает страницу, этот код выполняется в его браузере. Результат? Кража cookies, перенаправление на фишинговые сайты или установка кейлоггера.

Классический пример уязвимости:

// ❌ ОПАСНО: отображение пользовательского ввода без обработки
function renderComment(comment: string): string {
  return `<div class="comment">${comment}</div>`;
}

// Если пользователь ввел:
// <script>alert('XSS')</script>
// На странице появится всплывающее окно

Существует три основных типа XSS:

  • Reflected (отраженный): вредоносный код передается через URL или форму и сразу же отображается на странице.
  • Stored (хранимый): код сохраняется в базе данных (например, в комментарии) и выполняется при каждом просмотре страницы.
  • DOM-based: атака происходит на стороне клиента через манипуляции с DOM.

Стратегия защиты: экранирование и Content Security Policy

Первая линия обороны — экранирование данных. Прежде чем вывести любой пользовательский контент, замените опасные символы на их HTML-сущности. В TypeScript для этого можно написать простую функцию:

// ✅ БЕЗОПАСНО: функция экранирования HTML
function escapeHtml(unsafe: string): string {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

// Использование
const userComment = "<script>alert('XSS')</script>";
const safeComment = escapeHtml(userComment);
console.log(safeComment); // &lt;script&gt;alert('XSS')&lt;/script&gt;

Но если вы используете современные фреймворки (React, Angular, Vue), они уже делают это за вас. Например, React по умолчанию экранирует все данные, вставленные через JSX. Однако будьте осторожны с dangerouslySetInnerHTML — это прямой путь к уязвимости.

Вторая линия обороны — Content Security Policy (CSP). Это HTTP-заголовок, который говорит браузеру, какие ресурсы можно загружать и выполнять. Настройте CSP в вашем TypeScript-бэкенде:

// ✅ БЕЗОПАСНО: настройка CSP в Express.js
import express from 'express';
import helmet from 'helmet                

Блоги

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