WuYongJie 5821d73291 初始提交:3D家具安装展示系统
包含:
- furniture-3d-system:Next.js 16 全栈应用(资产中心、产品管理、3D装配动画)
- autocad-plugin:AutoCAD 家具导出插件源码
- 资产中心改造:编辑弹窗化、删除功能、Toast反馈、自定义Select组件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 14:00:19 +08:00

276 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 注入攻击。
*/