- 新增 PackagingPlan 数据库模型和迁移 - 实现包装方案 API (GET/POST/PUT/DELETE) - 创建 PackagingEditor 组件用于管理包装方案 - 在产品详情页集成包装方案标签页 - 修复 API 路由 params Promise 类型适配 (Next.js 16) - 修复脚本文件中 SKU ownerId 字段缺失问题 - 修复 jwt.ts、performance.ts、VirtualList.tsx 类型错误 - 修复测试文件导入路径和类型注解 - 所有 TypeScript 类型检查通过
276 lines
8.1 KiB
TypeScript
276 lines
8.1 KiB
TypeScript
/**
|
||
* 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 注入攻击。
|
||
*/
|