Pentestas / help

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):

  1. The bypass response must have a status in {200, 201, 204, 207, 301, 302, 307, 308}.
  2. The body content must differ from the baseline (different MD5).
  3. 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 as 200 with the same content).
  4. For 3xx, a Location header 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/403 originally
  • 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). A 200 on 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" in scan_types via 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/403 for the operator account but reachable for an attacker via these tricks.