Aqua Nautilus researchers have discovered a chain of critical vulnerabilities, dubbed CorePlague, in the widely used Jenkins Server and Update Center (CVE-2023-27898, CVE-2023-27905). Exploiting these vulnerabilities could allow an unauthenticated attacker to execute arbitrary code on the victim’s Jenkins server, potentially leading to a complete compromise of the Jenkins server.
Furthermore, these vulnerabilities could be exploited even if the Jenkins server is not directly reachable by attackers and could also impact self-hosted Jenkins servers.
The Research in a Nutshell
Jenkins is an open-source automation server that supports the software development lifecycle (SDLC) and can be customized using plugins to extend its functionality.
During our research, we found vulnerabilities in how Jenkins processes available plugins, which can result in security issues ranging from cross-site scripting (XSS) to remote code execution (RCE).
The vulnerabilities are achieved through a stored XSS exploitable by a Jenkins plugin with a malicious core version, which attackers upload to the Jenkins Update Center.
Once the victim opens the Available Plugin Manager on their Jenkins Server, the XSS is triggered, allowing attackers to run arbitrary code on the Jenkins Server utilizing the Script Console API.
Importantly, the vulnerability is triggered without any additional action from the victim, and the exploitation does not require the manipulated plugin to be installed.
Attackers could exploit these vulnerabilities to compromise Jenkins Servers, even though they are not directly reachable because the public Jenkins Update Center – which is used by default on Jenkins Servers to obtain available plugin lists – could be injected by attackers.
We disclosed these vulnerabilities CVE-2023-27898 and CVE-2023-27905 to the Jenkins team in January 2023. The Jenkins team acknowledged the vulnerabilities and issued a patch for the Jenkins server. They also released a patch for the Jenkins Update Center. We appreciate their collaboration and professionalism during the disclosure process.
Frequently Asked Questions
Do I need to patch my Jenkins Servers?
The Jenkins team released a patch for the public Jenkins Update Center on February 15, which mitigates some risks associated with this vulnerability since it is the first component involved. This is particularly significant since most Jenkins users rely on the public Jenkins Update Center for the list of the available Jenkins plugins. This means that if you rely on the public Jenkins Update Center to get plugins, your Jenkins server will be vulnerable but probably not exploitable. Thus, there is no immediate need to update it.
However, if you use self-hosted or customized Update Centers, you are at risk.
Which Jenkins Server versions are vulnerable?
Jenkins servers running versions 2.270 through 2.393 (both inclusive), LTS 2.277.1 through 2.375.3 (both inclusive) are vulnerable (this blog will detail a limitation of exploitation for the latest versions)
Which Jenkins Update Center versions are vulnerable?
Jenkins Update Centers with versions below 3.15 are vulnerable.
Detailed information is available in the Jenkins Security Advisory 2023-03-08.
Some Basic Jenkins Definitions
Artifact registry A binary repository manager used for storing and managing software artifacts. The Jenkins project uses its own Artifactory binary repository, to distribute core, library, and plugin releases.
Jenkins Update Center A component of the Jenkins automation server that provides access to a wide range of plugins and updates for the Jenkins platform. It allows Jenkins administrators to easily discover, download, and install plugins that extend the functionality of their Jenkins server. This is also known as Jenkins community update sites.
Jenkins Server Jenkins is a widely used open-source automation server that enables continuous integration and delivery of software projects.
Jenkins plugins are software components that extend the functionality of the Jenkins Server, enabling users to enhance and customize their experience, and the automation process. These Jenkins plugins offer features such as source code management, build triggers, notification mechanisms, and integrations with external tools and systems. They can be installed, updated, and managed via the Jenkins Update Center.
The vulnerabilities we describe in our blog are related to Jenkins plugins.
Improper Sanitation: The Jenkins Update Center
Now that we have a clear understanding of what a Jenkins plugin is, let’s understand the process of creating a plugin so it will be available for everyone.
In short, the initial uploading process is to write a plugin in Java and submit a pull request to the Jenkins team. After review and approval, a GitHub repository will be created at https://github.com/jenkinsci/your_plugin_name, and you will be granted write access to it.
Furthermore, the Jenkins team grants you write access to the artifact registry, allowing you to upload the compiled plugin.
Once the initial plugin release has been approved by the Jenkins team (which is usually just a procedural step), developers can release subsequent versions of the plugin without any involvement from the team.
As a result, an attacker could potentially upload a harmless plugin and then change it to a manipulated one without requiring any additional approval.
To publish a new version of a plugin to the artifact registry, developers must run the publish command, which builds the project and stores it in a designated folder. As illustrated in the image below (with ascii-magician as the plugin name in this example):
Once the build is complete, the publish command uploads the resulting artifacts (such as the plugin_name.hpi
, plugin_name.jar
, and plugin_name-ver.pom
files) to the artifact registry.
Before we proceed let’s examine the .hpi
file that was uploaded to the artifact registry.
The .hpi
file is actually a compressed zip file that contains various files and directories. Let’s take a closer look at the META_INF/ MANIFEST.MF
file:
The META-INF/MANIFEST.MF file contains key-value pairs that provide metadata about the plugin being published. (The Jenkins-Version is highlighted for future references.)
So far, we’ve provided an overview of the uploading process from the publisher’s perspective. Now after the artifacts are in the artifact registry, let’s shift our focus to the Update Center that provides the plugins to the Jenkins Server.
The Update Center processes all the plugins in the artifact registry and generates the update-center.json file that contains metadata about all available plugins. This file can be found here: https://updates.jenkins.io/update-center.json
To illustrate, let’s take a brief look at an example of the metadata associated with a plugin:
In the image above, you can see some of the metadata of the pam-auth plugin. This metadata includes details such as build date, labels, URL for download and the requiredCore which is the minimum Jenkins-Version that is required for the plugin.
How is the metadata of the plugin added to this JSON file?
The Update Center fetches the plugins from the artifact registry and for each plugin creates a JSON of metadata about the plugin. Here is how a part of the JSON is constructed:
From the image above, we can observe the JSONField of requiredCore is assigned from the HPI through the function hpi.getRequiredJenkinsVersion(). Let’s now examine this function:
In this image, we can see there is a use of the function getManifestAttributes().getValue(“Jenkins-Version“) which retrieves the value of the Jenkins-Version key from the Manifest file within the plugin’s HPI file (in the example of the manifest above it will return 2.361.3).
The code verifies that the obtained value is not null and then assigns it to the requiredCore JSONField that was mentioned earlier. Therefore, there is no sanitization of the requiredCore field at the Update Center, allowing an attacker to input any string in this field.
In summary, the attacker can upload an HPI file containing a Manifest file with a Jenkins-Version field that can have any value without restrictions. This value will be inserted into the requiredCore field of the JSON located at https://updates.jenkins.io/update-center.json
CVE-2023-27905
The Update Center is also a website where users can browse to view the available plugins and retrieve information about them. This can be done by accessing this URL https://updates.jenkins.io/download/plugins/plugin_name/
Now, let’s look at the following page:
On this page, we can view details about the script-security plugin, including the hashes and required Jenkins Server version for each version of the plugin.
In previous sections, we showed how an attacker can manipulate the Jenkins-Version attribute of a plugin by modifying its Manifest file, and that the Update Center does not perform any input sanitization on this field. Now, what if an attacker attempted to insert an XSS payload into the Jenkins-Version attribute, such as:
<image src =q onerror=prompt(8)>
Let’s examine the code that generates this page:
As we can observe from the code, the value of requiredJenkinsVersion is directly appended to the HTML without any escaping.
Therefore, an attacker can potentially upload a plugin with the XSS payload shown earlier in the Jenkins-Version attribute.
Then, when a user accesses the URL of the plugin https://updates.jenkins.io/download/plugins/attacker_plugin, the payload will be executed in their browser:
This vulnerability has been identified as CVE-2023-27905, and the Jenkins team has patched it on their hosted website.
CVE-2023-27898
In our previous demonstration of the vulnerability, we identified an XSS on the user in the site updates.jenkins.io, which could be exploited for various malicious purposes. As our objective was to identify potential threats to internal networks within the context of the Jenkins Server, we continued our search.
Given that the attacker can control the requiredCore value of a plugin uploaded to the Jenkins update site, it is critical to examine how the Jenkins Server will handle this value.
How are the Jenkins Server and the Jenkins Update Center connected?
The Jenkins Server interacts with the Jenkins Update Center to obtain information about available plugins and to download and install updates and plugins for the Jenkins Server.
The Jenkins Server obtains the available plugin list from the URL https://updates.jenkins.io/update-center.json and stores a copy of it.
This list is updated automatically, or manually by triggering an update through the Check now button on the Available Plugins page in the Jenkins Server.
Where is the requiredCore value, controlled by the attacker, used?
Whenever a Jenkins admin accesses the Available Plugin Manager page (http://<Jenkins_URL>/manage/pluginManager/available) to search for a plugin,
the available plugin list that the Jenkins Server retrieves from the Update Center is processed by the Jenkins Server.
For each available plugin within the list, the Jenkins Server checks if the plugin was built for a newer version of Jenkins Server by comparing the plugin’s requiredCore version of the plugin to the current Jenkins Server version.
If built for a newer version, the Jenkins Server will return the following message: “Warning: This plugin is built for Jenkins {attacker control requiredCore} or newer. Jenkins will refuse to load this plugin if installed.”
This warning will eventually be rendered by the available.hbs file:
The issue here is that the previous warning message will be rendered using the triple-stash {{{ Handlebars notation. Which means, any values inside these triple-stash handlebars notation will not be escaped.
In this case, attackers can manipulate the requiredCore version in the warning message, allowing malicious scripts contained within the message to execute without being properly escaped.
In summary, the attacker has uploaded a Jenkins plugin with a malicious core version e.g., 9.9.9<image src =q onerror=prompt(8)> to the Jenkins Update Center.
Now because the Jenkins plugin with the malicious core version is bigger than the victim Jenkins server version, a warning message will be displayed. (Assume the victim has Jenkins version 2.270, which means the malicious plugin version starts with 9.X.payload considers for a larger version of Jenkins server according to the isForNewerHusdon function.)
As a result, whenever a Jenkins admin accesses the Available Plugin Manager page (http://<Jenkins_Server>/manage/pluginManager/available) to install or search for a plugin, the malicious core version will render, displaying a warning message and triggering the XSS.
It’s important to note that the XSS vulnerability will be triggered without installing any malicious plugins; simply the presence of a malicious plugin in the feed is enough to activate the vulnerability.
The Tiering Mechanism
From the previous section, it seems that an attacker can easily compromise every Jenkins server in the world. However, this is not entirely accurate.
The Jenkins team implemented a site tiering mechanism to show only plugins that are compatible with the current Jenkins Server, meaning the requiredCore version of the plugin is older than the Jenkins Server. Since the requiredCore version is older, the warning message shown earlier will not appear, and the requiredCore value will not be processed as HTML, making it safe from the XSS.
In order to determine if there are any vulnerable versions, it is essential for us to possess a basic understanding of the tiering mechanism.
When the Jenkins Server wants to update the list of available plugins, it sends a request to the JSON in the update center along with a version parameter that matches the Jenkins server version, for example: https://updates.jenkins.io/update-center.json?version=2.332.3
The Update Center then employs this version parameter to redirect the request to a distinct URL, which corresponds to a different JSON. For instance, the previously mentioned URL would redirect to https://updates.jenkins.io/dynamic-stable-2.332.3/update-center.json
The JSON of this particular version will contain all the plugins that require a version of requiredCore that is either 2.332.3 or earlier.
In the interest of simplicity, we will not delve into the complete tiering mechanism. Nonetheless, it’s important to note that not all versions have an individual page, and the earliest version to possess a dedicated page is one that has been released for less than 400 days (we have simplified this explanation).
Therefore, if a Jenkins Server version that is older than 400 days makes the request, it will be redirected to the oldest version that has an individual page.
For instance, in the present time, the oldest version that has a dedicated page is 2.319.2. Consequently, if a Jenkins Server with an earlier version, such as 2.319, submits the following request: https://updates.jenkins.io/update-center.json?version=2.319.1
The request will be redirected to https://updates.jenkins.io/dynamic-stable-2.319.2/update-center.json
We understand that the JSON located at https://updates.jenkins.io/dynamic-stable-2.319.2/update-center.json contains all the plugins that require a version of requiredCore that is 2.319.2 or earlier. Nonetheless, the Jenkins Server that initiated the request has an older version of 2.319.1. As a result, the Jenkins Server will receive plugins that require a newer version of requiredCore than its own and, therefore, display the warning message as shown earlier.
Having established the existence of versions that display the warning message, the question arises: how can we exploit it?
To achieve this, we need to select the oldest version with a page, which is currently 2.319.2 (although it will eventually change due to the 400-day limitation). We then modify it to a lower version, say 2.319.1.1, and attach our XSS payload. For example:
2.319.1.1 <image src =q onerror=prompt(8)>
Now when an Administrator opens the Available Plugin Manager page:
The XSS attack succeeds, thereby enabling the attacker to execute arbitrary JavaScript on the victim’s browser within the context of the Jenkins Server website.
This makes all Jenkins versions earlier than 2.319.2 exploitable at this time of writing.
From XSS to RCE
Having established that the XSS attack is successful, we decided to test if we could escalate it to RCE on the Jenkins Server. At present, our custom JavaScript is running on the Jenkins admin’s browser, giving us the same privileges as the admin. This means we can perform any task that the admin is capable of, including running Groovy code on the Jenkins Server through the Script Console API.
In the following steps, we will demonstrate how the attacker, by exploiting the vulnerability, can obtain a reverse shell to their machine via the Groovy Script Console.
The initial payload that the attacker may insert into the Jenkins-Version attribute in the Manifest file is as follows:
In short, the payload instructs the browser to retrieve a JavaScript file from the attacker’s server https://attackers_machine/evil.js and execute its contents within the scope of the Jenkins Server.
Here’s an example of the JavaScript file (evil.js) that will be served to the victim by the attacker’s server:
In the JavaScript code, we specified the attacker’s IP address and port for the reverse shell payload in Groovy.
Next, we combined all the necessary data and issued a POST request to the /script endpoint to execute the Groovy code on the private Jenkins Server.
After the malicious Groovy code is executed by the Jenkins Server:
The attacker’s C2 receives the reverse shell from the victim’s Jenkins Server!
Now the attacker is granted full control over the entire Jenkins infrastructure. This level of access enables the attacker to manipulate builds, inject harmful code into artifacts and execute additional malicious activities.
Bringing the malicious plugin to the front
By default, Jenkins Server only displays the most frequently downloaded plugins in the main feed.
The attacker can use certain techniques to bring the malicious plugin to the main page of the available plugin feed or increase the likelihood that it will be rendered on the Available Plugin Manager page:
- Since the search function is based on any keywords found in the plugin description, an attacker can utilize this by uploading a plugin that contains all plugin names and popular keywords embedded in the description.
To make it even more difficult to detect, attackers can embed a lot of plugin names and popular terms in an HTML tag such as the <a> tag, for example <a href=”long_text”> .</a>
Since this tag is permitted, the plugin won’t appear suspicious. During our testing, we found that almost every search we conducted returned the malicious plugin. - It’s possible to boost the download count of a specific plugin and increase its popularity by submitting requests from fake instances.
Attack steps summary
Step 1 | An attacker creates an initial innocent plugin and gains upload permissions. |
Step 2 | The attacker modifies the HPI file with the XSS payload inside the Jenkins-Version attribute. |
Step 3 | The attacker uploads the modified HPI to the public Jenkins artifact registry. |
Step 4 | The Update Center fetches the plugin and stores the payload in the JSON of available plugins. |
Step 5 | The Jenkins Server fetches the new JSON from the Jenkins Update Center. |
Step 6 | An admin of a vulnerable Jenkins Server opens the Available Plugin Manager page. |
Step 7 | The XSS payload of the attacker is executed, executing malicious commands on the Jenkins Server with the permissions of the admin via the /script endpoint. |
Step 8 | The attacker compromises the Jenkins Server |
Disclosure timeline
16 Jan 2023 | Aqua Research team reported the vulnerabilities to Jenkins security team. |
16 Jan 2023 | Jenkins security team confirmed the vulnerabilities. |
15 Feb 2023 | Jenkins has released a fix for update-center2 and patched the public Jenkins Update Center. |
8 Mar 2023 | Jenkins has released a fix for Jenkins Server and announced Security Advisory CVE-2023-27898 and CVE-2023-27905. |
In Summary
Several important lessons can be drawn from our research.
Firstly, as opposed to Jenkins older than 400 days, which were immediately exploitable, we discovered that Jenkins Servers under 400 days old were vulnerable to these vulnerabilities but could not be exploited immediately. However, if these servers were not updated and remained out-of-date, they could be exploitable in the future. This underscores the importance of maintaining a regular update schedule for all components of the environment, which can help to prevent known and unknown vulnerabilities from being exploited.
Secondly, our research highlights the importance of defense-in-depth strategies for securing environments. It is critical to have multiple layers of protection in place to safeguard the environment and its components from vulnerabilities. By implementing different security measures, even if one component is compromised, the other layers of protection can mitigate the damage and prevent the attacker from escalating their attack.
Prevention with Aqua Platform
We recommend a defense-in-depth approach, using multiple controls at key points to prevent security incidents.
Scanning your SDLC environments with a tool like Aqua Trivy can help ensure that your version of Jenkins is not vulnerable to known vulnerabilities.
We recommend implementing further controls to protect your environments in case any vulnerabilities were exploited in your environment. For instance, you can scan your workloads for suspicious and malicious behavior in runtime with open-source tools such as Tracee. If you use the Aqua CNAPP we highly recommend using CNDR.
This solution is designed to empower security teams to detect and prevent cyberattacks at various stages of development using strong tools such as drift prevention, which prevents downloading and running malicious elements, and Cloud Native Detection and Response (CNDR), an eBPF-based tool designed to detect malicious behavior in runtime.