#!/usr/bin/env python3 # For authorized penetration testing and educational environments only. # Usage against targets without prior mutual consent is illegal. # It is the end user's responsibility to obey all applicable local, state and federal laws. """Agent for detecting excessive data exposure (OWASP API3) in API responses.""" import argparse import json import re from datetime import datetime, timezone try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False SENSITIVE_FIELDS = [ "password", "passwd", "secret", "token", "api_key", "apikey", "ssn", "social_security", "credit_card", "card_number", "cvv", "private_key", "secret_key", "access_key", "session_id", "internal_id", "salary", "bank_account", "routing_number", "date_of_birth", "dob", "national_id", "passport", ] PII_PATTERNS = { "email": r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', "phone": r'\+?1?\d{10,15}', "ssn": r'\d{3}-\d{2}-\d{4}', "credit_card": r'\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}', "ip_address": r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', } def analyze_response(response_json, endpoint=""): """Analyze API response for excessive data exposure.""" findings = [] def check_fields(obj, path=""): if isinstance(obj, dict): for key, value in obj.items(): current_path = f"{path}.{key}" if path else key key_lower = key.lower() for sf in SENSITIVE_FIELDS: if sf in key_lower: findings.append({ "field": current_path, "matched_pattern": sf, "value_type": type(value).__name__, "value_preview": str(value)[:20] + "..." if len(str(value)) > 20 else str(value), "severity": "HIGH", }) break check_fields(value, current_path) elif isinstance(obj, list): for i, item in enumerate(obj[:5]): check_fields(item, f"{path}[{i}]") check_fields(response_json) response_str = json.dumps(response_json) for pattern_name, pattern in PII_PATTERNS.items(): matches = re.findall(pattern, response_str) if matches: findings.append({ "type": "pii_exposure", "pattern": pattern_name, "match_count": len(matches), "samples": matches[:3], "severity": "HIGH", }) return findings def test_endpoint(url, headers=None): """Fetch API endpoint and analyze for data exposure.""" if not HAS_REQUESTS: return {"error": "requests library not available"} try: resp = requests.get(url, headers=headers, timeout=15, verify=False) data = resp.json() field_count = count_fields(data) findings = analyze_response(data, url) return { "endpoint": url, "status_code": resp.status_code, "total_fields": field_count, "sensitive_fields": len(findings), "findings": findings, } except (requests.RequestException, json.JSONDecodeError) as e: return {"endpoint": url, "error": str(e)[:200]} def count_fields(obj, count=0): """Count total fields in a JSON response.""" if isinstance(obj, dict): count += len(obj) for v in obj.values(): count = count_fields(v, count) elif isinstance(obj, list): for item in obj[:10]: count = count_fields(item, count) return count def compare_with_spec(response_json, spec_fields): """Compare response fields against expected OpenAPI spec fields.""" actual = set() def extract_keys(obj, prefix=""): if isinstance(obj, dict): for k in obj: path = f"{prefix}.{k}" if prefix else k actual.add(path) extract_keys(obj[k], path) extract_keys(response_json) extra = actual - set(spec_fields) return list(extra) def main(): parser = argparse.ArgumentParser( description="Detect excessive data exposure in API responses" ) parser.add_argument("--url", help="API endpoint to test") parser.add_argument("--json-file", help="JSON response file to analyze") parser.add_argument("--token", help="Bearer token for auth") parser.add_argument("--output", "-o", help="Output JSON report") args = parser.parse_args() print("[*] Excessive Data Exposure Detection Agent (OWASP API3)") report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []} if args.url: headers = {"Authorization": f"Bearer {args.token}"} if args.token else {} result = test_endpoint(args.url, headers) report["findings"].append(result) print(f"[*] {args.url}: {result.get('sensitive_fields', 0)} sensitive fields found") if args.json_file: with open(args.json_file, "r") as f: data = json.load(f) findings = analyze_response(data, args.json_file) report["findings"].extend(findings) print(f"[*] File analysis: {len(findings)} sensitive fields found") report["risk_level"] = "HIGH" if any( f.get("sensitive_fields", 0) > 0 or f.get("severity") == "HIGH" for f in 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()