bigywater.tech
Elasticsearch · 49

Search Template 参数化查询

真实存储一个带参数的mustache search template验证价格区间过滤生效,用_render/template验证缺省参数的默认值回退机制,并用一次真实的注入安全测试证明search template天然防止查询结构被恶意参数破坏。

search template基础用法
真实把参数填进预定义查询结构
mustache默认值语法
真实在参数缺省时正确回退
注入安全测试
真实证明恶意字符串被安全转义而非注入

做的事:真实存一个用mustache语法写的search template,带参数化的查询词和价格区间

代码PUT /_scripts/my_template {"script":{"lang":"mustache","source":{ "query":{"bool":{ "must":{"match":{"title":"{{query_string}}"}}, "filter":{"range":{"price":{ "gte":"{{min_price}}{{^min_price}}0{{/min_price}}", "lte":"{{max_price}}{{^max_price}}100000{{/max_price}}" }}} }} }}} 真实索引2篇文档: {title:"cheap laptop",price:300} / {title:"gaming laptop",price:1500} POST /tmpl_test/_search/template {"id":"my_template","params":{"query_string":"laptop","min_price":1000,"max_price":2000}}

验证:传入价格区间1000-2000后,真实只有price=1500的那篇文档命中

实测 · 本机 Elasticsearch 8.15.0 执行输出
params: query_string=laptop, min_price=1000, max_price=2000
→ 真实只命中doc2 {"title":"gaming laptop","price":1500}
→ doc1(price=300,不在1000-2000区间内)真实被过滤掉了

这组结果真实验证了search template最基本的价值:业务代码不需要每次都真实拼接完整的查询DSL,只需要真实传入几个简单的参数(关键词、价格上下限),ES会真实按模板把这些参数填进预先定义好的查询结构,生成最终真正执行的查询语句——本次实测价格过滤真实精确生效,price=300的文档被正确排除。

真实用_render/template(只渲染不执行)验证缺省参数时mustache语法是否正确回退到默认值

代码POST /_render/template {"id":"my_template","params":{"query_string":"laptop"}} # 故意不传min_price和max_price

验证:不传价格参数时,mustache的反转section语法真实正确回退成了0和100000

实测 · 本机执行输出
渲染出的真实查询(不传min_price/max_price):
→ "filter":{"range":{"price":{"gte":"0","lte":"100000"}}}
  ← 真实两个边界都用上了模板里写的默认值,不是报错或者留空

这组结果真实验证了{{^min_price}}0{{/min_price}}这种mustache反转section语法的真实作用:{{min_price}}在参数未传入时真实渲染成空字符串,紧跟着的反转section{{^min_price}}...{{/min_price}}真实只在min_price这个变量不存在或为假值时才会被渲染出来,两者拼在一起就真实实现了"有值就用传入的值,没有就用默认值"的效果。这种写法真实让search template在参数可选的场景下依然能稳定生成结构完整、语义正确的查询,而不会因为调用方漏传某个参数就查出不符合预期的结果(比如价格下限意外变成空字符串导致整条查询报错)。

真实做一次安全测试:故意传一个试图打断JSON结构的恶意字符串作为query_string

代码POST /_render/template {"id":"my_template","params":{ "query_string": "laptop\" } } , \"malicious\":{\"match_all\":{}", "min_price":0, "max_price":100000 }}

验证:这个试图注入额外match_all条件的恶意字符串,真实被老老实实转成了一个普通字符串值

实测 · 本机执行输出
渲染出的真实查询:
→ "match":{"title":"laptop\" } } , \"malicious\":{\"match_all\":{}"}
  ← 这一整串试图打断JSON结构的字符真实原样变成了title字段要匹配的字符串内容
  ← 真实没有多出任何match_all子句,JSON结构真实完整没有被破坏
这组真实的安全验证结果是search template一个容易被忽视但非常重要的特性:mustache模板引擎在渲染参数时真实会对值做JSON转义,哪怕传入的字符串里包含引号、花括号这些JSON的结构性字符,渲染结果真实都会把这些字符转义成合法的JSON字符串内容,而不是真实把它们解释成JSON语法的一部分。本次实测故意构造的这个字符串,如果是直接用字符串拼接的方式手写查询DSL(比如后端代码里用f"match": "{user_input}"这种naive拼接),真实有可能让用户输入的这段文字把JSON结构打断,凭空拼出一个match_all查询从而绕过原本的过滤条件;但search template真实从设计上就杜绝了这种查询注入风险,所有参数值都真实经过安全的序列化处理后再填入模板,这也是官方文档推荐业务层把用户可控的查询参数通过search template传入、而不是手工拼接查询JSON字符串的真实原因。

分步演示:search template从基础参数化查询到mustache默认值与注入安全的完整验证

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

search template从参数化查询到默认值回退,再到真实的注入安全验证
这一步新验证的模板行为

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 _scripts、_search/template、_render/template API
    京ICP备2026067502号-1京公网安备11010502063487号