← 中文资源

简体中文版本

TypeORM 参数化查询安全

让 TypeORM 查询远离注入:占位符绑定全部输入值,LIKE 通配符先转义再拼接,拒绝任何形式的裸 SQL 片段拼接。

阅读英文原文

示例说明

ORM 并非天然免疫注入——createQueryBuilder 的 where 里手拼字符串同样会中招。安全基线是:SQL 骨架只含结构文本,所有用户输入通过命名参数传入。LIKE 是特殊陷阱:用户输入里的百分号下划线会被当通配符解释,先转义再作为参数值传递,既防语法注入又防通配符滥用。

技术要点

  • 命名参数让值与 SQL 骨架分离
  • LIKE 通配符转义防止语义滥用
  • 裸片段拼接被显式策略拒绝

代码解读

searchByDomain 把域名里的特殊字符加反斜杠转义后包进百分号构成模式参数;status 同样走绑定。rejectUnsafeFragment 演示纵深防御:即便有人想拼片段,检出分号注释等 SQL 语法痕迹立即拒绝。查询结束 destroy 关闭连接池。

使用建议

排序字段这类无法参数化的结构项用白名单映射。审计代码时检索字符串拼接进 where 的反模式。数据库账号权限最小化是最后一道闸。

代码示例

import { DataSource, Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity({ name: 'accounts' })
class Account {
  @PrimaryGeneratedColumn() id!: number;
  @Column() email!: string;
  @Column() status!: string;
}

const dataSource = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  entities: [Account],
});

await dataSource.initialize();
const repo = dataSource.getRepository(Account);

async function searchByDomain(domain: string) {
  // LIKE 模式里的通配符要转义,值仍走参数绑定
  const escaped = domain.replace(/[\\%_]/g, (ch) => '\\' + ch);

  return repo
    .createQueryBuilder('account')
    .where('account.email LIKE :pattern', { pattern: '%' + escaped + '%' })
    .andWhere('account.status = :status', { status: 'active' })
    .orderBy('account.id', 'ASC')
    .limit(50)
    .getMany();
}

function rejectUnsafeFragment(input: string): never {
  if (/;|--|\/\*/.test(input)) {
    throw new Error('suspicious SQL fragment rejected');
  }
  throw new Error('raw fragments are not allowed');
}

const matches = await searchByDomain('example.com');
console.log('accounts found:', matches.length);
await dataSource.destroy();