Инъекционные уязвимости возникают там, где внешние данные без достаточной проверки попадают в интерпретируемый контекст: SQL-запросы, команды операционной системы, шаблоны поиска, NoSQL-выражения или генераторы HTML. Основная причина — отсутствие строгого разделения между данными и управляющей логикой.
Типовой сценарий:
const query = `SEL ECT * FR OM users WH ERE email = '${email}'`;
Если email содержит управляющие символы SQL, поведение
запроса меняется, превращая строку данных в часть логики выполнения.
Валидация в таких сценариях не заменяет параметризацию запросов, но формирует первый слой защиты: отсечение некорректных, неожидаемых и потенциально опасных структур данных до попадания в бизнес-логику.
Библиотека class-validator строится вокруг
декларативного описания ограничений через декораторы. Она работает на
уровне объектов, позволяя описывать контракт входных данных как класс с
набором правил.
Ключевая идея: данные считаются недоверенными до тех пор, пока не прошли проверку схемой
Пример базовой DTO-модели:
import { IsEmail, IsString, MinLength } fr om 'class-validator';
export class CreateUserDto {
@IsEmail()
email;
@IsString()
@MinLength(8)
password;
}
Такая модель формирует жесткий фильтр: любые отклонения от структуры приводят к ошибке валидации до попадания в слой доступа к данным.
Основной источник уязвимости — конкатенация строк:
const sql = "SEL ECT * FR OM users WH ERE name = '" + name + "'";
Даже если используется ORM, проблема сохраняется при динамических фильтрах.
Валидация снижает риск за счет ограничения допустимого набора символов и формата:
import { IsString, Matches, MaxLength } fr om 'class-validator';
export class SearchDto {
@IsString()
@MaxLength(50)
@Matches(/^[a-zA-Z0-9_ ]+$/)
query;
}
Регулярное выражение здесь выполняет роль ограничителя домена входных данных, исключая управляющие символы SQL.
NoSQL-базы часто принимают JSON-объекты напрямую, что открывает возможность подмены логики запроса:
// потенциально опасный input
{
"email": { "$ne": null }
}
Если такой объект проходит без фильтрации, он может изменить смысл запроса.
import { IsString, IsEmail } fr om 'class-validator';
export class LoginDto {
@IsEmail()
email;
@IsString()
password;
}
Использование примитивных типов (string, number) вместо объектов критично: любые структуры, отличные от строки, будут отклонены.
Инъекции часто строятся на неожиданных полях. Поэтому важна проверка не только значений, но и самой структуры объекта.
Пример DTO с ограничением допустимого формата:
import {
IsString,
IsInt,
Min,
Max,
IsOptional
} from 'class-validator';
export class ProductFilterDto {
@IsString()
category;
@IsOptional()
@IsInt()
@Min(0)
@Max(1000)
priceMin;
@IsOptional()
@IsInt()
@Min(0)
@Max(1000)
priceMax;
}
Такой подход предотвращает внедрение неожиданных операторов и вложенных конструкций, которые часто используются для обхода фильтров.
Инъекции могут быть не только синтаксическими, но и логическими: подмена условий, обход авторизации, изменение бизнес-логики.
Пример:
if (isAdmin === "true") {
grantAccess();
}
Без строгой типизации строка "false" может быть
интерпретирована как truthy-значение в других контекстах.
import { IsBoolean } from 'class-validator';
export class AccessDto {
@IsBoolean()
isAdmin;
}
Важно, что без трансформации входных данных строки
"true" и "false" не будут автоматически
приведены к boolean, что предотвращает скрытые ошибки интерпретации.
Инъекции могут проявляться через эксплуатацию крайних значений: огромные числа, отрицательные индексы, переполнения лимитов.
import { IsInt, Min, Max } from 'class-validator';
export class PaginationDto {
@IsInt()
@Min(0)
@Max(100)
lim it;
@IsInt()
@Min(0)
offset;
}
Такие ограничения защищают не только от атак, но и от деградации производительности через намеренно вредные запросы.
Подмена идентификаторов — частый вектор атак, особенно при доступе к ресурсам.
import { IsUUID } from 'class-validator';
export class GetUserDto {
@IsUUID()
id;
}
Без такого ограничения возможно внедрение строк, содержащих SQL/NoSQL-операторы или обход фильтров через альтернативные форматы идентификаторов.
Строковые поля — основной канал инъекций. Даже при использовании ORM или prepared statements они остаются опасными при логировании, шаблонизации или построении динамических фильтров.
import { IsString, MaxLength, Matches } from 'class-validator';
export class CommentDto {
@IsString()
@MaxLength(300)
@Matches(/^[^<>$]*$/)
text;
}
Ограничение символов <, > и
$ снижает риск внедрения шаблонов и управляющих
конструкций.
Инъекции часто прячутся во вложенных структурах JSON.
import { ValidateNested, IsString } from 'class-validator';
import { Type } from 'class-transformer';
class ProfileDto {
@IsString()
bio;
}
export class UserDto {
@IsString()
name;
@ValidateNested()
@Type(() => ProfileDto)
profile;
}
Без ValidateNested вложенные поля остаются
неконтролируемыми, создавая обходной путь для внедрения вредоносных
данных.
Валидация не заменяет экранирование и параметризацию. Она решает другую задачу: отсечение некорректной структуры до уровня бизнес-логики.
Комбинация этих уровней формирует устойчивую модель защиты от инъекций.
Чем меньше допустимых форм данных, тем ниже вероятность успешной инъекции. class-validator позволяет строить именно такую модель:
Эта стратегия уменьшает энтропию входных данных и делает поведение системы предсказуемым даже под нагрузкой некорректных запросов.