Валидация и XSS

В прикладных JavaScript-приложениях входные данные практически всегда являются внешним источником риска. Любое поле формы, query-параметр, JSON-тело запроса или данные из стороннего API могут содержать не только некорректные значения, но и внедрённые скрипты. Именно поэтому валидация рассматривается как первый слой защиты, уменьшающий вероятность появления XSS (Cross-Site Scripting), хотя и не заменяющий экранирование вывода и другие меры безопасности.

Библиотека class-validator предоставляет декларативный подход к проверке данных через декораторы, позволяя формализовать ограничения на уровне DTO-моделей. Такой подход снижает вероятность случайного пропуска опасных значений и делает контракт данных явным.


Природа XSS и точки возникновения

XSS возникает тогда, когда неподконтрольные данные попадают в контекст выполнения JavaScript или HTML без должного экранирования. Основные типы атак:

Reflected XSS Вредоносный код передаётся через запрос и сразу возвращается в ответе сервера.

Stored XSS Скрипт сохраняется в базе данных и затем воспроизводится для других пользователей.

DOM-based XSS Уязвимость возникает на стороне клиента при небезопасной обработке данных в DOM.

Ключевая особенность всех типов — наличие неконтролируемого пользовательского ввода, который проходит без достаточной проверки или очистки.


Валидация как снижение поверхности атаки

Валидация не устраняет XSS напрямую, но уменьшает количество допустимых входных вариантов, что снижает вероятность внедрения вредоносных конструкций.

Основная цель валидации:

  • ограничение типов данных
  • контроль длины
  • проверка формата
  • исключение неожиданных структур
  • фильтрация лишних полей

Пример: если поле должно содержать email, любые HTML-теги или скриптовые конструкции автоматически исключаются на уровне структуры данных.


Ограниченность валидации в контексте XSS

Критическая особенность: валидация не является механизмом санитизации.

Даже корректное значение, прошедшее проверку, может содержать потенциально опасные данные:

  • строка <img src=x oner ror=alert(1)> может пройти проверку IsString
  • URL может содержать javascript-схему в некоторых реализациях
  • текстовые поля комментариев могут содержать HTML

Поэтому class-validator используется как структурный фильтр, но не как инструмент очистки.


Декларативная модель проверки данных

Class-validator реализует подход, при котором правила описываются через декораторы DTO-классов.

import { IsString, MaxLength, MinLength } from "class-validator";

export class CommentDto {
  @IsString()
  @MinLength(1)
  @MaxLength(500)
  content: string;
}

Такая модель формирует строгий контракт: поле content всегда строка ограниченной длины. Это уже исключает часть атак, связанных с переполнением, вложенными структурами или неожиданными типами.


Базовые механизмы защиты структуры данных

Ключевые принципы, которые усиливают защиту от XSS при использовании class-validator:

Белый список полей (whitelisting)

Запрещает передачу неизвестных свойств:

import { validate } from "class-validator";

validate(dto, {
  whitelist: true,
  forbidNonWhitelisted: true,
});

Это предотвращает внедрение дополнительных полей, которые могут быть использованы в цепочках атак.


Жёсткая типизация DTO

DTO должен описывать только необходимые поля без избыточности:

import { IsEmail, IsString } from "class-validator";

export class UserDto {
  @IsEmail()
  email: string;

  @IsString()
  username: string;
}

Чем меньше поверхность входных данных, тем меньше вероятность эксплуатации.


Ограничение длины и формата

import { Matches, MaxLength } from "class-validator";

export class ProfileDto {
  @MaxLength(30)
  @Matches(/^[a-zA-Z0-9_]+$/)
  nickname: string;
}

Регулярные выражения позволяют исключить HTML-символы и управляющие последовательности.


Распространённые декораторы и их роль в безопасности

IsString / IsNumber / IsBoolean

Фильтрация по типу снижает риск передачи структурированных payload вместо примитивов.

MaxLength / MinLength

Ограничивают возможность вставки длинных скриптовых payload.

Matches

Позволяет реализовать строгий whitelist символов.

IsEmail / IsURL

Форматная проверка снижает вероятность внедрения нестандартных схем.

IsOptional

Позволяет явно управлять необязательными полями, исключая скрытые null/undefined обходы логики.


Вложенные структуры и риск каскадных атак

При работе с вложенными объектами важно использовать:

import { ValidateNested } from "class-validator";
import { Type } from "class-transformer";

export class AddressDto {
  @IsString()
  city: string;
}

export class UserDto {
  @ValidateNested()
  @Type(() => AddressDto)
  address: AddressDto;
}

Без ValidateNested вложенные объекты могут проходить без проверки, создавая обходные пути для внедрения вредоносных данных.


Разделение валидации и санитизации

Class-validator отвечает только за проверку. Очистка данных выполняется отдельно.

Типичная архитектура:

  • class-validator — структура и правила
  • class-transformer — преобразование типов
  • DOMPurify / аналог — очистка HTML

Пример опасной ошибки:

// Небезопасно
const html = dto.content;
document.innerHTML = html;

Даже при успешной валидации это создаёт XSS-уязвимость.


XSS и контекст вывода

Даже идеально провалидированные данные могут быть опасны при неправильном выводе.

Контексты:

  • HTML (вставка в DOM)
  • JavaScript (внутри скриптов)
  • URL (href/src)
  • CSS (inline стили)

Валидация не учитывает контекст, поэтому защита должна дополняться экранированием.


Слабые места при использовании class-validator

Использование any

data: any;

Полностью отключает защитный слой типов.

Отсутствие whitelist

Приводит к принятию неожиданных полей.

Проверка только на фронтенде

Клиентская валидация легко обходится.

Игнорирование вложенных объектов

Без ValidateNested структура остаётся уязвимой.


Поведенческие паттерны безопасной валидации

Строгая схема входных данных

Каждый endpoint должен иметь собственный DTO.

Запрет лишних полей

Whitelist включается глобально.

Минимизация доверия к данным

Любые данные считаются потенциально вредоносными до проверки.

Ограничение текстовых полей

Любые пользовательские строки имеют максимальную длину.


Валидация массивов и вложенных коллекций

Массивы часто используются как обходной канал для атак.

import { IsArray, IsString, MaxLength } from "class-validator";

export class TagsDto {
  @IsArray()
  @IsString({ each: true })
  @MaxLength(20, { each: true })
  tags: string[];
}

Без each: true элементы массива могут оставаться непроверенными.


Сочетание с серверными политиками безопасности

Валидация усиливается дополнительными механизмами:

  • Content Security Policy (CSP)
  • HTTP-only cookies
  • SameSite политики
  • экранирование выходных данных
  • запрет inline-скриптов

CSP особенно важна, так как даже при ошибке валидации она ограничивает выполнение внедрённого кода.


Контроль HTML-полей

Если приложение допускает HTML, классическая валидация недостаточна:

  • IsString не запрещает теги
  • MaxLength не удаляет script

В таких случаях требуется отдельная санитизация:

  • удаление script/style
  • фильтрация атрибутов onerror/onload
  • разрешённый список тегов

Типичные цепочки атаки при слабой валидации

  1. Пользователь отправляет payload в текстовое поле
  2. DTO принимает значение без ограничений
  3. Данные сохраняются в базу
  4. При рендеринге вставляются в DOM
  5. Скрипт выполняется в браузере другого пользователя

Каждое слабое звено усиливает следующее, и отсутствие строгой валидации на первом этапе делает остальные меры менее эффективными.


Роль преобразования данных перед валидацией

Преобразование типов перед проверкой уменьшает количество неожиданных сценариев:

import { Transform } from "class-transformer";

export class IdDto {
  @Transform(({ value }) => Number(value))
  id: number;
}

Это устраняет случаи, когда строка с вредоносным содержимым маскируется под числовой тип.


Итоговые принципы безопасной схемы

  • строгие DTO без избыточных полей
  • whitelist-валидация
  • ограничение длины строк
  • проверка массивов и вложенных объектов
  • отказ от any
  • разделение валидации и санитизации
  • контекстное экранирование при выводе
  • использование CSP как дополнительного слоя защиты