[CVE-2022-22980] Spring Data MongoDB SpEL 表达式注入
MongoDB 是一种面向文档的 NoSQL 数据库,具有可扩展性和灵活性,用于高容量数据存储。与传统关系数据库使用表和行不同,MongoDB 使用集合和文档。文档由键值对组成,这是 MongoDB 中的基本数据单元。
Spring Data for MongoDB 是 Spring Data 总括项目的一部分,旨在为新的数据存储提供熟悉且一致的基于 Spring 的编程模型,同时保留存储特有的特性和功能。Spring Data MongoDB 项目提供了与 MongoDB 文档数据库的集成。Spring Data MongoDB 的关键功能领域包括以 POJO 为中心的模型,用于与 MongoDB DBCollection 交互,以及轻松编写 Repository 风格的数据访问层。
2022 年 6 月 20 日,VMware 在其官方网站上发布了一份安全公告,涉及影响 Spring Data MongoDB 的 SpEL 表达式注入(可导致远程代码执行)漏洞。您可以在下方找到有关 CVE-2022-22980 漏洞的详细信息。
漏洞
当使用带有 SpEL 表达式的 @Query 或 @Aggregation 注解查询方法时,如果这些表达式包含用于值绑定的查询参数占位符,且输入未经净化处理,则 Spring Data MongoDB 应用程序容易受到 SpEL 注入攻击。此外,在不涉及额外应用程序代码的情况下暴露 repository 查询方法的配置(例如 Spring Data REST)同样容易受到攻击。
具体而言,当满足以下所有条件时,应用程序容易受到攻击:
@Query 或 @Aggregation 注解,使用 SpEL(Spring Expression Language)并在 SpEL 表达式中使用输入参数引用(?0、?1、…)如果满足以下任一条件,则应用程序不易受到攻击:
QueryMethodEvaluationContextProvider受影响版本
Spring Data MongoDB 3.4.0、3.3.0 至 3.3.4 及更早版本受到 CVE-2022-22980 Spring Data MongoDB SpEL 表达式注入 漏洞的影响。
状态
包含修复程序的 Spring Data MongoDB 3.4.1 和 3.3.5 已发布。
缓解措施和建议的临时解决方案
首选方案是升级到 Spring Data MongoDB 3.4.1 和 3.3.5 或更高版本。如果您已完成升级,则无需任何临时解决方案。但是,某些用户可能无法快速完成升级。因此,Spring 团队在下方提供了一些临时解决方案。
使用数组语法: 如果应用程序需要由用户输入控制的动态 SpEL 表达式,请重写查询或聚合声明,在表达式中使用参数引用(用 [0] 代替 ?0)实现自定义 repository 方法: 使用自定义 repository 方法实现替换 SpEL 表达式是一种可行的临时解决方案,可以在应用程序代码中组装动态查询。有关更多详细信息,请参阅 关于 repository 自定义的参考文档。补丁分析:GitHub Issue 和相关提交
SpEL 注入漏洞的 GitHub issue 可从 github.com/spring-projects/spring-data-mongodb/issues/4089 访问。
借助以下两个提交,相关漏洞已得到修复。
通过这些提交,新增了 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/EvaluationContextExpressionEvaluator.java 类。
class EvaluationContextExpressionEvaluator implements SpELExpressionEvaluator {
ValueProvider valueProvider;
ExpressionParser expressionParser;
Supplier<EvaluationContext> evaluationContext;
public EvaluationContextExpressionEvaluator(ValueProvider valueProvider, ExpressionParser expressionParser,
Supplier<EvaluationContext> evaluationContext) {
this.valueProvider = valueProvider;
this.expressionParser = expressionParser;
this.evaluationContext = evaluationContext;
}
@Nullable
@Override
public <T> T evaluate(String expression) {
return evaluateExpression(expression, Collections.emptyMap());
}
public EvaluationContext getEvaluationContext(String expressionString) {
return evaluationContext != null ? evaluationContext.get() : new StandardEvaluationContext();
}
public SpelExpression getParsedExpression(String expressionString) {
return (SpelExpression) (expressionParser != null ? expressionParser : new SpelExpressionParser())
.parseExpression(expressionString);
}
public <T> T evaluateExpression(String expressionString, Map<String, Object> variables) {
SpelExpression expression = getParsedExpression(expressionString);
EvaluationContext ctx = getEvaluationContext(expressionString);
variables.entrySet().forEach(entry -> ctx.setVariable(entry.getKey(), entry.getValue()));
Object result = expression.getValue(ctx, Object.class);
return (T) result;
}
}


package org.springframework.data.mongodb.util.json;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.data.util.Lazy;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
/**
* Reusable context for binding parameters to a placeholder or a SpEL expression within a JSON structure. <br />
* To be used along with {@link ParameterBindingDocumentCodec#decode(String, ParameterBindingContext)}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.2
*/
public class ParameterBindingContext {
private final ValueProvider valueProvider;
private final SpELExpressionEvaluator expressionEvaluator;
/**
* @param valueProvider
* @param expressionParser
* @param evaluationContext
*/
public ParameterBindingContext(ValueProvider valueProvider, SpelExpressionParser expressionParser,
EvaluationContext evaluationContext) {
this(valueProvider, expressionParser, () -> evaluationContext);
}
/**
* @param valueProvider
* @param expressionParser
* @param evaluationContext a {@link Supplier} for {@link Lazy} context retrieval.
* @since 2.2.3
*/
public ParameterBindingContext(ValueProvider valueProvider, ExpressionParser expressionParser,
Supplier<EvaluationContext> evaluationContext) {
this(valueProvider, new EvaluationContextExpressionEvaluator(valueProvider, expressionParser, evaluationContext));
}