Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| """ | |
| http_method_tampering_scanner.py — HTTP Method Tampering Scanner | |
| ================================================================= | |
| Tests all HTTP methods (TRACE, PUT, DELETE, PATCH, OPTIONS, CONNECT) on | |
| discovered endpoints. TRACE enables XST, PUT can write files. | |
| Also tests HTTP method override headers. | |
| """ | |
| from scanners.base_scanner import BaseScanner | |
| DANGEROUS_METHODS = { | |
| "TRACE": ("High", "Enables Cross-Site Tracing (XST) — reflects HTTP headers including cookies/auth tokens back to the client. Can be used to bypass HttpOnly cookie protection via JavaScript."), | |
| "PUT": ("Critical","HTTP PUT on a web server may allow direct file upload, enabling Remote Code Execution (e.g., uploading a webshell)."), | |
| "DELETE": ("High", "HTTP DELETE allowed. An attacker could delete files or resources if the web server maps it to the filesystem."), | |
| "CONNECT": ("Medium", "CONNECT method may allow the server to be used as an HTTP proxy for SSRF or network pivoting."), | |
| "PATCH": ("Medium", "HTTP PATCH allowed. May allow partial modification of server-side resources."), | |
| "PROPFIND":("Medium", "WebDAV PROPFIND allowed. May leak file listings and metadata."), | |
| "MOVE": ("High", "WebDAV MOVE allowed. An attacker can rename/move files on the server."), | |
| "COPY": ("Medium", "WebDAV COPY allowed. An attacker can copy files on the server."), | |
| "MKCOL": ("Medium", "WebDAV MKCOL allowed. An attacker can create directories on the server."), | |
| } | |
| # Curated list of HTTP method override header name variants used in real WAF bypass scenarios. | |
| # Only valid HTTP header name chars (printable ASCII, no control chars) are used. | |
| # Do NOT use waf_evade() here — it generates URL-encoded / control-char variants | |
| # that urllib rejects as invalid header names. | |
| OVERRIDE_HEADERS = [ | |
| # Standard override headers | |
| "X-HTTP-Method-Override", | |
| "X-HTTP-Method", | |
| "X-Method-Override", | |
| # Case variations (common WAF bypass) | |
| "x-http-method-override", | |
| "x-http-method", | |
| "x-method-override", | |
| "X-Http-Method-Override", | |
| "X-Http-Method", | |
| # Additional known override header aliases | |
| "X-Forwarded-Method", | |
| "X-Original-Method", | |
| "X-Rewrite-Method", | |
| "XHTTP-Method", | |
| ] | |
| class HttpMethodTamperingScanner(BaseScanner): | |
| SCANNER_NAME = "HTTP Method Tampering Scanner" | |
| _SCANNER_KEY = "http_method_tampering" | |
| def __init__(self, scan_id, target, domain, **kwargs): | |
| super().__init__(scan_id, target, domain, **kwargs) | |
| def run(self) -> list: | |
| self.log("INFO", f"[HTTPMethod] Testing dangerous HTTP methods on {self.target}...") | |
| advertised = self._get_options_methods() | |
| if advertised: | |
| self.log("INFO", f"[HTTPMethod] OPTIONS Allow header: {advertised}") | |
| for method, (severity, description) in DANGEROUS_METHODS.items(): | |
| status, headers, body = self._send_method(method, self.target) | |
| if status and status not in (405, 501, 400, 0): | |
| cwe = ["CWE-749"] | |
| owasp = "A05:2021 – Security Misconfiguration" | |
| if method == "TRACE" and body and "TRACE" in body.upper(): | |
| self.add_vuln( | |
| title="TRACE Method Enabled — Cross-Site Tracing (XST) Risk", | |
| severity=severity, | |
| category="HTTP Method Tampering", | |
| cvss_score=7.4, | |
| description=f"The server responded to a TRACE request with HTTP {status} " | |
| f"and reflected the request body back. {description}", | |
| remediation="Disable TRACE globally: Apache: `TraceEnable Off` | " | |
| "Nginx: `map $request_method $block {{ TRACE 1; }} if ($block) {{ return 405; }}`", | |
| evidence=f"TRACE request reflected body, status {status}", | |
| confidence="Confirmed", | |
| cwe_ids=cwe, | |
| owasp_category=owasp, | |
| ) | |
| elif method == "PUT": | |
| self.add_vuln( | |
| title="HTTP PUT Method Accepted", | |
| severity=severity, | |
| category="HTTP Method Tampering", | |
| cvss_score=9.1, | |
| description=f"Server responded HTTP {status} to a PUT request. {description}", | |
| remediation="Disable PUT in web server config unless explicitly required by the API. " | |
| "If required, restrict to authenticated endpoints only.", | |
| evidence=f"PUT request accepted with status {status}", | |
| confidence="Confirmed", | |
| cwe_ids=cwe, | |
| owasp_category=owasp, | |
| ) | |
| elif method == "DELETE": | |
| self.add_vuln( | |
| title="HTTP DELETE Method Accepted", | |
| severity=severity, | |
| category="HTTP Method Tampering", | |
| cvss_score=7.5, | |
| description=f"Server responded HTTP {status} to a DELETE request. {description}", | |
| remediation="Restrict DELETE to authenticated, authorized API routes. " | |
| "Disable WebDAV if not needed.", | |
| evidence=f"DELETE request accepted with status {status}", | |
| confidence="Confirmed", | |
| cwe_ids=cwe, | |
| owasp_category=owasp, | |
| ) | |
| elif method == "CONNECT": | |
| self.add_vuln( | |
| title="HTTP CONNECT Method Accepted", | |
| severity=severity, | |
| category="HTTP Method Tampering", | |
| cvss_score=5.3, | |
| description=f"Server responded HTTP {status} to CONNECT. {description}", | |
| remediation="Block CONNECT at the web server level unless operating a proxy.", | |
| evidence=f"CONNECT request accepted with status {status}", | |
| confidence="Confirmed", | |
| cwe_ids=cwe, | |
| owasp_category=owasp, | |
| ) | |
| elif method == "PATCH": | |
| self.add_vuln( | |
| title="HTTP PATCH Method Accepted", | |
| severity=severity, | |
| category="HTTP Method Tampering", | |
| cvss_score=5.3, | |
| description=f"Server responded HTTP {status} to a PATCH request. {description}", | |
| remediation="Restrict PATCH to authenticated API routes with proper input validation.", | |
| evidence=f"PATCH request accepted with status {status}", | |
| confidence="Confirmed", | |
| cwe_ids=cwe, | |
| owasp_category=owasp, | |
| ) | |
| elif method in ("PROPFIND", "MOVE", "COPY", "MKCOL"): | |
| self.add_vuln( | |
| title=f"HTTP {method} Method Accepted (WebDAV)", | |
| severity=severity, | |
| category="HTTP Method Tampering", | |
| cvss_score=6.5 if method == "MOVE" else 5.3, | |
| description=f"Server responded HTTP {status} to {method}. {description}", | |
| remediation="Disable WebDAV methods unless explicitly required. " | |
| "Restrict to authenticated API routes with proper input validation.", | |
| evidence=f"{method} request accepted with status {status}", | |
| confidence="Confirmed", | |
| cwe_ids=cwe, | |
| owasp_category=owasp, | |
| ) | |
| self.log("WARNING", f"[HTTPMethod] {method} → HTTP {status}") | |
| if advertised and any(m in advertised for m in ["TRACE", "PUT", "DELETE"]): | |
| self.add_vuln( | |
| title="Dangerous Methods Advertised in OPTIONS Response", | |
| severity="Medium", | |
| category="HTTP Method Tampering", | |
| cvss_score=5.3, | |
| description=f"OPTIONS Allow header reveals: `{advertised}`. Dangerous methods are publicly advertised.", | |
| remediation="Filter the Allow header to only expose methods actually required.", | |
| evidence=f"Allow header: {advertised}", | |
| confidence="Confirmed", | |
| cwe_ids=["CWE-749"], | |
| owasp_category="A05:2021 – Security Misconfiguration", | |
| ) | |
| self._test_method_override_headers() | |
| if not self.vulns: | |
| self.log("SUCCESS", "[HTTPMethod] No dangerous HTTP methods accepted.") | |
| return self.vulns | |
| def _get_options_methods(self): | |
| body, status, headers = self._make_request( | |
| self.target, method="OPTIONS", return_response_obj=True | |
| ) | |
| if status == 0: | |
| return "" | |
| if isinstance(headers, dict): | |
| return headers.get("Allow", headers.get("allow", "")) | |
| return getattr(headers, "get", lambda x, y="": y)("Allow", "") | |
| def _send_method(self, method, url): | |
| data = b"WSS-probe" if method in ("PUT", "PATCH") else None | |
| body, status, headers = self._make_request( | |
| url, method=method, data=data, return_response_obj=True | |
| ) | |
| return status, headers if isinstance(headers, dict) else {}, body or "" | |
| def _test_method_override_headers(self): | |
| for header_name in OVERRIDE_HEADERS: | |
| for override_method in ["PUT", "DELETE", "PATCH", "TRACE"]: | |
| body, status = self._make_request( | |
| self.target, | |
| method="POST", | |
| data=b"WSS-override-test", | |
| headers={"Content-Type": "application/x-www-form-urlencoded", header_name: override_method} | |
| ) | |
| if status and status not in (405, 501, 400, 404, 0): | |
| self.add_vuln( | |
| title=f"HTTP Method Override via `{header_name}` — {override_method}", | |
| severity="Medium", | |
| category="HTTP Method Tampering", | |
| cvss_score=6.1, | |
| description=f"Server accepted `{override_method}` via `{header_name}: {override_method}` header. " | |
| f"Response status: {status}. HTTP method override headers can bypass access controls " | |
| f"that only check the HTTP method.", | |
| remediation="Disable HTTP method override headers at the reverse proxy " | |
| "unless explicitly required. Validate the actual HTTP method, not the override header.", | |
| evidence=f"{header_name}: {override_method} → HTTP {status}", | |
| confidence="High", | |
| cwe_ids=["CWE-749"], | |
| owasp_category="A05:2021 – Security Misconfiguration", | |
| ) | |