本文目录导读:

脚本过滤非法请求参数是保障Web应用安全的重要环节,核心思想是不信赖任何来自客户端的数据,采用 白名单(只允许合法字符) 比 黑名单(禁止非法字符) 更安全。
以下从三个层面提供脚本方案:通用逻辑、代码实现(Node.js/Python/Java示例)、以及具体的防御场景。
通用过滤逻辑(建议集成到中间件)
一个健壮的过滤脚本通常包含以下步骤:
- 输入清洗(Sanitization):删除或编码危险的字符。
- 输入验证(Validation):检查格式、类型、长度、范围。
- 参数类型强制转换:确保
id是整数,page是数字。
代码实现示例
Node.js (Express 中间件示例)
使用 validator 库和 xss 库进行清洗。
// 安装: npm install validator xss
const validator = require('validator');
const xss = require('xss');
// 过滤函数:对传入的 query/body 进行递归清洗
function sanitizeObject(obj) {
if (typeof obj === 'string') {
// 1. 去除首尾空格(Trim)
let clean = obj.trim();
// 2. 转义 HTML 特殊字符,防止 XSS
clean = xss(clean);
// 3. 去除控制字符(ASCII 0-31, 127)
clean = clean.replace(/[\x00-\x1f\x7f]/g, '');
return clean;
} else if (Array.isArray(obj)) {
return obj.map(item => sanitizeObject(item));
} else if (obj && typeof obj === 'object') {
const cleaned = {};
for (const [key, value] of Object.entries(obj)) {
// 过滤参数名:只允许字母、数字、下划线、中划线
if (!/^[a-zA-Z0-9_-]+$/.test(key)) {
delete obj[key]; // 删除非法的参数名
continue;
}
cleaned[key] = sanitizeObject(value);
}
return cleaned;
}
return obj;
}
// Express 中间件
function paramFilter(req, res, next) {
try {
req.query = sanitizeObject(req.query);
req.body = sanitizeObject(req.body);
// 检查关键参数类型
if (req.query.id && !validator.isInt(req.query.id)) {
return res.status(400).json({ error: 'Invalid ID format' });
}
next();
} catch (error) {
return res.status(400).json({ error: 'Parameter sanitization failed' });
}
}
// 应用中间件(在路由之前)
app.use(paramFilter);
Python (Flask 示例)
使用 bleach 库清洗 HTML,使用正则校验。
import re
import bleach
from flask import request, abort, jsonify
# 白名单模式:只允许指定的字符
ALLOWED_PATTERN = re.compile(r'^[a-zA-Z0-9_\-\s\.\,\!\?]*$')
def sanitize_param(value, max_length=255):
"""清洗单个参数"""
if not isinstance(value, str):
return value
# 1. 截断长度(防止DOS攻击)
value = value[:max_length]
# 2. 去除不可见控制字符(除了空格和换行)
value = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', value)
# 3. 清洗HTML标签(防止XSS),允许一些安全标签如 <b>, <i>
value = bleach.clean(value, tags=['b', 'i', 'u'], strip=True)
return value
def filter_params():
"""过滤请求参数"""
filtered = {}
for key, value in request.args.items():
# 过滤参数名:只允许字母数字
if not re.match(r'^[a-zA-Z0-9_]+$', key):
continue # 跳过非法参数名
# 对值进行清洗
filtered[key] = sanitize_param(value)
return filtered
# 在视图函数中使用
@app.route('/search')
def search():
params = filter_params()
keyword = params.get('keyword', '')
# 具体业务验证
if len(keyword) > 0 and not ALLOWED_PATTERN.match(keyword):
abort(400, description="Keyword contains invalid characters")
return jsonify({"status": "ok", "keyword": keyword})
Java (Spring Boot 统一过滤)
创建自定义的 HttpServletRequestWrapper 或使用过滤器。
import org.springframework.web.util.HtmlUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.regex.Pattern;
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final Pattern[] XSS_PATTERNS = new Pattern[]{
Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("onload=(.*?) ", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL)
};
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value == null) return null;
return cleanXSS(value);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) return null;
String[] cleaned = new String[values.length];
for (int i = 0; i < values.length; i++) {
cleaned[i] = cleanXSS(values[i]);
}
return cleaned;
}
private String cleanXSS(String value) {
// 1. HTML 转义(核心)
value = HtmlUtils.htmlEscape(value);
// 2. 移除危险模式(第二道防线)
for (Pattern pattern : XSS_PATTERNS) {
value = pattern.matcher(value).replaceAll("");
}
// 3. 限制最大长度
if (value.length() > 1000) {
value = value.substring(0, 1000);
}
// 4. 移除控制字符
value = value.replaceAll("[\\x00-\\x1f\\x7f]", "");
return value;
}
}
针对不同攻击类型的专项过滤
| 攻击类型 | 过滤策略 | 示例正则/逻辑 |
|---|---|---|
| SQL 注入 | 永远不要自己拼SQL,使用参数化查询(PreparedStatement),过滤特殊关键字(, , , , ) | 使用 ORM / 参数化查询,配合 \b(UNION|SELECT|DROP)\b 等关键字检测 |
| XSS (跨站脚本) | HTML 实体编码 < > & " 为 < 等 |
使用 validator.escape() 或 bleach.clean() |
| 命令注入 | 严格白名单,只允许字母数字,禁用 , &, , , , \n | ^[a-zA-Z0-9_\-\.]+$ |
|
| 路径遍历 | 移除 , , ,使用 basename() 函数 |
path.normalize(path.basename(userInput)) |
| NoSQL 注入 | 对查询参数(如 $gt, $ne, $regex)进行类型校验,禁止传入对象/数组 |
拒绝 body 中的 {$ne: ''} 结构 |
| 文件上传 | 检查 MIME 类型、文件扩展名,重命名文件,限制大小 | 使用白名单扩展名:[jpg, png, gif] |
最佳实践总结
- 不要重复造轮子:使用成熟的库(OWASP ESAPI, validator.js, bleach, Spring Security)。
- 深度防御:前端验证(体验) + 后端验证(安全) + 数据库存储过程(备份)。
- 记录日志,不暴露细节:发现非法请求时,记录IP和参数,但返回统一的“400 Bad Request”。
- 超长字符串:对字符串参数设置最大长度(255 或 1024 字符),防止缓存区溢出或DOS。
- 参数名也要过滤:防止通过污染参数名绕过逻辑(例如注入
_method或__proto__)。 - JSON 与 NoSQL 注入:API 接收
application/json,不要直接使用JSON.parse,先对键值进行类型校验。
一个“偷懒但有效”的加固技巧
如果不确定具体规则,可以执行以下强制类型转换(适用于REST API):
- ID 参数:
id = parseInt(id, 10),如果转换失败,返回错误。 - 布尔值:只接受
true/false字符串,或0/1。 - 枚举值:用白名单数组
['admin', 'user', 'guest']校验。 - 富文本:使用专业的 HTML 清洗器(如 DOMPurify, bleach)清理后,再存入数据库。
最安全的脚本是结合了白名单验证、类型强制转换和成熟库的清洗逻辑,并且始终运行在服务端。