Анализ CVE-2024-43044 — от чтения файлов до удаленного выполнения кода в Jenkins через агентов
Введение
Jenkins — это широко используемый инструмент для автоматизации задач, таких как сборка, тестирование и развёртывание программного обеспечения. Он является важной частью процесса разработки во многих организациях. Если злоумышленник получает доступ к серверу Jenkins, это может привести к серьёзным последствиям, таким как кража учетных данных, манипуляции с кодом или даже срыв процессов развёртывания. Доступ к Jenkins предоставляет злоумышленнику возможность вмешаться в программный конвейер, что может привести к хаосу в процессе разработки и компрометации конфиденциальных данных.
В этой статье мы рассмотрим консультативное заключение по уязвимости CVE-2024-43044, которая представляет собой уязвимость, связанную с произвольным чтением файлов в Jenkins. Мы покажем, как можно использовать эту уязвимость для эскалации привилегий с целью достижения удалённого выполнения кода (RCE) на контроллере Jenkins при захвате агента Jenkins.
Обзор архитектуры Jenkins
Архитектура Jenkins основана на модели "контроллер-агенты", где контроллер Jenkins является основным узлом в установке Jenkins. Контроллер управляет агентами Jenkins и координирует их работу, включая планирование задач на агентах и мониторинг их состояния [3]. Взаимодействие между контроллером и агентами может происходить через Inbound-соединение (ранее известное как "JNLP") или SSH.
Реализация коммуникационного слоя, который обеспечивает межпроцессное взаимодействие, выполняется в библиотеке Remoting/Hudson [4]. В репозитории [5] также содержится полезная документация о том, как работает библиотека Remoting/Hudson. На изображении ниже показаны некоторые важные компоненты этой архитектуры.
Анализ уязвимости
Команда Jenkins выпустила консультативное заключение (SECURITY-3430 / CVE-2024-43044) [1], касающееся уязвимости, связанной с произвольным чтением файлов, которая позволяет агенту читать файлы с контроллера. Это происходит из-за функции, позволяющей контроллеру передавать JAR-файлы агентам. Согласно заключению, проблема заключается в том, что "реализация метода ClassLoaderProxy#fetchJar, вызываемого на контроллере, не ограничивает пути, по которым агенты могут запрашивать файлы из файловой системы контроллера".
Среди коммитов, связанных с уязвимостью, имеется тест [7], содержащий код для эксплуатации этой уязвимости.
Этот код в первую очередь получает доступ к классу hudson.remoting.RemoteClassLoader, который отвечает за загрузку файлов классов с удаленного узла через канал. Конкретно он обращается к объекту Proxy, находящемуся в поле proxy класса RemoteClassLoader. Обработчик для этого Proxy является экземпляром класса hudson.remoting.RemoteInvocationHandler.
Затем код использует этот обработчик для вызова метода fetchJar, что приводит к выполнению метода hudson.remoting.RemoteInvocationHandler.invoke. Этот процесс, в свою очередь, подготавливает удалённый вызов процедуры (RPC) к контроллеру. На стороне контроллера вызов достигает метода hudson.remoting.RemoteClassLoader$ClassLoaderProxy.fetchJar. Как показано ниже, метод fetchJar на контроллере не проверяет URL (который контролируется пользователем) и загружает ресурс без валидации.
Ниже представлено изображение, которое помогает визуализировать этот процесс.
Эта уязвимость позволяет обойти систему контроля доступа "Agent -> Controller" [13], которая включена по умолчанию с версии Jenkins 2.326 для управления доступом агентов к контроллеру и предотвращения захвата контроля над ним.
Патч вводит валидатор и некоторые системные свойства Java для контроля функциональности fetchJar.
Системные свойства Java включают:
- jenkins.security.s2m.JarURLValidatorImpl.REJECT_ALL — Отклоняет любую попытку получения JAR-файла.
- hudson.remoting.Channel.DISABLE_JAR_URL_VALIDATOR — Отключает проверку валидности URL.
Валидатор проверяет, указывает ли запрашиваемый URL на допустимый JAR-файл (JAR-файл из плагинов или ядра), как видно из приведённых ниже фрагментов кода:
jenkinsci/remoting/src/main/java/hudson/remoting/RemoteClassLoader.java
jenkinsci/jenkins/core/src/main/java/jenkins/security/s2m/JarURLValidatorImpl.java
Обратитесь к рекомендациям по устранению уязвимостей для получения дополнительной информации и методов обхода.
Получение удалённого выполнения кода (RCE).
Согласно рекомендациям, атака может быть инициирована «процессами агентов, кодом, работающим на агентах, или злоумышленниками, обладающими разрешением Agent/Connect» [1]. Мы реализовали наш эксплойт с учётом гибкости, поддерживая как агентов с обратной связью [15], так и SSH-подключения.
Использование секрета для агента с обратной связью
В этом режиме наш эксплойт действует как настраиваемый агент, инициирующий подключение к контроллеру. Чтобы воспользоваться им, вам потребуется следующая информация:
Один из способов получения этой информации — после получения доступа к узлу агента просмотреть все запущенные процессы. Вероятнее всего, вы обнаружите процесс Java, в котором эти данные указаны в командной строке, так как это является стандартным способом подключения агентов с обратной связью, предложенным Jenkins после их настройки.
Другой способ получения информации — утечка учетных данных. Следует отметить, что перед запуском нашего агента необходимо завершить работу уже запущенного агента или дождаться его отключения, так как один и тот же агент не может подключаться к серверу Jenkins более одного раза одновременно.
Пример запуска эксплойта этим способом:
java -jar exploit.jar mode_secret http://localhost:8080/ test b55d9b7fede47864572f4d0830a564a83ae78a4f297c1178b7f55601784f645c
Подключение к уже запущенному процессу Remoting
В этом режиме мы подключаемся к уже запущенному процессу Remoting с помощью API инструментария Java [16]. Мы подключаем Java-агент, который выполнит эксплуатацию уязвимости.
Это особенно полезно, когда подключение агент/контроллер происходит через SSH, так как в этом режиме не используется секрет агента. SSHLauncher, запущенный на контроллере, выполняет команду:
java -jar remoting.jar -workDir WORKDIR -jar-cache WORKDIR/remoting/jarCache
через сессию SSH и перенаправляет ввод и вывод для создания канала связи с агентом.
Например, при атаке с использованием вредоносного скрипта сборки, загруженного в кодовый репозиторий, управление сборкой которого осуществляется через Jenkins (запущенный на агенте), злоумышленник не сможет получить имя агента или его секрет, поскольку они не существуют в этом сценарии. В таком случае будет запущен процесс Remoting, подключенный к контроллеру через каналы SSH. Наш эксплойт найдет PID этого процесса и внедрит в него Java-код для выполнения следующих шагов.
Чтобы использовать этот режим, вам потребуется предоставить следующую информацию:
- URL целевого сервера Jenkins (опционально — эксплойт использует IP-адрес контроллера, подключенного через SSH, и создаёт URL Jenkins как http://IP:8080/. В этом случае на машине должны быть установлены утилиты pgrep, ps и netstat);
- Команда, которая будет выполнена.
Изображение ниже демонстрирует пример атаки, в которой pipeline выполняет команду mvn package внутри ненадёжного клонированного репозитория. Предположим, Jenkins настроен так, чтобы не выполнять сборки локально на контроллере через встроенный узел. Этого достаточно, чтобы скомпрометировать контроллер Jenkins:
Вредоносному репозиторию в этой настройке нужны только два файла:
Когда предоставляется секрет/имя агента, эксплойт использует его для установления соединения с сервером Jenkins, используя библиотеку Remoting. Мы используем класс Engine и ожидаем, пока соединение не будет установлено. Однако при подключении к уже существующему подключенному агенту эти шаги пропускаются.
Затем мы получаем экземпляр hudson.remoting.RemoteClassLoader из одного из запущенных потоков.
Мы используем его для создания объекта reader, который обрабатывает вызов метода fetchJar().
if (this.ccl == null) this.ccl = this.getRemoteClassLoader(); this.reader = new RemoteFileReader(this.ccl);
С помощью этого объекта мы можем использовать его для загрузки файлов с сервера. Не требуется обхода пути, вы можете запросить файлы, указав их полный путь, например:
this.reader.readAsString("file:///etc/passwd");
Подделка действительных файлов cookie пользователя
В рекомендации [1] подтверждается, что RCE возможен с этой уязвимостью, и указывается на вторую рекомендацию [2] по другой уязвимости для чтения файлов, выпущенной в январе 2024 года, в которой перечислены некоторые способы получения RCE с такого рода недостатками в Jenkins.
Один из подходов привлек наше внимание, так как он не требует изменения конфигурации, то есть работает против стандартной установки. Техника удаленного выполнения кода через куки-файл «Remember me» (Запомнить меня) заключается в подделке куки для учетной записи администратора, что позволяет злоумышленнику войти в приложение и получить доступ к консоли сценариев для выполнения команд.
Требования для применения этой техники:
- Включена функция «Remember me» (по умолчанию).
- Злоумышленники могут получить бинарные секреты.
- Злоумышленники имеют разрешение Overall/Read, чтобы иметь возможность читать содержимое файлов, превышающее несколько первых строк.
Уязвимость удовлетворяет этим требованиям, так как она может быть использована для чтения бинарных файлов и полного содержимого файла.
Для создания действительного куки требуется определённые данные. В нашей реализации мы использовали следующий подход:
- Чтение файла $JENKINS_HOME/users/users.xml для получения списка пользователей, имеющих учетные записи на сервере Jenkins;
- Чтение каждого файла $JENKINS_HOME/users/*.xml, чтобы извлечь информацию о пользователях, такую как: имя пользователя, user seed, временная метка и хэш пароля;
- Чтение необходимых файлов для подписи куки: Имея эти данные, мы воспроизводим алгоритм подписания куки в Jenkins [8], который можно описать следующим псевдокодом [6]:
Имея эти данные, мы воспроизводим алгоритм подписания куки в Jenkins [8], который можно описать следующим псевдокодом [6]:
Этот куки-файл может быть отправлен как “Cookie: remember-me=VALUE” в запросах к веб-приложению Jenkins.
После того как мы получили куки-файл "remember-me", мы можем запросить CSRF-токен (называемый Jenkins-Crumb) по адресу /crumbIssuer/api/json. Также стоит захватить куки-файл JSESSIONID, полученный в ответе, так как эти два файла связаны.
Затем мы отправляем POST-запрос на /scriptText, передавая значение Jenkins-Crumb в заголовке и значение JSESSIONID в куки, наряду с куки "remember-me". Код Groovy, который должен быть выполнен, передается через параметр POST с именем "script".
Наш код выполняет все эти шаги автоматически. Команда curl, представляющая этот конечный запрос, может выглядеть следующим образом:
curl -X POST "$JENKINS_URL/scriptText" --cookie "remember-me=$REMEMBER_ME_COOKIE; JSESSIONID...=$JSESSIONID" --header "Jenkins-Crumb: $CRUMB" --header "Content-Type: application/x-www-form-urlencoded" --data-urlencode "script=$SCRIPT"
Выполнение команды с помощью Groovy так же просто, как выполнение:
println "uname -a".execute().text
Это краткий обзор шагов, присутствующих в нашем эксплойте:
- Получить ссылку на hudson.remoting.RemoteClassLoader;
- Создать читатель файлов с его помощью;
- Прочитать необходимые файлы (всего 3) для подделки куки-файла для данного пользователя;
- Прочитать список пользователей Jenkins;
- Получить информацию (id, временная метка, seed и хэш) о каждом отдельном пользователе;
- Подделать куки-файл "remember-me" для пользователей до получения доступа к Сценарному движку Jenkins;
- Использовать Сценарный движок Jenkins для выполнения системных команд;
- Получить имена пользователей и хэши в формате, готовом для взлома с помощью John the Ripper [14].
Стоит отметить, что мы тестировали наш эксплойт только на Jenkins Docker, но считаем, что он должен работать и на других установках с небольшими или без изменений.
Код эксплойта можно найти по адресу: https://github.com/convisolabs/CVE-2024-43044-jenkins
В этом посте мы описали наш подход к эксплуатации уязвимости, связанной с CVE-2024-43044, для достижения удаленного выполнения кода (RCE) на уязвимом сервере Jenkins. Хотя существует множество различных окружений, использующих Jenkins, которые не охвачены в данном анализе, мы разработали эксплойт так, чтобы его было легко адаптировать под нужды других исследователей. Мы также считаем, что некоторые части могут быть повторно использованы в других эксплойтах для уязвимостей чтения файлов в Jenkins.
- https://www.jenkins.io/security/advisory/2024-08-07/#SECURITY-3430
- https://www.jenkins.io/security/advisory/2024-01-24/#SECURITY-3314
- https://www.jenkins.io/doc/book/using/using-agents/
- https://www.jenkins.io/projects/remoting/
- https://github.com/hudson/www/
- https://gist.github.com/mtiennnnn/551b7320c064db02aad815c6bdb91d9
- https://github.com/jenkinsci/jenkins/blob/203b6a6c851697e83aefc37d1812bfde06390bfe/test/src/test/java/jenkins/security/Security3430Test.java#L244
- https://github.com/jenkinsci/jenkins/blob/jenkins-2.470/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java#L174
- https://hub.docker.com/r/jenkins/jenkins
- https://www.jenkins.io/doc/book/managing/system-properties/
- https://naiwaen.debuggingsoft.com/blog/wp-content/uploads/2022/06/2022-05-28_201016.jpg
- https://github.com/advisories/GHSA-h856-ffvv-xvr4
- https://www.jenkins.io/doc/book/security/controller-isolation/#agent-controller-access-control
- https://www.openwall.com/john/
- https://github.com/jenkinsci/remoting/blob/master/docs/inbound-agent.md
- https://www.baeldung.com/java-instrumentation