Elasticsearch SQL接口比JDBC更危险,因其直接执行未校验的字符串SQL,支持script函数执行任意Painless表达式,而JDBC经DSL翻译且有字段/函数白名单限制。

ES 本身不执行 SQL,所谓“Elasticsearch 与 SQL 联动查询”,实际只有两种情况:一是用 Elasticsearch SQL 功能(POST /_sql),二是业务层用 JDBC/ODBC 驱动连 ES 做 SQL 查询。这两种场景都存在注入风险,但根源和解法完全不同——不能套用传统 SQL 注入的防护思路,否则会漏掉关键攻击面。
为什么 Elasticsearch SQL 接口比 JDBC 更危险
ES 的 _sql 接口接受纯字符串 SQL,且默认允许 SELECT、SHOW、DESCRIBE 等语句,攻击者一旦拿到该接口权限,可直接枚举索引、探测字段类型、甚至用 SCRIPT 函数执行任意 Painless 表达式(如 script("doc['@timestamp'].value"))。而 JDBC 驱动本质是把 SQL 翻译成 Query DSL 再发给 ES,中间多了一层解析和校验。
-
_sql接口不校验字段是否存在,SELECT * FROM logs WHERE user = 'admin' OR 1=1这类语句能直接执行 - JDBC 驱动对
WHERE条件中的字段名、函数名有白名单限制(如禁止script()、eval()) - ES 8.x 默认关闭
_sql接口,但很多老项目仍开着;JDBC 则常被误认为“安全”而放开权限
禁用 _sql 接口 + 白名单字段校验是底线
如果你的应用真需要 SQL 形式查询,优先走 JDBC,并确保服务端不暴露 _sql。若必须启用 _sql(比如 BI 工具直连),必须做两件事:
- 在
elasticsearch.yml中显式关闭:xpack.sql.enabled: false(ES 7.12+)或rest.action.multi.allow_expensive_queries: false - 所有用户输入的字段名、表名、函数参数,必须通过白名单校验。例如,
user_input_field只能是['title', 'status', 'created_at']中的一个,其他一概拒掉 - 禁止用户控制
ORDER BY字段值——ORDER BY后不能是变量,只能硬编码或从白名单映射
JDBC 场景下仍要防 Query DSL 滥用
JDBC 驱动看似安全,但攻击者可通过构造恶意 SQL 触发底层 DSL 的非预期行为。例如:
SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products)
这类子查询会被翻译成嵌套聚合,可能触发 search.max_buckets 限制或 OOM。更隐蔽的是:
- 用
REGEXP函数传入超长正则表达式,造成回溯爆炸 - 在
WHERE中写script("params._source.keys"),绕过字段白名单读取元数据 - 拼接
UNION ALL查询,让驱动生成多个search请求,放大资源消耗
应对方式不是改 SQL,而是限制 JDBC 层的执行能力:es.sql.jdbc.max_fetch_size 设为 1000,es.sql.jdbc.timeout 设为 5s,并在应用层拦截含 script(、eval(、regex( 的 SQL 字符串。
真正安全的做法:放弃 SQL,回归 DSL + 参数化构造
所有“SQL 联动”需求,最终都要落到 Query DSL 上。与其在 SQL 层做过滤,不如彻底放弃用户直输 SQL,改为前端传结构化参数,后端用 JSON 序列化生成 DSL:
- 搜索关键词 →
{"match": {"title": {"query": user_input}}},用json.dumps()构造,绝不字符串拼接 - 排序字段 → 映射到白名单:
sort_map = {"date": "created_at.keyword", "score": "score"},再塞进sort数组 - 聚合路径 → 对
aggs.terms.field做类型校验,只允许keyword类型字段,拒绝text或object
最易被忽略的一点:DSL 中的 script 字段、path 字段、field 字段,只要来源是用户输入,就必须经过白名单或类型校验——它们才是真正的注入入口,而不是 SQL 关键字本身。










