File size: 11,467 Bytes
d543fc1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"""

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",
                    )