
Автономное воспроизведение и анализ в Docker уязвимости CVE-2024-23897 — произвольное чтение файлов в Jenkins CLI через раскрытие аргументов с @-синтаксисом в args4j.
Полностью автономная локальная воспроизводимость CVE-2024-23897 — критической (базовая оценка CVSS 3.1 — 9.8) уязвимости произвольного чтения файлов в Jenkins CLI. Проект поднимает два Docker-стека, различающихся только минорной версией Jenkins, запускает один и тот же proof of concept против обоих и показывает, как уязвимость срабатывает на непропатченном контроллере и не даёт о себе знать на пропатченном.
Всё управляется одной Python-программой, poc.py. Обвязка лишь оркестрирует окружение (Docker Compose, официальный клиент jenkins-cli.jar и точный захват вывода). Сама уязвимость находится в Java-коде Jenkins и здесь заново не реализуется.
Полный письменный анализ, включая разбор патча и декомпозицию CVSS, — в report/report.pdf.
Jenkins CLI строит свой парсер аргументов с помощью библиотеки args4j. В args4j есть функция expandAtFiles, управляемая флагом atSyntax и включённая по умолчанию, которая раскрывает любой аргумент вида в содержимое этого файла перед запуском команды. Файл открывается с привилегиями процесса контроллера Jenkins. Поскольку все три CLI-транспорта (HTTP, WebSocket, SSH) сходятся в одном парсере, любой клиент, способный отправить любую CLI-команду, может читать произвольные файлы с контроллера. Исправление (коммит ) добавляет константу , по умолчанию равную , отключая раскрытие.
@/path/to/file554f0378ALLOW_AT_SYNTAXfalseЭто не классический path traversal: здесь нет ../ и нет базового каталога, из которого нужно выходить. Путь открывается напрямую. По критерию результата это произвольное чтение файла; по критерию механизма — подстановка аргументов.
cve-2024-23897-jenkins-poc/
README.md # this file
LICENSE
poc.py # Python reproduction harness (all subcommands)
Dockerfile.vuln # jenkins/jenkins:2.426.2-lts + matrix-auth
Dockerfile.fix # jenkins/jenkins:2.426.3-lts + matrix-auth
docker-compose.vuln.yml # jenkins-vuln + attacker-vuln
docker-compose.fix.yml # jenkins-fix + attacker-fix
init.groovy.d/
01-create-users.groovy # bootstraps admin + readuser via matrix-auth
evidence/
docker-versions.txt # host Docker + Compose versions
output-vulnerable.txt # captured during `poc.py exploit`
output-fixed.txt # captured during `poc.py verify-fix`
report/
report.pdf # full written analysis
report.tex # LaTeX source (self-contained, no external figures)
| Требование | Примечания |
|---|---|
| Docker Engine 24 или новее | точная использованная версия записана в evidence/docker-versions.txt |
| Docker Compose v2 | поставляется встроенным плагином docker compose |
| Python 3.8 или новее | только стандартная библиотека, pip install не нужен |
| Место на диске | около 1,5 ГБ для двух образов Jenkins, образа temurin и плагина matrix-auth |
JDK на хосте не требуется. Java работает внутри контейнера атакующего. В compose-файлах зафиксировано platform: linux/amd64, поэтому образы ведут себя одинаково на Apple Silicon; это операционное решение и не затрагивает уязвимость, которая не зависит от платформы.
Изнутри репозитория:
python3 poc.py up-vuln # build and start the vulnerable stack, fetch the CLI jar
python3 poc.py place-proof # write the harmless marker file inside the controller
python3 poc.py exploit # run the PoC, writes evidence/output-vulnerable.txt
python3 poc.py up-fix # tear down vuln, build and start the patched stack
python3 poc.py verify-fix # run the same PoC, writes evidence/output-fixed.txt
python3 poc.py teardown # stop and remove both stacks
Первый запуск занимает несколько минут (загрузка образов плюс установка плагина). Последующие запуски значительно быстрее.
poc.py exploit завершается успешно, когда в захваченном выводе появляется маркерная строка POC-PROOF-LINE (утечка произошла). poc.py verify-fix завершается успешно при противоположном условии: маркер должен отсутствовать. Обе подкоманды при неудаче завершаются с ненулевым кодом, так что два файла свидетельств и их статус выхода и есть результат теста.
Оба compose-файла запускают одни и те же два сервиса в одной Docker-сети: контроллер Jenkins (жертва) и небольшой контейнер атакующего eclipse-temurin:17-jre. poc.py выполняется на хосте и управляет Docker через subprocess, но сам вызов java -jar jenkins-cli.jar ... происходит внутри контейнера атакующего. У атакующего нет доступа к тому данных Jenkins; он обращается к контроллеру только по сети, как это делал бы удалённый атакующий.
init.groovy.d/01-create-users.groovy использует плагин matrix-auth для создания двух учётных записей с намеренно разными правами:
| Учётная запись | Права | Роль в PoC |
|---|---|---|
admin | Jenkins.ADMINISTER | существует только для выполнения требования «хотя бы один администратор», для атаки не используется |
readuser | только Jenkins.READ (Overall/Read) | аутентифицированный атакующий |
| anonymous | нет | неаутентифицированный атакующий |
Это воспроизводит точное разделение из официального бюллетеня: Overall/Read против anonymous, а не более широкое «любой вошедший пользователь против anonymous», которое дало бы значение по умолчанию ядра Jenkins. Таким образом, полная утечка файлов объясняется только самой CVE, а не административными полномочиями.
PoC прогоняет четыре сценария против каждого контроллера. В таблице ниже сведено A/B-сравнение; полные захваты вывода — в evidence/.
| Наблюдение | Уязвимая 2.426.2 | Пропатченная 2.426.3 |
|---|---|---|
обработка @-токена | раскрывается в содержимое файла | обрабатывается как литеральная строка |
readuser + connect-node | полное раскрытие файла (3 из 3 строк) | нет раскрытия |
anonymous + who-am-i / help | частичная утечка (первая строка) через ошибку парсера до проверки аутентификации | нет раскрытия |
маркер POC-PROOF-LINE в выводе | присутствует | отсутствует |
Единственная переменная, которая меняется между двумя колонками, — минорная версия Jenkins, а через неё — значение atSyntax по умолчанию после патча. Поэтому противоположные результаты объясняют изменение поведения исправлением парсера в коммите 554f0378.
Запуск Jenkins в контейнере не является мерой смягчения. Парсер читает файлы с привилегиями JVM Jenkins, и эти файлы находятся в той же файловой системе контейнера, где расположено хранилище учётных данных. Граница контейнера защищает хост от процесса Jenkins, а не процесс Jenkins от самого себя. Docker-окружение здесь — демонстрационная песочница, и не более.
Обновитесь до пропатченного релиза: 2.442 (weekly) либо 2.426.3 или 2.440.1 (LTS); все они выпущены 24 января 2024 года. Если немедленное обновление невозможно, оставьте системное свойство hudson.cli.CLICommand.allowAtSyntax неустановленным (это значение по умолчанию); тогда ALLOW_AT_SYNTAX остаётся равным false, и раскрытие отключено. Отключение отдельных CLI-транспортов — лишь частичная мера, поскольку все три сходятся в одном и том же парсере.
Вся работа выполняется в локальном Docker-окружении с безобидным трёхстрочным файлом-маркером (/tmp/poc-proof.txt), который обвязка создаёт сама. Ни один публичный экземпляр Jenkins не сканируется и не опрашивается, и никакой реальный секрет, такой как secrets/master.key или credentials.xml, никогда не читается.
554f0378 в jenkinsci/jenkins: https://github.com/jenkinsci/jenkins/commit/554f03782057c499c49bbb06575f0d28b5200edbParserProperties.withAtSyntax): https://github.com/kohsuke/args4j