全文检索本身不防SQL注入,因LIKE、MATCH AGAINST等动态构造查询时若未转义或参数化,攻击者可借引号、布尔符、分号等注入恶意代码,且MySQL AGAINST不支持占位符,风险尤高。

全文检索本身不防SQL注入,只要它最终拼进SQL语句里,风险就和普通字段一样高——尤其像 LIKE、MATCH AGAINST 这类动态构造的查询,稍不注意就会被绕过。
全文检索字段为什么特别容易中招?
因为全文检索常依赖用户输入构造模糊匹配逻辑,比如用 CONCAT('%', :keyword, '%') 做 LIKE,或直接把关键词塞进 MATCH(col) AGAINST(? IN NATURAL LANGUAGE MODE)。但这些接口本身不校验内容,也不自动转义——AGAINST 后面的参数如果未经处理就拼进去,攻击者输 test" OR 1=1 -- 就可能让整个条件失效。
-
MATCH...AGAINST不接受参数化占位符(MySQL 8.0 仍不支持在AGAINST子句中用?或命名参数) -
LIKE查询中,通配符%和_是合法语义字符,不能简单过滤,否则会破坏检索功能 - 部分 ORM(如 Django 的
__search)底层仍走字符串拼接,未强制参数化全文字段
MySQL FULLTEXT 检索必须手动转义的三个位置
MySQL 的 MATCH...AGAINST 要求输入是纯文本短语,但它的解析器对引号、括号、布尔操作符(+、-、>、<)敏感。攻击者可利用这些符号干扰语法结构,甚至闭合引号后注入额外子句。
- 对双引号
"和括号( )必须做转义:用\"替换",用\(和\)替换括号(MySQL 全文布尔模式认这个) - 过滤掉非法布尔前缀:如开头的
+、-、>、<,它们可能被用于构造+admin -password类绕过逻辑 - 禁用分号
;和注释符--、#:哪怕只出现在关键词中间,也要提前截断或拒绝整条请求
PostgreSQL tsvector + plainto_tsquery 怎么安全传参?
PostgreSQL 相对友好:plainto_tsquery() 和 to_tsquery() 都接受参数化输入,但前提是——你得确保传进去的是字符串字面量,而不是拼接后的 SQL 片段。
- 正确写法:
WHERE document @@ plainto_tsquery('english', ?),然后用PreparedStatement绑定参数(Java)或cursor.execute(..., [keyword])(Python) - 错误写法:
WHERE document @@ plainto_tsquery('english', '"'+keyword+'"')—— 这里单引号和双引号都可能被逃逸突破 - 注意
to_tsquery()支持布尔语法(&、|、!),若业务允许高级搜索,需白名单限制操作符,而非放任用户输入任意组合
ES + SQL 插件场景下最容易忽略的漏点
Elasticsearch 的 sql 接口(如 _sql REST endpoint)虽声称“类SQL”,但它底层不走数据库预编译,所有字段名、值、函数名全靠解析器硬拆——这意味着 SELECT * FROM logs WHERE message LIKE ? 中的 ? 在 ES 里只是字符串替换,不是参数绑定。
- ES SQL 不支持真正意义上的参数化,
params字段仅用于值替换,无法阻止字段名或函数名注入(如message; DROP TABLE logs) - 必须前置校验:关键词只允许 ASCII 字母、数字、空格、常见标点(
. , ! ? ( ) [ ]),其余一律拒绝 - 涉及动态字段名(如按时间分表
logs_202609)时,表名绝不能来自用户输入,必须查白名单映射表或用固定模板生成
最危险的不是不会写参数化,而是以为用了 AGAINST 或 @@ 就天然免疫——它们只是语法糖,不等于安全边界。真正起作用的,永远是你对输入做的第一道清洗和最后一道绑定。

















