Elasticsearch 与 SQL 混用架构存在注入穿透风险,需严格校验字段名、禁用高危函数、双层类型验证、统一 DSL 抽象及 ID 列表清洗。

ES 本身不执行 SQL,所谓“Elasticsearch 与 SQL 混用架构”通常指两类场景:一是通过 Elasticsearch SQL 接口(/_sql)直接提交类 SQL 查询;二是业务层同时对接 MySQL/PostgreSQL 和 ES,前端搜索请求被路由到不同引擎,而参数在多层间透传。这两类场景下,“注入穿透”不是传统 SQL 注入,而是用户输入绕过某一层校验后,在另一层被当作可执行结构解析——比如过滤了 ES 的 script 字段,却没拦住 SQL 接口里的 WHERE 子句中嵌套的恶意 JSON 表达式。
避免在 Elasticsearch SQL 接口中拼接 WHERE 条件
Elasticsearch SQL 支持 SELECT * FROM index WHERE field = ? 形式的参数化查询,但很多人误以为它像 JDBC 一样严格隔离数据与结构。实际上,? 占位符只支持标量值(字符串、数字、布尔),不支持字段名、函数名、聚合表达式或嵌套 JSON 对象。
- 错误做法:
"SELECT * FROM logs WHERE " + user_input_field + " = ?"——user_input_field若为_source.keys或params._source,可触发字段枚举或脚本上下文泄露 - 正确做法:对
user_input_field强制白名单校验,仅允许如status、level、timestamp等预定义字段;SQL 查询模板固定,仅参数走params传入 - 禁用所有非必要函数:在
elasticsearch.yml中设置xpack.sql.enabled: true后,显式关闭高风险能力:xpack.sql.dialect.allow_scripting: false、xpack.sql.dialect.allow_aggs: false
跨引擎参数透传时必须做双层字段类型校验
当一个搜索请求既查 MySQL 又查 ES(例如“商品标题+库存状态”联合筛选),用户传入的 sort、filter、aggs 参数可能被分别喂给两个系统。MySQL 的 ORDER BY ? 不会执行代码,但 ES 的 sort 字段若未校验类型,就可能被用于访问元字段或触发动态映射。
- 对每个字段名,先查 MySQL 的
INFORMATION_SCHEMA.COLUMNS确认其存在且类型安全(如非TEXT或JSON);再查 ES 的GET /index/_mapping,确认该字段在 mapping 中是keyword或date,而非text、object或未定义 - 禁止将同一字段名直接复用于两个引擎的排序/聚合:MySQL 的
price是 DECIMAL,ES 的price可能是float或keyword,类型错配会导致 ES 返回空结果或 400 错误,反而暴露底层结构 - 统一抽象过滤条件为中间 DSL(如自定义 JSON Schema),由网关层翻译成 MySQL WHERE 和 ES query,避免原始参数直通任一后端
警惕 SQL 接口返回结果反向污染 ES 查询上下文
有些系统用 Elasticsearch SQL 查出 ID 列表,再拿这些 ID 去 terms 查询 ES 全文字段。如果 SQL 查询本身被注入(如 WHERE id IN (1,2) UNION SELECT _id FROM secrets),返回的 ID 列表就可能包含非法值,导致后续 terms 查询命中不该查的文档,甚至触发 max_terms_count 超限熔断。
- SQL 查询必须启用
filter_path限制返回字段,只取业务需要的列(如id、score),禁用_source、_id等元字段输出 - 对 SQL 返回的 ID 列表做二次清洗:正则匹配
^[a-zA-Z0-9_-]{1,128}$,长度截断,去重,并限制总数(如size: 1000) - 后续 ES
terms查询改用terms_lookup机制,让 ES 自己从指定索引里拉取 ID,而不是把客户端传来的 ID 列表全量塞进去
最易被忽略的是:SQL 接口和原生 Query DSL 共享同一套认证、限流、审计日志配置。一个被绕过的 SQL 查询,可能比同等复杂度的 DSL 查询更难被 WAF 或 SIEM 规则捕获——因为它的语法看起来太像合法业务语句。防御的关键不是加更多正则,而是让每层都只相信自己验证过的那一小段输入。

















