6.4.5 输入验证:防止注入攻击
一句话破题
注入攻击的本质是:攻击者的输入被当作代码执行了。防御的关键是:把数据和代码严格分开。
核心价值
掌握输入验证能让你:
- 防止 SQL 注入导致数据泄露
- 防止命令注入导致服务器被控
- 通过安全审计
快速上手
使用 Zod 验证输入
typescript
import { z } from 'zod'
const UserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150),
})
export async function POST(request: Request) {
const body = await request.json()
const result = UserSchema.safeParse(body)
if (!result.success) {
return Response.json({ error: result.error.issues }, { status: 400 })
}
// 使用 result.data,类型安全
}使用 Prisma 防止 SQL 注入
typescript
// ✅ 安全:Prisma 自动参数化
const user = await prisma.user.findUnique({
where: { email: userInput }
})
// ✅ 安全:使用 $queryRaw 的模板字符串
const users = await prisma.$queryRaw`
SELECT * FROM users WHERE name = ${userInput}
`
// ❌ 危险:使用 $queryRawUnsafe
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM users WHERE name = '${userInput}'`
)常见验证规则
typescript
const schemas = {
// ID 格式
id: z.string().uuid(),
// 只允许字母数字
slug: z.string().regex(/^[a-zA-Z0-9-]+$/),
// 手机号
phone: z.string().regex(/^1[3-9]\d{9}$/),
// 限制长度
bio: z.string().max(500),
// 枚举值
role: z.enum(['user', 'admin', 'editor']),
// 数字范围
page: z.coerce.number().int().min(1).default(1),
}文件上传验证
typescript
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
const maxSize = 5 * 1024 * 1024 // 5MB
export async function POST(request: Request) {
const formData = await request.formData()
const file = formData.get('file') as File
if (!allowedTypes.includes(file.type)) {
return Response.json({ error: '不支持的文件类型' }, { status: 400 })
}
if (file.size > maxSize) {
return Response.json({ error: '文件太大' }, { status: 400 })
}
// 处理上传
}避坑指南
新手最容易犯的错
- 只在前端验证,后端不验证——前端验证可以被绕过
- 使用正则匹配"危险字符"——容易遗漏,应该用白名单
- 拼接 SQL 语句——永远使用参数化查询
- 信任文件扩展名——应该验证 MIME 类型
