All posts
The uppercase letter that bypassed authorization

The uppercase letter that bypassed authorization

A single uppercase letter in a URL path will sometimes get you past an authorization layer that blocks the lowercase version. I picked this up by accident years ago and it's been a one-second check on every 401 or 403 since. Hit rate is low. The payoff when it does fire is a clean bypass into something that should have been gated.

The setup is whatever endpoint you have that returns 401 or 403. Say /api/getUsers. The endpoint exists, the authorization layer is blocking you.

Try switching some letters to uppercase. Instead of /api/getUsers, hit /aPi/getUsers or /Api/getUsers. Mix it up a bit.

# Blocked by the authorization layer
$ curl -i https://target.example.com/api/getUsers
HTTP/1.1 403 Forbidden

# Same endpoint, one capital letter, authorization doesn't match
$ curl -i https://target.example.com/aPi/getUsers
HTTP/1.1 200 OK
Content-Type: application/json

[{"id":1,"email":"admin@..."}, ...]

Sometimes this works. The authorization check fails to trigger, and suddenly you're in.

Why does this happen? In my experience, it usually comes down to two things:

First, developers manually write case-sensitive authorization filters. They hardcode specific paths into their security layer without accounting for variations in casing. The filter looks for /api/getUsers exactly, and when you send /aPi/getUsers, it doesn't match. The request slips through.

Second, reverse proxies can forward misconfigured paths to the backend without running the same security checks. The proxy normalizes or passes the path differently than the authorization layer expects, creating a gap in coverage.

This is an edge case. Over nine years of bug bounty work I've seen it land fewer than ten times. Those ten times made the check worth keeping.

It takes seconds. When you have a 401 or 403 and are about to move on, try a few case variations first. When it works, the gap is between two layers that disagreed on whether /api/getUsers and /aPi/getUsers are the same path.

Authorization should be case-insensitive. Paths should be normalised before hitting any security layer. In practice configurations drift, code ships under deadline pressure and one of the two layers ends up doing exact-match comparison on a value the other layer has already mutated.

That's the nature of security research. You test the edges, the weird cases, the things that shouldn't work but sometimes do. Most of the time, nothing happens. But every once in a while, you find something.

Want me to find this in your app, or learn to find it yourself?

Request a pentestBook mentoring
← PreviousThe RCE hidden behind a forgotten endpointNext →The hidden cost of CVE scanner convenience