#!/usr/bin/env python3 """Agent for API security testing using 42Crunch audit methodology.""" import json import argparse from datetime import datetime try: import yaml except ImportError: yaml = None OWASP_API_CHECKS = { "API1:2023": {"name": "Broken Object Level Authorization", "check": "bola"}, "API2:2023": {"name": "Broken Authentication", "check": "auth"}, "API3:2023": {"name": "Broken Object Property Level Authorization", "check": "bopla"}, "API4:2023": {"name": "Unrestricted Resource Consumption", "check": "resource"}, "API5:2023": {"name": "Broken Function Level Authorization", "check": "bfla"}, "API6:2023": {"name": "Unrestricted Access to Sensitive Business Flows", "check": "flow"}, "API7:2023": {"name": "Server-Side Request Forgery", "check": "ssrf"}, "API8:2023": {"name": "Security Misconfiguration", "check": "config"}, "API9:2023": {"name": "Improper Inventory Management", "check": "inventory"}, "API10:2023": {"name": "Unsafe Consumption of APIs", "check": "consumption"}, } def load_spec(spec_path): """Load OpenAPI spec.""" with open(spec_path) as f: if spec_path.endswith((".yaml", ".yml")): return yaml.safe_load(f) return json.load(f) def audit_spec_security(spec): """Perform static security audit of OpenAPI specification.""" findings = [] security_schemes = spec.get("components", {}).get("securitySchemes", {}) global_security = spec.get("security", []) if not security_schemes: findings.append({ "owasp": "API2:2023", "issue": "no_security_schemes", "severity": "CRITICAL", "score_deduction": 30, }) if not global_security: findings.append({ "owasp": "API8:2023", "issue": "no_global_security", "severity": "HIGH", "score_deduction": 20, }) paths = spec.get("paths", {}) for path, methods in paths.items(): for method, details in methods.items(): if method not in ("get", "post", "put", "patch", "delete"): continue if details.get("security") == []: findings.append({ "path": path, "method": method.upper(), "owasp": "API2:2023", "issue": "security_disabled", "severity": "CRITICAL", "score_deduction": 25, }) if method in ("post", "put", "patch"): body = details.get("requestBody", {}) content = body.get("content", {}) for media, media_def in content.items(): schema = media_def.get("schema", {}) if not schema: findings.append({ "path": path, "method": method.upper(), "owasp": "API3:2023", "issue": "no_input_schema", "severity": "HIGH", "score_deduction": 15, }) if schema.get("additionalProperties") is not False: findings.append({ "path": path, "method": method.upper(), "owasp": "API3:2023", "issue": "mass_assignment_risk", "severity": "MEDIUM", "score_deduction": 10, }) for param in details.get("parameters", []): p_schema = param.get("schema", {}) if p_schema.get("type") == "string" and not p_schema.get("maxLength"): findings.append({ "path": path, "method": method.upper(), "parameter": param.get("name"), "owasp": "API4:2023", "issue": "unbounded_string", "severity": "MEDIUM", "score_deduction": 5, }) responses = details.get("responses", {}) if "429" not in responses: findings.append({ "path": path, "method": method.upper(), "owasp": "API4:2023", "issue": "no_429_response", "severity": "MEDIUM", "score_deduction": 5, }) servers = spec.get("servers", []) for server in servers: url = server.get("url", "") if url.startswith("http://"): findings.append({ "server": url, "owasp": "API8:2023", "issue": "http_not_https", "severity": "HIGH", "score_deduction": 15, }) return findings def calculate_security_score(findings): """Calculate security score (0-100) based on findings.""" total_deduction = sum(f.get("score_deduction", 0) for f in findings) score = max(0, 100 - total_deduction) if score >= 80: grade = "A" elif score >= 60: grade = "B" elif score >= 40: grade = "C" else: grade = "F" return {"score": score, "grade": grade, "total_findings": len(findings)} def main(): parser = argparse.ArgumentParser(description="42Crunch-Style API Security Testing Agent") parser.add_argument("--spec", required=True, help="OpenAPI spec file") parser.add_argument("--output", default="api_security_test_report.json") args = parser.parse_args() spec = load_spec(args.spec) report = {"generated_at": datetime.utcnow().isoformat()} findings = audit_spec_security(spec) score = calculate_security_score(findings) report["security_score"] = score report["findings"] = findings report["owasp_coverage"] = {k: v["name"] for k, v in OWASP_API_CHECKS.items()} print(f"[+] Security Score: {score['score']}/100 (Grade: {score['grade']})") print(f"[+] Findings: {len(findings)}") with open(args.output, "w") as f: json.dump(report, f, indent=2, default=str) print(f"[+] Report saved to {args.output}") if __name__ == "__main__": main()