Access-control bypass
Most production sites put a gate in front of protected pages — a CDN, a reverse proxy, a WAF, or a framework auth filter. The gate decides which requests reach the backend. Whenever the gate and the backend normalise URLs, headers, or HTTP methods differently, an attacker can craft a request the gate refuses but the backend obediently serves.
Pentestas's forbidden_bypass module probes for these inconsistencies on every URL the crawler saw return 401 or 403. Each successful bypass becomes a HIGH-severity ACCESS_CONTROL_BYPASS finding.
When it runs
Right after the crawl, before the heavy injection probes. The module collects every URL the crawler observed at status 401 or 403, dedupes them, and runs the bypass catalogue against up to 30 of them (the cap stops a misconfigured target with thousands of forbidden URLs from consuming the whole scan budget).
The phase shows up in the live feed as [FORBIDDEN_UNMASK] Probing access-control bypasses and is cancelled by the same Redis stop flag that cancels every other phase.
Technique catalogue
Up to 47 distinct attempts per URL, in four families:
1. Path normalisation (22 tricks)
The path component is rewritten in ways that often round-trip through CDN/WAF/proxy normalisation differently from the backend's:
| Family | Examples |
|---|---|
| Trailing | /admin/, /admin/., /admin/./, /admin/.. |
| Tomcat semicolon | /admin/..;/, /admin;/ |
| Encoded segments | /admin/..%2f, /%2eadmin, /admin%20, /admin%09, /admin%00 |
| Extension append | /admin.json, /admin.html, /admin.css, /admin.js |
| Query / fragment | /admin?, /admin# |
| Case folding | /ADMIN, /Admin |
| Prefix tricks | /./admin, //admin, /\admin |
2. Trust-the-proxy headers (15 tricks)
Some upstream layers grant access if a downstream-trusted header asserts the request is "internal" or rewrites the URL:
X-Original-URL: /admin
X-Rewrite-URL: /admin
X-Custom-IP-Authorization: 127.0.0.1
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Forwarded-Host: localhost
X-Host: localhost
Forwarded: for=127.0.0.1
Cluster-Client-IP: 127.0.0.1
Referer: <origin>/admin
Same path, just one extra header per request.
3. HTTP method substitution (7 methods)
Some gates only filter GET. The backend may happily serve the same content for POST, HEAD, PATCH, OPTIONS, PUT, DELETE, or TRACE.
4. Method override on a POST (3 headers)
Spring, Rails, and .NET MVC commonly honour:
POST /admin
X-HTTP-Method-Override: GET
The gate sees POST, the framework reroutes it to its GET /admin handler.
Detection logic
Each attempt is compared against the baseline response (the gated 401/403):
- The bypass response must have a status in
{200, 201, 204, 207, 301, 302, 307, 308}. - The body content must differ from the baseline (different MD5).
- For status
200, the body must be at least 50 bytes AND at least 64 bytes different in size from the baseline (filters custom 403 pages that some WAFs return as200with the same content). - For 3xx, a
Locationheader is treated as proof on its own.
A finding is emitted only when all checks pass. Verified flag is set to true.
What the finding looks like
- Title:
403 / 401 bypass: <technique> reaches protected page - Severity: HIGH
- CWE: CWE-284 (Improper Access Control)
- Endpoint: the URL that returned
401/403originally - Payload: the modified path, header line, or method label
- Evidence: baseline status, bypass status, body size, body excerpt
- Recommendation: move the access-control check into the application layer (or onto the same component that resolves the final route), so URL normalisation can't desynchronise the gate and the backend; if a WAF or proxy enforces the rule, normalise the request URL, headers, and method before evaluation, and reject ambiguous inputs rather than passing them through.
Limits
- Cap of 30 URLs per scan and 50 attempts per URL — keeps the bypass phase under ~1,500 requests even on a 403-heavy site.
- The module never sends a body on write methods (
PUT,PATCH,DELETE). A200on those alone is enough signal; we don't risk creating or mutating data on the target. - The module is opt-out — leave the forbidden bypass module checked on the New scan dialog (it ships on by default), or include
"forbidden_bypass"inscan_typesvia the API.
See also
- Validation — the global Accuracy Gate that filters every finding before it reaches you.
- Authenticated scans — supplying credentials so the crawler reaches more URLs that may turn out to be
401/403for the operator account but reachable for an attacker via these tricks.