Bitsight TRACE Systematic Approach: CVE-2024-23897 as a Case Study

Bitsight TRACE Systematic Approach- CVE-2024-23897 as a Case Study hero
Written by Eduardo Silva
Senior Vulnerability Researcher

Introduction

This article provides details on how Bitsight TRACE addressed CVE-2024-23897, an arbitrary file read vulnerability that affects Jenkins, a well-known open-source automation server. It includes technical details, common pitfalls, and decisions made since the CVE disclosure until now.

The investigation of CVE-2024-23897 is an example of how we can obtain the target instance version but not solely rely on it to classify an instance as vulnerable. First, we go deep to understand the vulnerability. If there are mitigations that, just by looking at the version, we don't know if they are applied, we perform an active test to understand if they are effectively vulnerable to perform a high-confidence scan.

This detection has already been deployed in our Internet scanner, Groma, for a couple of months, so our customers are already seeing the results.

Research phase

To begin with, we try to understand what technology is affected by the present CVE and its attack surface in the public network. In this case, Jenkins, a widely known continuous integration/continuous delivery and deployment (CI/CD) automation software, is the affected software. Regarding its internet presence, as seen in the image below, Bitsight Groma Explorer hits around 58 thousand instances, with prevalence in the United States and China.

Publicly Accessible Jenkins instances in Groma Explorer
Figure 1. Publicly Accessible Jenkins instances in Groma Explorer

Jenkin’s internet exposure is notorious, and since the exploit(s) for CVE-2024-23897 are publicly accessible, any vulnerable exposed instance is at risk of exploitation. According to the advisory, it could allow unauthenticated attackers to read arbitrary files (e.g., secrets, sensitive data, or proprietary code) on the Jenkins controller file system and cause a significant impact.

Based on these facts, the following sections describe how we researched how to detect this vulnerability in depth.

Technical details

The CVE-2024-23897 vulnerability, which can be exploited via HTTP, WebSocket, and Secure Shell (SSH), requires a comprehensive understanding. This report will focus on the HTTP path and conduct a detailed root-cause analysis to understand the flaw, how it can be explored, and how to detect it.

Background

Jenkins includes a built-in Command-Line Interface (CLI) that allows users to interact with the system from script or shell environments. It uses the args4j library to parse command arguments and options to the Jenkins controller during CLI command processing. A vulnerability exists within this library that enables unauthenticated users to read the first few lines of any file on the file system. Authenticated users, on the other hand, can access entire file content.

Jenkins-CLI offers users a command-line interface to execute custom commands found in the hudson/cli directory of the Jenkins Git repository. By accessing http://jenkins/cli?remoting=false (which triggers jenkins-cli.jar), as mentioned in the advisory writeup, the endpoint will throw a PlainCliEndpointResponse() exception that ends in the generateResponse function shown in Figure 2. This function handles the CLI connections and returns the response according to what is requested.

generateResponse() function
Figure 2.  generateResponse() function

As can be seen, the download and upload request handlers are connected by the UUID. From observing the code, the download request is added to a services Map and is used to check if it is already in the “cache” when an upload request is received. This means both requests need to work in pairs. The Vulnerability and exploit analysis section details how the exploit leverages this behavior to read arbitrary files.

Vulnerability and exploit analysis

The endpoint http://jenkins/cli?remoting=false relies on two POST requests: a downloader and an uploader. The downloader executes the commands and retrieves their output, while the uploader contains the commands and their associated arguments. Jenkins connects these two components using the UUID found in the Session header of both requests.

Exploit Flow Sequence Diagram
Figure 3. Exploit Flow Sequence Diagram

As stated in TrendMicro analysis, when either of these requests is received, the following methods will be called in order:

  1. The CliCrumbExecution method will validate the endpoint.
  2. The FullDuplexHttpService method will handle the request and response (note that PlainCLIProtocol is used to make the request).
  3. Finally, the CmdLineParser method, which utilizes the vulnerable args4j library, will parse arguments from the CLI input.

The parseArgument function in args4j is invoked when the Jenkins Server receives a CLI command with arguments. It then calls the expandAtFiles (sink function) to process these arguments. expandAtFiles contains the vulnerable code.

Dangerous function
Figure 4. Dangerous function

The expandAtFiles function iterates through the command arguments, looking for an @ character. If found, it reaches the sink function and replaces the @ character and the file path (Figure 7) with the file’s contents. A malicious user can read arbitrary file contents in the target instance if an exploit is successful. Below is a representation of the output in case of success.

Local attack - Unauthenticated
Figure 5. Local attack - Unauthenticated

Patch/workaround

Jenkins maintainers addressed this issue by releasing a patch for versions 2.442 and LTS 2.426.3, which removed the vulnerable expandAtfiles command parser feature.

In addition, the maintainers mentioned a temporary workaround in the advisory in case updating Jenkins was not an option. The workaround involves disabling access to the CLI, which aims to prevent exploitation by breaking the attack vector (interaction with jenkins-cli.jar) leveraged by the exploit.

The impact of mitigations on scanning

At this stage, we have the following knowledge: vulnerability details, how the exploit works at a high level, and how the patch or workaround fixes the issue. With this information in hand, other questions arise regarding scanning:

  • How does the patch/workaround affect our scan confidence?
  • A version check is enough? Do we need to test it actively?
  • In our context, is the exploit intrusive?

The research shows that the temporary workaround affects how vulnerable Jenkins instances are scanned. As mentioned above, the suggested workaround disables the CLI manually, making a simple version check unreliable for confidently determining vulnerability status.

We must take an active role in probing the target to confirm if it's vulnerable, requiring us to explore the attack vector without being intrusive. In our context, intrusiveness is the ability to execute arbitrary code in target instances or access sensitive data. Our scan must avoid intrusive behavior like reading arbitrary files in a target instance. To overcome this issue, we needed to investigate Jenkins’ internals and determine how the exploit worked.

Tailored non-intrusive payload: The ideal solution

Our strategy involves understanding how Jenkins handles the exploit payload and adapting its behavior to our purposes.

1. Understand the vulnerable code

As mentioned in the Vulnerability and exploit analysis section, expandAtFiles is the sink function for this vulnerability. When invoking a CLI command with arguments, Jenkins uses args4j’s parseArgument, which calls expandAtFiles.

Vulnerable code details
Figure 6. Vulnerable code details

The function checks if the argument starts with the @ character. If so, it reads the file in the path after the @ and expands a new argument for each line, as mentioned in the advisory writeup. Below is an example using the /etc/passwd file.

Argument Expansion using /etc/passwd (source: Sonar writeup)
Figure 7. Argument Expansion using /etc/passwd (source: Sonar writeup)

2. Develop a non-intrusive/sensitive check using the public exploit primitives

The original publicly available exploit uses a payload that will read the contents of any valid file in a target Jenkins instance, which here at Bitsight, is considered too intrusive.

       b'\x00\x00\x00\x06\x00\x00\x04help\x00\x00\x00\r\x00\x00\x0b@etc/passwd\x00\x00\x00\x05\x02\x00\x03GBK\x00\x00\x00\x07\x01\x00\x05zh_CN\x00\x00\x00\x00\x03'
    

Figure 8. Intrusive payload targeting /etc/passwd file

During our vulnerability research phase we found an alternative path, where the goal is to set the condition if (!file.exists()) to True and trigger the CmdLineException in the expandAtFiles function shown in Figure 9, sending a default error message and avoiding reading any system files.

ExpandAtFiles CmdLineException
Figure 9. ExpandAtFiles CmdLineException

To achieve the above scenario, in Figure 10, we use an empty file path, which will always trigger the CmdLineException. This allows us to safely assess whether or not the workaround is in place and determine vulnerability status. Refer to the following Jenkins Binary Protocol Format subsection to understand why the payload has the following format.

        
b'\x00\x00\x00\x06\x00\x00\x04help\x00\x00\x00\x03\x00\x00\x01@'
        

Figure 10. Payload with an empty file path

Jenkins binary protocol format

Let's pay attention to Jenkins’s response from the Vulnerability and exploit analysis section, along with the exploit and our tailored payload. They contain binary characters within the interaction to jenkins-cli.jar. This happens because Jenkins uses a specific binary format based on sequential frames. The following is a breakdown of our tailored payload’s binary format as an example:

            
Payload -> \x00\x00\x00\x06\x00\x00\x04help\x00\x00\x00\x03\x00\x00\x01@
            
\x00\x00\x00\x06 - Length Field (4 bytes): This represents the length of the argument, excluding the length field itself and the opcode.
            
\x00 - Opcode Field (1 byte): This represents the message's operation code (opcode).
\x00\x04 - Data field length (2 bytes) - the length of the argument. In our case, the argument is “help” which has 4 (four) bytes.
"help" - Data field content (4 bytes) - executes the Jenkins-CLi help command.
            
\x00\x00\x00\x03 - Length Field (4 bytes): This represents the length of the 2nd argument, excluding the length field itself and the opcode.
\x00 - Opcode Field (1 byte): This represents the message's operation code (opcode).
            
\x00\x01 - Data field length (2 bytes) - the length of the argument. In our case, the argument is “@” which has 1 (one) byte.
            
@ - Data field content (1 byte) - Special char used in an argument to test the presence of the vulnerability (expandFiles feature).
            
            

3. Analyzing the response

Using our tailored payload, we expect to trigger the CmdLineException exception before reaching the sink code, returning the response seen in the image below.

Tailored payload against Jenkins 2.441
Figure 11. Tailored payload against Jenkins 2.441

Below is a breakdown of the response.

                
RESPONSE: b'\x00\x00\x00\x00\x01\x08\n\x00\x00\x00\x16\x08ERROR: No such file: \n\x00\x00\x00\x1e\x08java -jar jenkins-cli.jar help\x00\x00\x00\n\x08 [COMMAND]\x00\x00\x00\x01\x08\n\x00\x00\x00N\x08Lists all the available commands or a detailed description of single command.\n\x00\x00\x00\x1f\x08 COMMAND : Name of the command\n\x00\x00\x00\x04\x04\x00\x00\x00\x02'
                
- ERROR: No such file: \n  - our input was an empty file path (@'')
                
- java -jar jenkins-cli.jar help - The jenkins-cli help output which confirms the command help was provided
                
- Lists all the available commands or a detailed description of single command.\n\x00\x00\x00\x1f\x08 COMMAND : Name of the command\n\x00\x00\x00\x04\x04\x00\x00\x00\x02' - The CLI general description and the output of java -jar jenkins-cli.jar help command
                
                

To confidently determine the vulnerability status, we check the status code as well as the response body’s vulnerable pattern. If the status code is 200 (OK) and the response body contains “ERROR: No such file:” and “java -jar jenkins-cli.jar”, a given instance is considered vulnerable.

On the other hand, if we try our payload against a patched Jenkins version, the following response error is received:

Patched instance response
Figure 12. Patched instance response

It will respond with a message error stating that authentication is required, allowing us to differentiate vulnerable from non-vulnerable instances.

The response does not contain sensitive data as it only includes a static CmdLineException error provided by the Jenkins-CLI. With this approach, we can safely and unintrusively determine if the workaround is in place and confidently assess the target vulnerability status.

Data analysis

The last step is to review the scan data to ensure the accuracy of our detection capability. Below is a sample of vulnerable Jenkins responses with different locales.

Scan Data Sample with the version and response body
Figure 13. Scan Data Sample with the version and response body

The example below demonstrates the raw bytes of these responses.

                    
# Response 1 - English (US)
                    
\x00\x00\x00\x00\x01\x08\n\x00\x00\x00\x16\x08ERROR: No such file: \n\x00\x00\x00\x1e\x08java -jar jenkins-cli.jar help\x00\x00\x00\n\x08 [COMMAND]\x00\x00\x00\x01\x08\n\x00\x00\x00N\x08Lists all the available commands or a detailed description of single command.\n\x00\x00\x00\x1f\x08 COMMAND : Name of the command\n\x00\x00\x00\x04\x04\x00\x00\x00\x02
                    
# Response 2 - German
                    
\x00\x00\x00\x00\x01\x08\n\x00\x00\x00\x16\x08ERROR: No such file: \n\x00\x00\x00\x1e\x08java -jar jenkins-cli.jar help\x00\x00\x00\n\x08 [COMMAND]\x00\x00\x00\x01\x08\n\x00\x00\x00m\x08Zeigt eine Auflistung aller verf\xc3\xbcgbaren Befehle oder die detaillierte Beschreibung eines einzelnen Befehls.\n\x00\x00\x00\x1f\x08 COMMAND : Name of the command\n\x00\x00\x00\x04\x04\x00\x00\x00\x02
                    
# Response 3 - Spanish
                    
\x00\x00\x00\x00\x01\x08\n\x00\x00\x00\x16\x08ERROR: No such file: \n\x00\x00\x00\x1e\x08java -jar jenkins-cli.jar help\x00\x00\x00\n\x08 [COMMAND]\x00\x00\x00\x01\x08\n\x00\x00\x004\x08Muestra la lista de todos los comandos disponibles.\n\x00\x00\x00\x1f\x08 COMMAND : Name of the command\n\x00\x00\x00\x04\x04\x00\x00\x00\x02
                    
                    

Figure 14. Raw bytes for different locales

Comparing the responses, we can see the help menu description influences the bytes (bold text) placed in the response. According to the Jenkins Binary Protocol, different locales will have different bytes, as these have different lengths depending on the configured locale.

Therefore, relying solely on the specific bytes of the response will not work reliably across different locales, and we would fail to identify the condition we’re trying to detect (i.e. false negatives).

Instead, we can see that the strings “ERROR: No such file:” and “java -jar jenkins-cli.jar” are present in all of these responses, regardless of locale. Identifying these strings anywhere in the response gives us a reliable way of detecting the vulnerability.

Summary

This blog demonstrated the Bitsight TRACE team's workflow for investigating CVE-2024-23897, which affects Jenkins. To identify vulnerable instances with high confidence, we can’t just rely on doing a version check but also gently probing the target to confirm its vulnerability status and that no workarounds or mitigations are in place.

As security researchers, we emphasize the importance of safely scanning without impacting the target system's confidentiality, integrity, and availability while maintaining reliable scan data. The blog showcases this mindset in our systematic approach to incorporating vulnerability detection in the Bitsight platform.