/** * SQL 注入安全测试 * * 测试目标: * - 验证所有数据库查询都使用参数化查询 * - 确保 Prisma ORM 正确处理特殊字符 * - 测试各种 SQL 注入攻击向量 * * @created 2025-12-13 */ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); describe('SQL 注入安全测试', () => { afterAll(async () => { await prisma.$disconnect(); }); describe('用户查询 SQL 注入测试', () => { it('应该安全处理包含 SQL 关键字的邮箱', async () => { const maliciousEmail = "admin' OR '1'='1"; // Prisma 会自动使用参数化查询 const user = await prisma.user.findUnique({ where: { email: maliciousEmail }, }); // 应该返回 null,因为没有这个邮箱的用户 expect(user).toBeNull(); }); it('应该安全处理包含 SQL 注释的邮箱', async () => { const maliciousEmail = "admin@example.com'; --"; const user = await prisma.user.findUnique({ where: { email: maliciousEmail }, }); expect(user).toBeNull(); }); it('应该安全处理包含 UNION 查询的邮箱', async () => { const maliciousEmail = "admin@example.com' UNION SELECT * FROM User --"; const user = await prisma.user.findUnique({ where: { email: maliciousEmail }, }); expect(user).toBeNull(); }); it('应该安全处理包含 DROP TABLE 的输入', async () => { const maliciousEmail = "admin@example.com'; DROP TABLE User; --"; const user = await prisma.user.findUnique({ where: { email: maliciousEmail }, }); expect(user).toBeNull(); // 验证 User 表仍然存在 const userCount = await prisma.user.count(); expect(userCount).toBeGreaterThanOrEqual(0); }); }); describe('产品搜索 SQL 注入测试', () => { it('应该安全处理包含 SQL 关键字的搜索查询', async () => { const maliciousQuery = "'; DROP TABLE Product; --"; // 使用 contains 进行模糊搜索 const products = await prisma.product.findMany({ where: { OR: [ { name: { contains: maliciousQuery, mode: 'insensitive' } }, { modelNumber: { contains: maliciousQuery, mode: 'insensitive' } }, ], }, }); // 应该返回空数组或正常结果,不会执行 DROP TABLE expect(Array.isArray(products)).toBe(true); // 验证 Product 表仍然存在 const productCount = await prisma.product.count(); expect(productCount).toBeGreaterThanOrEqual(0); }); it('应该安全处理包含单引号的搜索查询', async () => { const queryWithQuote = "O'Reilly"; const products = await prisma.product.findMany({ where: { name: { contains: queryWithQuote, mode: 'insensitive' }, }, }); // 应该正常处理,不会导致 SQL 错误 expect(Array.isArray(products)).toBe(true); }); it('应该安全处理包含反斜杠的搜索查询', async () => { const queryWithBackslash = "test\\product"; const products = await prisma.product.findMany({ where: { name: { contains: queryWithBackslash, mode: 'insensitive' }, }, }); expect(Array.isArray(products)).toBe(true); }); }); describe('用户 ID 查询 SQL 注入测试', () => { it('应该安全处理包含 SQL 关键字的 ID', async () => { const maliciousId = "1 OR 1=1"; // Prisma 会验证 ID 类型 await expect( prisma.user.findUnique({ where: { id: maliciousId }, }) ).resolves.toBeNull(); }); it('应该安全处理包含特殊字符的 ID', async () => { const maliciousId = "1'; DROP TABLE User; --"; await expect( prisma.user.findUnique({ where: { id: maliciousId }, }) ).resolves.toBeNull(); }); }); describe('复杂查询 SQL 注入测试', () => { it('应该安全处理包含 SQL 注入的 WHERE 条件', async () => { const maliciousName = "admin' OR '1'='1"; const maliciousEmail = "admin@example.com' OR '1'='1"; const users = await prisma.user.findMany({ where: { AND: [ { name: maliciousName }, { email: maliciousEmail }, ], }, }); // 应该返回空数组,因为没有匹配的用户 expect(users).toEqual([]); }); it('应该安全处理包含 SQL 注入的 ORDER BY', async () => { // Prisma 的 orderBy 是类型安全的,不接受字符串 const products = await prisma.product.findMany({ orderBy: { createdAt: 'desc', }, }); expect(Array.isArray(products)).toBe(true); }); it('应该安全处理包含 SQL 注入的分页参数', async () => { // 测试恶意的 skip 和 take 参数 const maliciousSkip = "0; DROP TABLE Product; --"; const maliciousTake = "10 OR 1=1"; // Prisma 会验证参数类型,这些会导致类型错误 // 在实际应用中,应该在 API 层面验证这些参数 await expect(async () => { await prisma.product.findMany({ skip: parseInt(maliciousSkip) || 0, take: parseInt(maliciousTake) || 10, }); }).not.toThrow(); }); }); describe('原始 SQL 查询安全测试', () => { it('应该使用参数化查询执行原始 SQL', async () => { const maliciousEmail = "admin' OR '1'='1"; // 使用 Prisma 的 $queryRaw 和参数化查询 const users = await prisma.$queryRaw` SELECT * FROM "User" WHERE email = ${maliciousEmail} ` as unknown[]; // 应该返回空数组,参数被正确转义 expect(Array.isArray(users)).toBe(true); expect(users.length).toBe(0); }); it('不应该使用字符串拼接执行原始 SQL', async () => { // 这是一个反例,展示不安全的做法 // 在实际代码中应该避免这种写法 const maliciousEmail = "admin' OR '1'='1"; // ❌ 不安全的做法(仅用于测试) // const unsafeQuery = `SELECT * FROM "User" WHERE email = '${maliciousEmail}'`; // ✅ 安全的做法:使用参数化查询 const safeUsers = await prisma.$queryRaw` SELECT * FROM "User" WHERE email = ${maliciousEmail} `; expect(Array.isArray(safeUsers)).toBe(true); }); }); describe('批量操作 SQL 注入测试', () => { it('应该安全处理批量更新操作', async () => { const maliciousData = { name: "admin' OR '1'='1", email: "admin@example.com'; DROP TABLE User; --", }; // 尝试批量更新(实际上不会匹配任何记录) const result = await prisma.user.updateMany({ where: { email: maliciousData.email, }, data: { name: maliciousData.name, }, }); // 应该返回 0 条更新记录 expect(result.count).toBe(0); // 验证表仍然存在 const userCount = await prisma.user.count(); expect(userCount).toBeGreaterThanOrEqual(0); }); it('应该安全处理批量删除操作', async () => { const maliciousCondition = "1=1; DROP TABLE User; --"; // Prisma 的类型系统会阻止这种查询 // 这里我们测试正常的删除操作 const result = await prisma.user.deleteMany({ where: { email: maliciousCondition, }, }); expect(result.count).toBe(0); }); }); }); /** * 测试总结: * * Prisma ORM 的安全特性: * 1. ✅ 自动使用参数化查询 * 2. ✅ 类型安全的查询构建 * 3. ✅ 自动转义特殊字符 * 4. ✅ 防止 SQL 注入攻击 * * 最佳实践: * 1. 始终使用 Prisma 的查询 API,避免原始 SQL * 2. 如果必须使用原始 SQL,使用 $queryRaw 和参数化查询 * 3. 在 API 层面验证和清理用户输入 * 4. 使用 TypeScript 的类型系统增强安全性 * * 结论: * Prisma ORM 提供了强大的 SQL 注入防护, * 只要正确使用其 API,系统就能有效防止 SQL 注入攻击。 */