Class Validator 不防 SQL 注入,仅做输入格式校验;必须配合 ValidationPipe 全局启用、白名单模式及数据库层参数化查询才能有效降低风险。

Class Validator 本身不防 SQL 注入,但它能拦截恶意输入,间接降低注入风险——前提是它被正确集成到请求生命周期中,并配合参数化查询使用。
Class Validator 的作用边界在哪
很多人误以为加了 @IsEmail() 或 @MinLength(8) 就等于防住了 SQL 注入。其实不是:Class Validator 只做输入格式校验,不碰数据库操作。它防不住绕过 DTO 的原始查询,也拦不下已通过验证但语义异常的数据(比如用户名填 "admin'; DROP TABLE users; --",只要长度、字符类型合法,ValidationPipe 就会放行。
- 它只在请求体、查询参数、路由参数等进入控制器前生效
- 对
req.rawBody、req.headers中未声明的字段默认不校验(除非开启whitelist: true) - 不处理数据库层拼接,也不影响 TypeORM 的
queryRunner.query()等原始执行逻辑
必须搭配 ValidationPipe 全局启用
单独给 DTO 加装饰器没用,必须让 NestJS 在每条请求路径上自动触发校验。否则 DTO 只是普通 TS 接口,不执行任何验证逻辑。
- 在
main.ts中调用app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })) -
whitelist: true会丢弃 DTO 里没定义的字段,防止攻击者传入__proto__、constructor等危险键名 -
forbidNonWhitelisted: true直接 400 拒绝含未知字段的请求,比静默丢弃更安全 - 别漏掉
transform: true,否则字符串数字(如"123")不会转成number类型,可能在后续 ORM 查询中引发隐式类型问题
验证规则要覆盖常见注入特征
基础格式校验(如邮箱、长度)不够,得主动过滤 SQL 关键字和特殊符号。Class Validator 提供 @Matches() 和自定义装饰器能力:
- 对可能拼进 SQL 的字段(如搜索关键词、排序字段),用正则限制字符集:
@Matches(/^[a-zA-Z0-9_\-\s]+$/, { message: '仅允许字母、数字、下划线、短横线和空格' }) - 避免直接校验 SQL 关键字(如
SELECT、UNION),因为大小写、编码、注释符(--、/* */)容易绕过;优先用白名单字符集 - 对 ID 类参数,强制用
@IsInt()+@Min(1),杜绝字符串型 ID 被用于原始查询 - 不要在 DTO 中暴露原始 SQL 片段字段(如
rawWhereClause: string),这类设计本身就是高危信号
真正防注入的关键在数据库层
Class Validator 是前置过滤器,不是防火墙。最终防线永远是数据库交互方式:
- TypeORM 的
find()、findOne()、createQueryBuilder().where()默认参数化,安全 - 若必须用
queryRunner.query('SELECT * FROM x WHERE y = ?', [value]),确保所有用户输入都走参数占位符,绝不字符串拼接 - 禁用
queryRunner.query(`SELECT * FROM x WHERE y = '${value}'`)—— 这种写法哪怕 value 已被 Class Validator 验证,仍可能因 Unicode 归一化、宽字节等边缘情况被绕过 - 环境变量中设置
NODE_ENV=production,关闭 TypeORM 的synchronize: true,防止开发配置泄漏到生产
最容易被忽略的是:验证管道和 ORM 参数化必须同时存在。只做一边,防护就断链。比如 DTO 验证很严,但某个管理后台接口绕过 DTO 直接读 req.query.q 并拼 SQL,整套防御就失效了。











