本文目录导读:

输入校验是防御恶意数据的第一道防线,其核心原则是“白名单优于黑名单”,即明确允许什么,而不是试图列出所有不允许的。
以下是系统化的输入校验策略,按层次和类型进行组织,用于有效拦截恶意数据:
第一层:基础语法与类型校验(最底层、最严格)
这是在任何业务逻辑之前都必须进行的校验,主要目标是确保数据符合预期的“形状”和“类型”。
-
强类型校验(Type Coercion & Check):
-
做法: 严格检查数据类型(整数、浮点数、布尔值、字符串、数组、对象等),不要依赖隐式类型转换。
-
示例(Node.js/TypeScript):
// 错误:允许任何字符串,即使它是 "NaN" 或 "Infinity" function processAge(age: string) { ... } // 正确:强制转换为数字,并检查结果是否为有效数字 function processAge(age: unknown): number { if (typeof age !== 'number' || isNaN(age) || !isFinite(age)) { throw new Error('年龄必须是有效数字'); } return age; }
-
-
长度与大小限制(Bounds Check):
- 做法: 对字符串、数组、数值设定严格的长度和范围限制。
- 为什么: 防止缓冲区溢出(Buffer Overflow)、拒绝服务攻击(通过超大 payload 耗尽内存)、以及长字符串的注入攻击。
- 示例:
- 用户名:
MIN_LENGTH=3, MAX_LENGTH=64 MAX_LENGTH=1000- 年龄:
0 <= age <= 150 - 文件上传: 文件大小限制(
maxFileSize: 10MB)
- 用户名:
-
格式与模式匹配(Pattern Validation):
- 做法: 使用正则表达式(Regex)或预定义格式校验。
- 白名单模式:
^[a-zA-Z0-9_]+$(只允许字母、数字、下划线)。 - 预定义格式: 电子邮件(遵循RFC 5321,但保守一点)、IP地址、UUID、URL(使用标准库,避免正则写漏)、银行卡号等。
- 特别注意: 绝对不要用正则去解析HTML(这是SQL注入的常见误区),应该用专门的HTML解析库。
第二层:内容安全校验(针对特定攻击)
在通过基础校验后,需要对内容本身进行安全检查,以防范注入和脚本攻击。
-
SQL注入防御(最经典):
-
正确做法: 永远不要拼接SQL字符串,始终使用参数化查询或预编译语句。
-
示例(Python/psycopg2):
# 错误:拼接字符串 cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'") # 正确:参数化查询 cursor.execute("SELECT * FROM users WHERE name = %s", (user_input,))
-
-
跨站脚本攻击(XSS)防御:
- 输出编码(Output Encoding) 是最终防线,但输入校验也能提供防护。
- 做法:
- HTML实体编码: 将
<,>,&, , 转换为<,>,&,",'。 - URL编码: 在URL参数中,使用
encodeURIComponent()。 - JavaScript编码: 在JavaScript上下文中,使用
\x3c等转义。 - 白名单HTML标签: 如果允许富文本(如Markdown编辑器),使用成熟的库(如
DOMPurify,sanitize-html)来移除所有不允许的标签和属性。
- HTML实体编码: 将
-
命令注入(Command Injection)防御:
- 绝对原则: 尽量不要使用系统命令(如
exec,system,shell_exec),如果必须使用,不要将用户输入直接传入。 - 做法:
- 如果只是执行一个文件,使用文件API(如
fs.readFile)而不是cat。 - 如果需要执行系统命令(如
ping),使用参数化方式或专门库。 - 转义/过滤: 对输入进行白名单过滤(只允许字母、数字、 和 ),并移除 , ,
&, , ,\n等shell特殊字符。
- 如果只是执行一个文件,使用文件API(如
- 绝对原则: 尽量不要使用系统命令(如
-
路径遍历(Path Traversal)防御:
- 做法:
- 白名单根目录: 只允许访问特定目录(如
/var/www/uploads/)。 - 标准化路径: 用
path.normalize()或realpath()解析 和 。 - 检查结果: 确认最终的路径是否在以白名单目录开头。
const safeDir = path.resolve('/var/www/uploads'); const userPath = path.resolve(safeDir, userInput); if (!userPath.startsWith(safeDir)) { throw new Error('禁止访问此路径'); } - 拒绝特殊字符: 直接拒绝包含 或 的输入。
- 白名单根目录: 只允许访问特定目录(如
- 做法:
-
NoSQL注入(如MongoDB):
- 做法: 使用ORM/ODM(如Mongoose, Prisma)自带的参数化查询,避免直接拼接JSON对象作为查询条件。
- 禁用运算符: 如果接受用户输入作为查询键,必须拦截 , 开头的键名(如
$ne,$regex,$where)。
-
文件上传漏洞防御:
- 文件类型验证: 不要仅依赖Content-Type头(可伪造),应读取文件头(Magic Number) 进行验证。
- JPEG:
FF D8 FF;PNG:89 50 4E 47;GIF:47 49 46 38。
- JPEG:
- 文件名净化: 移除或替换 , , 空字符(
\0),空格等,最佳做法是重命名文件(如用UUID)。 - 防病毒扫描: 在存储前进行扫描(如果可行)。
- 图片重处理(Re-processing): 如果是图片,用库(如Sharp, Pillow)重新编码,这会“展平”隐藏在图片中的恶意代码。
- 文件类型验证: 不要仅依赖Content-Type头(可伪造),应读取文件头(Magic Number) 进行验证。
第三层:业务逻辑与行为校验(最顶层、最复杂)
这部分防御的是“看起来合法,但行为恶意”的数据,如机器人攻击、重复提交、爬虫。
-
速率限制(Rate Limiting):
- 根据IP、用户ID、Session令牌限制每秒/每分钟的请求次数。
- 对敏感接口(登录、注册、发送验证码)实施更严格限制。
-
令牌与防重放(CSRF Token / Nonce):
- CSRF Token: 在表单中嵌入一个随机生成的、与用户Session绑定的Token,提交时验证,防止攻击者诱导用户执行操作。
- Nonce / 验证码: 防止一次性请求被重复提交(一次支付请求只能成功一次)。
-
请求来源验证(Referrer/Origin Header):
- 对于API接口,检查
Origin或Referer头,确保请求来自你自己的前端域名(但此头可伪造,作为辅助而非唯一防线)。
- 对于API接口,检查
-
内容安全策略(CSP,Content Security Policy):
- 虽然不直接校验输入,但可以限制浏览器只执行白名单的脚本、样式、图像,如果攻击者注入
<script>标签,CSP可以阻止其执行。
- 虽然不直接校验输入,但可以限制浏览器只执行白名单的脚本、样式、图像,如果攻击者注入
一个完整的输入校验流程
用户输入 --> 1. 基础校验
|-- 类型检查 (必须是数字/字符串/数组)
|-- 长度范围 (3-64 字符)
|-- 格式匹配 (正则白名单)
如果失败 --> 返回 400 Bad Request (拒绝)
如果通过 -->
|-- 2. 内容安全校验
| |-- SQL参数化 (数据库查询)
| |-- XSS消毒 (输出前,但输入时也可做初步)
| |-- 路径规范化 (文件操作)
| |-- 命令参数化 (系统调用)
| 如果失败 --> 返回 400 Bad Request (拒绝)
| 如果通过 -->
| |-- 3. 业务逻辑校验
| |-- 速率限制 (是否太快?)
| |-- CSRF Token (是否伪造?)
| |-- 重复提交检查 (已经处理过?)
| 如果失败 --> 429 Too Many Requests 或 403 Forbidden
| 如果通过 --> 执行业务逻辑 (处理数据)
关键原则回顾
- 永远不要信任客户端。 所有前端校验(JS校验)都是辅助性的,后端必须重新校验所有数据。
- 白名单优于黑名单。 明确允许的列表远比试图屏蔽所有恶意列表更安全、更易于维护。
- 深度防御(Defense in Depth)。 依靠多层校验,即使你做了参数化查询,也仍然要对HTML进行消毒以防XSS。
- 失败安全(Fail Secure)。 当校验系统抛异常或无法判断时,拒绝请求,而不是放行。
- 标准化与库。 使用成熟的、广泛使用的校验库(如:
- Java: Hibernate Validator (JSR 380), OWASP Java Encoder
- Python: Pydantic, Marshmallow, OWASP Python Beasts
- Node.js: Joi, Yup, Zod, Validator.js
- Go: 标准库
regexp,strconv,net/html等 - C/C++: 特别小心,使用安全函数(如
snprintf代替sprintf),使用内存安全语言或库。
通过系统化地应用上述策略,你可以有效地将绝大多数恶意数据拦截在系统之外。