Spread
A spreadsheet app with an admin console that is “blocked at the edge” — which turns out to be a precise description of the whole problem. The guard lives in one tier; the content lives in another; the two disagree about what a URL is.
Spoiler-light notes — no flags, credentials or copy-paste exploits. Solve it yourself first.
Recon
Single web port, one vhost, and a small CSV upload / spreadsheet-viewer application. The navigation contains an item that reads “Admin — internal only (blocked at the edge)”.
That wording is the box handing you the answer’s shape. “Blocked at the edge” tells you three things at once: there is a privileged interface, it exists on this same origin, and the thing stopping you is a network-layer rule rather than the application’s own authorisation.
Reading the 403 correctly
Requesting the admin path returns a 403 — but the Server header on that 403 is the edge
proxy, not the application.
That single header is the entire diagnosis. If the application itself were refusing, the response would come from the app’s own server. A response produced by the thing in front of the app means the request never reached the app — so the app has never had an opinion about whether you’re allowed. The access control has been pushed up a tier, and the tier below is still serving the content.
Where the two tiers disagree
The edge matches the protected path as a literal string. The application’s router, like almost every modern web framework’s, canonicalises the request path before routing — it collapses duplicate separators, decodes percent-encoded characters, and normalises trailing slashes. Every one of those normalisations turns a path that the edge does not recognise into the exact path the app does.
So a handful of equivalent spellings of the same path render the admin console, while the one spelling the edge was told to block is the only one that gets stopped. The bypass doesn’t require any encoding cleverness beyond knowing that the two tiers normalise differently — the hard part is recognising that they do.
Why this generalises
The bug isn’t “the regex was incomplete”. It’s authorisation enforced at the wrong tier. Any time a rule is applied in front of the application rather than inside it, the application is still happy to serve the same content through any other path that reaches it:
- a different spelling of the same path,
- a different hostname that routes to the same backend,
- an alternate port or direct-to-origin connection,
- a method the proxy’s rule never considered.
Testing these is cheap and almost always decisive, and it belongs before reaching for anything heavier. The proxy is not the application, and “the edge blocks it” is not the same sentence as “it is protected”.
A practical checklist for any path a front-end tier claims to protect — all of these are things the edge commonly fails to recognise while the origin happily serves them:
- a doubled separator, an omitted separator, or a trailing separator that the rule didn’t anticipate;
- percent-encoded characters anywhere in a path segment, including the segment separators themselves;
- case changes, where the app routes case-insensitively and the rule does not;
- the path reached under a different hostname, a different port, or a direct-to-origin address that bypasses the front tier entirely;
- the same resource addressed by an ID rather than by name.
Each is a one-line change to the request, and each either works or rules out the whole approach in seconds. Working through them is far cheaper than concluding the endpoint is protected and moving on.
Takeaways
- A 403 whose
Serverheader belongs to the proxy means the app never saw the request — and therefore never applied its own authorisation. - Enforce authorisation in the application. Edge rules are a second line, never the only one.
- Whenever a path is “blocked”, enumerate equivalent encodings and normalisations before assuming the block holds: double separators, percent-encoding, encoded trailing separators.
- The same backend is often reachable by more than one name, port or route. Try them.
- Hints in the UI (“internal only”, “blocked at the edge”) describe the architecture — take them literally.