搜索框sql注入防御必须采用参数化查询,辅以白名单校验;单纯字符转义不可靠,全文索引不防注入,响应行为需统一避免信息泄露。

搜索框是SQL注入高发区,单纯对特殊字符转义(如 addslashes()、mysql_real_escape_string())既不可靠也不够用;全文索引(如 MySQL FULLTEXT)本身不防注入,它只是查询方式——真正防线必须落在参数化查询上。
为什么“转义特殊字符”在搜索场景下基本失效
开发者常以为把单引号替换成两个单引号、或用 addslashes() 就安全了,但这是错觉:
- MySQL 宽字节注入可绕过
addslashes()(如 %df%27 在 GBK 编码下被解析为合法单引号) -
LIKE子句中通配符%和_本身不是 SQL 元字符,但攻击者会把它和' OR 1=1 --混合使用,转义逻辑根本覆盖不到 - 不同数据库转义规则不一致(PostgreSQL 不认
'',SQL Server 用'',MySQL 用\'),硬编码转义极易漏判 - 前端传来的
keyword可能含 URL 编码、Unicode 零宽空格、BOM 头等,清洗前先得解码,而解码顺序错误就会导致绕过
全文索引(FULLTEXT)不能替代参数化,但能辅助收敛风险面
启用 FULLTEXT 索引本身不会让 SQL 更安全,但它能天然限制某些注入手法的发挥空间:
- 全文查询语法固定(如
MATCH(title) AGAINST(? IN NATURAL LANGUAGE MODE)),不支持任意子查询或UNION SELECT注入(除非你手动拼接) - MySQL 的
AGAINST()只接受字符串字面量或参数占位符,不支持表达式,客观上堵死了OR 1=1类逻辑注入路径 - 但注意:如果你写成
"MATCH(title) AGAINST('" . $kw . "')"—— 仍属字符串拼接,$kw里塞test' AND SLEEP(5) --照样触发基于时间的盲注 - PostgreSQL 的
to_tsquery()同理,必须用$1占位符传参,不能用||拼接用户输入
搜索框唯一靠谱的防御组合:参数化 + 白名单前置校验
参数化是底线,白名单是保险丝。两者缺一不可:
- Python Flask + SQLAlchemy:用
filter(Article.title.like('%' + keyword + '%'))(+ 拼接在 Python 层,SQL 参数仍由驱动绑定),禁用任何f-string或format() - Java MyBatis:所有用户输入强制走
#{keyword},${}只用于固定字段名(如动态表名),且需额外白名单校验 - PHP PDO:必须用
prepare("SELECT * FROM article WHERE title LIKE :kw")+bindValue(':kw', "%{$keyword}%"),禁用bindParam()绑定变量名时传用户输入 - 白名单校验示例(PHP):
if (!preg_match('/^[x{4e00}-x{9fa5}a-zA-Z0-9s.,!?-]+$/u', $keyword)) { die('invalid input'); }—— 这步不是为了“防注入”,而是快速拦截明显异常流量,减轻 DB 压力
最易被忽略的一点:搜索接口的响应行为要统一。别让 ' 触发数据库报错而 abc 返回 200,这种差异本身就是信息泄露——攻击者靠响应时间、状态码、空结果集就能完成盲注。所有异常都应走同一套错误处理,返回 200 + 通用提示,日志里记细节。











