搜索框是SQL注入高危入口,因其需动态拼接LIKE、ORDER BY等SQL结构且用户输入不可控;后端白名单必须精确到字段级,覆盖q(关键词)、sort(排序字段)、filter(筛选值)三类参数,并严防Unicode绕过、原型污染及URL编码注入。

搜索框是SQL注入高危入口,前端校验完全无效,白名单必须落在后端且精确到字段级。
为什么搜索框特别危险
搜索功能常需动态拼接 LIKE、ORDER BY 或多字段模糊匹配,容易触发字符串拼接;而用户输入又高度不可控——空格、单引号、括号、分号、Unicode零宽字符都可能绕过简单过滤。更关键的是,很多团队误以为“加了 mysql.escape() 就安全”,但该函数对 ORDER BY name ASC 这类结构型输入毫无防护力。
- 常见错误现象:
WHERE title LIKE '%${req.query.q}%'直接拼接;或用escape()后仍允许输入name' OR '1'='1 - 使用场景:商品搜索、日志检索、后台管理模糊查用户/订单
- 性能影响:正则校验或枚举查表开销可忽略,但若每次校验都查数据库或读 YAML 配置,会引入延迟和单点故障
后端白名单校验必须覆盖三类字段
不能只防“值”,更要防“结构”。搜索接口通常涉及三类需白名单的参数:
-
q(搜索关键词):限制长度(如 ≤ 50 字)、禁止控制字符、强制去除首尾空格、转义通配符%和_—— 不是删掉,而是用\%转义后在 SQL 中加ESCAPE '\' -
sort(排序字段):只允许预定义字段名,如['title', 'created_at', 'score'];严禁接受title ASC, email DESC这种带语法的输入 -
filter(筛选条件):比如 status=active,必须校验值是否在['active', 'draft', 'archived']中,且拒绝active'--这类带注释的变体
Node.js 示例:用常量对象 + hasOwnProperty 防原型污染
别写 if (['a','b'].includes(input)),数组易被原型污染覆盖。用不可枚举的常量对象更稳妥:
const VALID_SORT_FIELDS = { title: true, created_at: true, score: true };
function validateSortField(input) {
if (!input || typeof input !== 'string') return false;
// 用 hasOwnProperty.call 避免 Object.prototype 被篡改
return Object.prototype.hasOwnProperty.call(VALID_SORT_FIELDS, input);
}
// 调用
if (!validateSortField(req.query.sort)) {
throw new Error('Invalid sort field');
}
注意:req.query.sort 必须是字符串,且不能含空格或逗号;如果前端传 sort=title,DESC,应拆解后分别校验字段名和方向(DESC 也需白名单)。
容易被忽略的边界情况
白名单不是一劳永逸的事。以下几点在真实项目中高频出问题:
- 用户输入含全角单引号(‘)、中文引号(“)、Unicode 替换字符(U+200B 零宽空格),正则
/^[a-zA-Z0-9_]+$/会放过它们 -
q=abc%20OR%201=1是 URL 编码后的注入,后端收到的是解码后的字符串,校验前必须确保已 decode - ORM 如 Sequelize 的
where: { [Op.or]: [...] }安全,但若手动拼WHERE ${rawCondition},哪怕只用于内部日志查询,也等于开门揖盗 - 搜索接口若支持“高级语法”(如
site:example.com AND type:pdf),解析器必须用白名单驱动的 DSL,而非eval或正则替换
真正难的不是写校验逻辑,而是把所有动态拼接点——包括日志埋点、审计记录、导出 SQL 模板——全部纳入白名单管控范围。漏掉一个,整套防线就形同虚设。

















