Analysis of CVE-2024-43044 – from file reading to remote code execution in Jenkins via agents

Introduction
Jenkins is a widely used tool for automating tasks such as building, testing, and deploying software. It is an important part of the development process in many organizations. If an attacker gains access to the Jenkins server, this can lead to serious consequences such as credential theft, code manipulation, or even disruption of deployment processes. Access to Jenkins provides an attacker with the ability to tamper with the software pipeline, which can wreak havoc on the development process and compromise sensitive data.

In this article, we will review the vulnerability advisory CVE-2024-43044, which is an arbitrary file read vulnerability in Jenkins. We will show how this vulnerability can be used to escalate privileges to achieve remote code execution (RCE) on the Jenkins controller by hijacking the Jenkins agent.

Jenkins Architecture Overview
Jenkins architecture is based on a controller-agent model, where the Jenkins controller is the main node in the Jenkins installation. The controller manages Jenkins agents and coordinates their work, including scheduling tasks on the agents and monitoring their status [3]. Communication between the controller and agents can occur via an Inbound connection (formerly known as “JNLP”) or SSH.

The implementation of the communication layer, which provides interprocess communication, is performed in the Remoting/Hudson library [4]. In the repository [5] There is also useful documentation on how the Remoting/Hudson library works. The image below shows some of the important components of this architecture.

Vulnerability Analysis
The Jenkins team has issued an advisory (SECURITY-3430 / CVE-2024-43044) [1]an arbitrary file read vulnerability that allows an agent to read files from a controller. This is due to a feature that allows the controller to push JAR files to agents. According to the conclusion, the problem is that “the implementation of the ClassLoaderProxy#fetchJar method called on the controller does not restrict the paths through which agents can request files from the controller's file system.”

Among the commits associated with the vulnerability is a test [7]which contains code to exploit this vulnerability.

This code first accesses the hudson.remoting.RemoteClassLoader class, which is responsible for loading class files from the remote host via a pipe. Specifically, it accesses the Proxy object located in the proxy field of the RemoteClassLoader class. The handler for this Proxy is an instance of the hudson.remoting.RemoteInvocationHandler class.

The code then uses this handler to call the fetchJar method, which causes the hudson.remoting.RemoteInvocationHandler.invoke method to execute. This process, in turn, prepares a remote procedure call (RPC) to the controller. On the controller side, the call reaches the hudson.remoting.RemoteClassLoader$ClassLoaderProxy.fetchJar method. As shown below, the fetchJar method on the controller does not check the URL (which is controlled by the user) and loads the resource without validation.

Below is an image that helps visualize this process.

This vulnerability allows you to bypass the “Agent -> Controller” access control system [13]which is enabled by default since Jenkins 2.326 to control agent access to the controller and prevent it from being taken over.

Patch

The patch introduces a validator and some Java system properties to control fetchJar functionality.

Java system properties include:

  • jenkins.security.s2m.JarURLValidatorImpl.REJECT_ALL – Rejects any attempt to retrieve the JAR file.

  • hudson.remoting.Channel.DISABLE_JAR_URL_VALIDATOR – Disables URL validity checking.

The validator checks whether the requested URL points to a valid JAR file (JAR file from plugins or core), as seen in the code snippets below:

jenkinsci/remoting/src/main/java/hudson/remoting/RemoteClassLoader.java

jenkinsci/jenkins/core/src/main/java/jenkins/security/s2m/JarURLValidatorImpl.java

Refer to the vulnerability advisory for additional information and workarounds.

Receiving Remote Code Execution (RCE).

Prerequisites

According to the recommendations, the attack could be initiated by “agent processes, code running on agents, or attackers with Agent/Connect permission.” [1]. We implemented our exploit with flexibility in mind, supporting both agents with feedback [15]and SSH connections.

Using a secret for a closed-loop agent

In this mode, our exploit acts as a custom agent that initiates a connection to the controller. To use it, you will need the following information:

One way to obtain this information is to view all running processes after gaining access to the agent node. You will most likely find a Java process that has this information on the command line, as this is the standard way for Jenkins to connect closed-loop agents once they are configured.

Another way to obtain information is through credential leakage. It should be noted that before running our agent, we need to shut down the already running agent or wait for it to shut down, since the same agent cannot connect to the Jenkins server more than once at the same time.

An example of running an exploit this way:

java -jar exploit.jar mode_secret http://localhost:8080/ test b55d9b7fede47864572f4d0830a564a83ae78a4f297c1178b7f55601784f645c

Connecting to an already running Remoting process

In this mode, we connect to an already running Remoting process using the Java Toolkit API [16]. We connect a Java agent that will exploit the vulnerability.

This is especially useful when the agent/controller connection is via SSH, since this mode does not use the agent secret. SSHLauncher running on the controller executes the command:

java -jar remoting.jar -workDir WORKDIR -jar-cache WORKDIR/remoting/jarCache

through an SSH session and redirects input and output to create a communication channel with the agent.

For example, in an attack using a malicious build script uploaded to a code repository whose build is managed through Jenkins (running on an agent), the attacker would not be able to obtain the agent's name or secret because they do not exist in that script. In this case, the Remoting process will be launched, connected to the controller via SSH channels. Our exploit will find the PID of this process and inject Java code into it to perform the following steps.

To use this mode, you will need to provide the following information:

  • URL of the target Jenkins server (optional – the exploit uses the IP address of the controller connected via SSH and creates the Jenkins URL as http://IP:8080/. In this case, the pgrep, ps and netstat utilities must be installed on the machine);

  • The command to be executed.

The image below shows an example of an attack in which the pipeline executes the mvn package command inside an untrusted cloned repository. Let's say Jenkins is configured to not run builds locally on the controller via the built-in node. This is enough to compromise the Jenkins controller:

The malicious repository in this setup only needs two files:

Reading arbitrary files

When the secret/agent name is provided, the exploit uses it to establish a connection to the Jenkins server using the Remoting library. We use the Engine class and wait until the connection is established. However, when connecting to an existing connected agent, these steps are skipped.

We then get an instance of hudson.remoting.RemoteClassLoader from one of the running threads.

We use it to create a reader object that handles the fetchJar() method call.

if (this.ccl == null) this.ccl = this.getRemoteClassLoader();
 
this.reader = new RemoteFileReader(this.ccl);

With this object we can use it to download files from the server. No path traversal is required, you can request files by specifying their full path, for example:

this.reader.readAsString("file:///etc/passwd");

Tampering with valid user cookies

In recommendation [1] confirms that RCE is possible with this vulnerability and points to the second recommendation [2] according to another file reading vulnerability released in January 2024, which lists some ways to obtain RCE with this kind of flaws in Jenkins.

One approach caught our attention because it does not require configuration changes, meaning it works against the default setup. The “Remember me” cookie remote code execution technique involves forging a cookie for the administrator account, allowing an attacker to log into the application and gain access to the scripting console to execute commands.

Requirements for using this technique:

  • Remember me feature is enabled (default).

  • Attackers can obtain binary secrets.

  • Attackers have the Overall/Read permission to be able to read the contents of files beyond the first few lines.

The vulnerability satisfies these requirements as it can be exploited to read binary files and the full contents of a file.

To create a valid cookie, certain data is required. In our implementation we used the following approach:

  1. Reading the $JENKINS_HOME/users/users.xml file to get a list of users who have accounts on the Jenkins server;

  2. Reading each $JENKINS_HOME/users/*.xml file to extract user information such as: username, user seed, timestamp and password hash;

  3. Reading the necessary files for signing cookies: With this data, we reproduce the cookie signing algorithm in Jenkins [8]which can be described by the following pseudocode [6]:

    • $JENKINS_HOME/secret.key

    • $JENKINS_HOME/secrets/master.key

    • $JENKINS_HOME/secrets/org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices.mac

Given this data, we reproduce the cookie signing algorithm in Jenkins [8]which can be described by the following pseudocode [6]:

This cookie may be sent as “Cookie: remember-me=VALUE” in requests to the Jenkins web application.

Executing Code

Once we have received the “remember-me” cookie, we can request a CSRF token (called Jenkins-Crumb) at /crumbIssuer/api/json. It's also worth capturing the JSESSIONID cookie received in the response, since the two files are related.

We then send a POST request to /scriptText, passing the Jenkins-Crumb value in the header and the JSESSIONID value in the cookie, along with the “remember-me” cookie. The Groovy code to be executed is passed through a POST parameter named “script”.

Our code performs all these steps automatically. The curl command representing this final request might look like this:

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"

Executing a command with Groovy is as simple as running:

println "uname -a".execute().text

Results of the exploit

This is a short overview of the steps present in our exploit:

  1. Get a reference to hudson.remoting.RemoteClassLoader;

  2. Create a file reader using it;

  3. Read the necessary files (3 in total) to fake a cookie for a given user;

  4. Read the list of Jenkins users;

  5. Get information (id, timestamp, seed and hash) about each individual user;

  6. Forge the “remember-me” cookie for users before gaining access to the Jenkins Scripting Engine;

  7. Use the Jenkins Scripting Engine to execute system commands;

  8. Get usernames and hashes in a format ready for hacking with John the Ripper [14].

It's worth noting that we only tested our exploit on Jenkins Docker, but we think it should work on other installations with little or no modifications.

The exploit code can be found at: https://github.com/convisolabs/CVE-2024-43044-jenkins

Conclusion

In this post, we described our approach to exploit the CVE-2024-43044 vulnerability to achieve remote code execution (RCE) on a vulnerable Jenkins server. Although there are many different environments using Jenkins that are not covered in this analysis, we designed the exploit to be easily adaptable to the needs of other researchers. We also believe that some parts could be reused in other exploits for Jenkins file read vulnerabilities.

Links

  1. https://www.jenkins.io/security/advisory/2024-08-07/#SECURITY-3430

  2. https://www.jenkins.io/security/advisory/2024-01-24/#SECURITY-3314

  3. https://www.jenkins.io/doc/book/using/using-agents/

  4. https://www.jenkins.io/projects/remoting/

  5. https://github.com/hudson/www/

  6. https://gist.github.com/mtiennnnn/551b7320c064db02aad815c6bdb91d9

  7. https://github.com/jenkinsci/jenkins/blob/203b6a6c851697e83aefc37d1812bfde06390bfe/test/src/test/java/jenkins/security/Security3430Test.java#L244

  8. https://github.com/jenkinsci/jenkins/blob/jenkins-2.470/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java#L174

  9. https://hub.docker.com/r/jenkins/jenkins

  10. https://www.jenkins.io/doc/book/managing/system-properties/

  11. https://naiwaen.debuggingsoft.com/blog/wp-content/uploads/2022/06/2022-05-28_201016.jpg

  12. https://github.com/advisories/GHSA-h856-ffvv-xvr4

  13. https://www.jenkins.io/doc/book/security/controller-isolation/#agent-controller-access-control

  14. https://www.openwall.com/john/

  15. https://github.com/jenkinsci/remoting/blob/master/docs/inbound-agent.md

  16. https://www.baeldung.com/java-instrumentation

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *