September 20, 2026
"403 Forbidden Bypass: How to Find Critical Security Bugs (2026 Guide)"
"Master 403 Bypasses, HTTP/2 exploits, & TLS fingerprinting to turn rejections into bug bounty paydays."

By Pushkar Padhye
8 min read
The 403 That Wasn't a Dead End
How to Turn "Forbidden" Into "Found Something Juicy"
You find an interesting endpoint.
You send the request.
And the server responds:
403 Forbidden403 ForbiddenYou try again.
Still 403.
Different endpoint?
Admin panel?
At this point, the server might as well be saying:
"Nice try, buddy."
But here's the thing:
403 is not always the end of the road.
Sometimes, it's the beginning.
A 403 tells you that something understood your request and decided you weren't allowed to continue.
The interesting question is:
Who made that decision & did every layer of the application make the same decision?
That's where things get fun.
First, What Does 403 Actually Mean?
HTTP status codes are basically the internet way of leaving sticky notes on your requests.
200 β Here's what you asked for.
301 β Go somewhere else.
404 β I can't find that.
403 β I know what you want. No.
500 β Something exploded.200 β Here's what you asked for.
301 β Go somewhere else.
404 β I can't find that.
403 β I know what you want. No.
500 β Something exploded.A 403 usually means the server understood your request but refused to fulfill it.
But modern applications aren't usually:
Browser β ServerBrowser β ServerThey're more like:
Browser
β
CDN
β
WAF
β
Reverse Proxy
β
Web Server
β
Application
β
APIBrowser
β
CDN
β
WAF
β
Reverse Proxy
β
Web Server
β
Application
β
APIAnd here's the interesting part:
These layers don't always interpret a request identically.
If one layer sees:
/admin/adminwhile another eventually interprets the request as:
/admin//admin/you have a potential discrepancy.
And security bugs love discrepancies.
Rule #1: Don't Chase the 200
Let's get one thing out of the way.
Suppose you start with:
/admin β 403/admin β 403You try something weird and get:
/admin/ β 200/admin/ β 200Your brain immediately screams:
"BYPASS!"
Slow down.
That 200 could be:
- A login page
- A redirect
- A generic application page
- A custom error page
- A cached response
- Or the actual protected resource
The interesting result isn't:
403 β 200403 β 200It's:
Unauthorized
β
Protected functionality
β
AccessibleUnauthorized
β
Protected functionality
β
AccessibleThat's the difference between an interesting response and a vulnerability.
Now let's actually test some things.
Your 403 Payload Cheat Sheet
Don't think of these as magic strings.
Think of each payload as a question you're asking the application.
Test Example What you're investigating Trailing slash/admin/Path normalization Double slash//admin//Proxy/backend parsing Dot segment/admin/./Canonicalization Encoded character/%61dminURL decoding Encoded slash/admin%2fSeparator handling Double encoding/%2561dminMultiple decoding stages Forwarded IPX-Forwarded-For: 127.0.0.1Proxy trust Rewrite header X-Original-URL: /adminURL rewriting Alternate method Post /adminMethod-level authorization Duplicate parameter?role=user&role=adminParser differences Semicolon/admin;Framework-specific parsing
Let's break down the interesting ones.
1. The Trailing Slash Trick
Start with:
GET /admin HTTP/1.1
Host: target.exampleGET /admin HTTP/1.1
Host: target.exampleYou get:
403 Forbidden403 ForbiddenNow try:
GET /admin/ HTTP/1.1
Host: target.exampleGET /admin/ HTTP/1.1
Host: target.exampleWhy?
Because different components may normalize paths differently.
You can also investigate:
/admin//
/admin/.//admin//
/admin/./Imagine:
WAF
β
"That's not exactly /admin."
Backend
β
normalizes it
β
/adminWAF
β
"That's not exactly /admin."
Backend
β
normalizes it
β
/adminThat disagreement is what you're hunting.
2. URL Encoding
Here's a classic.
The character:
aacan be represented as:
%61%61So:
/admin/adminbecomes:
/%61dmin/%61dminTry:
GET /%61dmin HTTP/1.1
Host: target.exampleGET /%61dmin HTTP/1.1
Host: target.exampleYou can also investigate encoded separators:
/admin%2f/admin%2fThe interesting question isn't:
"Does
%2fbypass the WAF?"
It's:
"Does the WAF decode this the same way the backend does?"
That's a much more powerful way to think about bypass research.
3. Double Encoding
Now we go one level deeper.
You know:
%2f%2frepresents /.
So what happens when the % itself is encoded?
%252f%252fConceptually:
%252f
β
%2f
β
/%252f
β
%2f
β
/If multiple layers decode the request at different stages, you can occasionally get:
Security layer β sees one thing
Backend β sees anotherSecurity layer β sees one thing
Backend β sees anotherAnd that's where parser confusion becomes interesting.
4. Headers Can Be Dangerous
Now let's talk about something beginners often overlook.
HTTP headers aren't just boring metadata.
Sometimes applications make security decisions based on them.
For example:
X-Forwarded-For: 127.0.0.1X-Forwarded-For: 127.0.0.1or:
X-Real-IP: 127.0.0.1X-Real-IP: 127.0.0.1Why test this?
Because badly configured applications can sometimes trust attacker-controlled proxy information.
Conceptually:
You
β
X-Forwarded-For: 127.0.0.1
β
Application
β
"Internal request!"You
β
X-Forwarded-For: 127.0.0.1
β
Application
β
"Internal request!"If that actually changes authorization behavior, you've found something worth investigating.
Don't assume it works.
Prove that the application is making a security decision based on the header.
5. X-Original-URL
Here's another one that can produce some interesting behavior in proxy-heavy architectures.
Try comparing:
GET / HTTP/1.1
Host: target.exampleGET / HTTP/1.1
Host: target.examplewith:
GET / HTTP/1.1
Host: target.example
X-Original-URL: /adminGET / HTTP/1.1
Host: target.example
X-Original-URL: /adminYou might get:
Normal request β 200
Modified request β 403Normal request β 200
Modified request β 403Or:
Normal request β 200
Modified request β different responseNormal request β 200
Modified request β different responseOr, in a badly configured environment:
Modified request β protected contentModified request β protected contentThe last case is where things get spicy.
6. HTTP Method Confusion
Don't forget that HTTP has more than GET.
GET
POST
PUT
PATCH
DELETE
HEAD
OPTIONSGET
POST
PUT
PATCH
DELETE
HEAD
OPTIONSSuppose:
GET /admin HTTP/1.1GET /admin HTTP/1.1returns:
403403You can investigate whether authorization is consistently enforced for another supported method:
POST /admin HTTP/1.1POST /admin HTTP/1.1If the response changes, investigate why.
For state-changing requests, keep your tests non-destructive and follow the target's rules.
The interesting finding isn't:
GET β 403
POST β 200GET β 403
POST β 200It's:
An unauthorized user can actually perform functionality that should require authorization.
7. Duplicate Parameters
Here's a fun one.
Suppose you have:
/account?role=user/account?role=userWhat happens if you send:
/account?role=user&role=admin/account?role=user&role=adminDifferent components may disagree about which value wins.
For example:
WAF
β
uses first value
β
role=user
Application
β
uses last value
β
role=adminWAF
β
uses first value
β
role=user
Application
β
uses last value
β
role=adminOr vice versa.
This is called parameter pollution, and it can become interesting when duplicate parameters influence authorization, redirects, account selection, or other security-sensitive behavior.
Again:
The bug isn't the duplicate parameter.
The bug is the inconsistent interpretation that produces security impact.
Now Open Burp
This is where theory becomes much more fun.
Send your request to Burp Repeater.
Start with a baseline:
GET /admin HTTP/1.1
Host: target.example
Cookie: session=...GET /admin HTTP/1.1
Host: target.example
Cookie: session=...Record:
Status: 403
Length: 1234Status: 403
Length: 1234Now change one thing.
GET /admin/ HTTP/1.1GET /admin/ HTTP/1.1Compare.
Reset.
Try:
GET /%61dmin HTTP/1.1GET /%61dmin HTTP/1.1Compare.
Reset.
Try a relevant header:
X-Original-URL: /adminX-Original-URL: /adminCompare.
You're no longer randomly throwing payloads.
You're running experiments.
And that's a much better skill.
Wait⦠The WAF Can Look at TLS Too?
Here's the part many beginners don't think about.
You may assume the WAF only sees:
URL
Headers
Cookies
BodyURL
Headers
Cookies
BodyBut modern security systems can also use connection-level signals, including characteristics of the TLS handshake.
So you might see:
Browser β Target β 200
Burp β Target β 403Browser β Target β 200
Burp β Target β 403Even though you're sending what appears to be the same HTTP request.
That should make you curious.
Because your HTTP request isn't necessarily the only thing that's different.
Playing With TLS in Burp
In current Burp versions, you can find TLS-related configuration under:
Settings
β
Network
β
TLSSettings
β
Network
β
TLSThere you can inspect/configure how Burp negotiates TLS with upstream servers, including supported protocols and cipher suites.
Don't think:
"I'll randomly change ciphers until the WAF gives up."
Think:
"Is the target treating Burp differently because of the connection characteristics?"
That's a much more interesting experiment.
The Really Interesting Part: TLS Pass-Through
Burp also has TLS pass-through.
Look under:
Settings
β
Proxy
β
TLS pass throughSettings
β
Proxy
β
TLS pass throughFor an authorized target, you can configure a host to pass through without Burp terminating the TLS connection.
That gives you an interesting comparison:
Normal Burp
β
Browser β Burp β Target
β
403Normal Burp
β
Browser β Burp β Target
β
403versus:
TLS pass-through
β
Browser β Burp β Target
β
?TLS pass-through
β
Browser β Burp β Target
β
?Now you can ask:
Is the target reacting to the browser's original TLS characteristics?
That's a completely different dimension of testing.
And suddenly your 403 investigation has gone from:
"Maybe I need another URL.""Maybe I need another URL."to:
"Wait... what exactly is the WAF fingerprinting?""Wait... what exactly is the WAF fingerprinting?"That's the kind of rabbit hole bug bounty gets fun for.
Don't Forget HTTP/2
TLS isn't the only layer worth investigating.
Modern browsers commonly use HTTP/2 when supported.
Burp can also work with HTTP/2, and you can investigate HTTP/1 versus HTTP/2 behavior.
For example:
HTTP/1.1 β 403
HTTP/2 β ?HTTP/1.1 β 403
HTTP/2 β ?Or the reverse.
Again, you're looking for differences in how the request is processed.
Sometimes a security control understands one representation correctly and another imperfectly.
Sometimes nothing happens.
That's fine.
You're learning how the target works.
Information Disclosure: The Other Half of the Story
Let's say you've finally reached something interesting.
Now what?
Look for information disclosure.
A website can accidentally reveal:
- Internal hostnames
- Private IP addresses
- File paths
- Stack traces
- Source code
- Configuration
- API credentials
- Cloud credentials
- Database connection details
- Internal API endpoints
Places worth understanding include:
/.env
/.git/config
/debug
/status
/health/.env
/.git/config
/debug
/status
/healthAnd don't forget backup files:
index.php.bak
config.old
app.js~
backup.zipindex.php.bak
config.old
app.js~
backup.zipThese aren't magic URLs.
They're common examples of things that have historically been exposed through poor deployment practices.
If you find something, verify what it actually contains and what impact it has.
JavaScript Is Basically a Treasure Map
Open the application's JavaScript.
Seriously.
Modern frontend applications often reveal a surprising amount of information.
Search for things like:
/api/
/graphql
/admin
/internal
/debug
token
key
secret/api/
/graphql
/admin
/internal
/debug
token
key
secretYou might discover:
- Hidden API routes
- Feature flags
- Internal endpoints
- Parameter names
- Third-party integrations
- Client-side configuration
And sometimes you'll encounter something that looks like a secret.
But remember:
"Looks like a key""Looks like a key"doesn't automatically mean:
"Valid credential""Valid credential"If it's actually usable for unauthorized access, now you've got something worth reporting.
The Killer Combo
Now put the pieces together.
Imagine:
/api/internal/users/api/internal/usersreturns:
403403You investigate the request processing.
You discover a parsing difference.
You verify that an unauthorized request reaches the protected endpoint.
Then the endpoint returns information it shouldn't.
Maybe internal user data.
Maybe configuration.
Maybe something even more sensitive.
Now you've chained:
Access-control weakness
+
Information disclosure
=
Meaningful security impactAccess-control weakness
+
Information disclosure
=
Meaningful security impactThat's much more interesting than simply getting a different status code.
Your Workflow
Here's the whole process:
1. Find an interesting endpoint
/admin
/internal
/api/internal/.../admin
/internal
/api/internal/...2. Capture the baseline
4034033. Send it to Repeater
Don't modify ten things at once.
4. Test path variations
/admin/
/admin//
/admin/./
/%61dmin/admin/
/admin//
/admin/./
/%61dmin5. Test relevant headers
X-Forwarded-For
X-Real-IP
X-Original-URLX-Forwarded-For
X-Real-IP
X-Original-URL6. Test request methods
Where appropriate:
GET
POST
PUT
PATCHGET
POST
PUT
PATCH7. Compare HTTP versions / connection behavior
HTTP/1.1
HTTP/2HTTP/1.1
HTTP/28. Investigate TLS behavior
Normal Burp
TLS pass-throughNormal Burp
TLS pass-through9. Look for information disclosure
Check responses, JavaScript, error messages, and other in-scope resources.
10. Prove impact
Don't submit:
"I got 200!"
Submit:
"An unauthorized request can access X, which exposes Y."
That's a bug bounty report.
The AI Slop Problem
And here's something worth remembering in 2026.
AI can help you understand a target.
It can explain an HTTP response.
It can suggest hypotheses.
It can help write your report.
But don't let it do your thinking for you.
If an AI tells you:
"This is definitely a critical 403 bypass."
Ask:
"Show me the evidence."
If you can't reproduce it, don't report it.
If you can't explain it, don't pretend you understand it.
A clean, reproducible finding beats a pile of AI-generated guesses.
Your Challenge
Here's your homework.
Find a legal lab or an in-scope bug bounty target.
Find a 403.
Then don't immediately Google:
"403 bypass payload"
Instead, build a little experiment.
Start with:
Baseline
β
Path
β
Encoding
β
Headers
β
Methods
β
HTTP/1 vs HTTP/2
β
TLS behaviorBaseline
β
Path
β
Encoding
β
Headers
β
Methods
β
HTTP/1 vs HTTP/2
β
TLS behaviorRecord what changes.
Then ask the question that matters:
"Why?"
If you can answer that question, you're no longer just throwing payloads at a website.
You're learning how its security architecture works.
Final Thoughts
The best bug bounty hunters aren't necessarily the people with the biggest wordlists.
They're the people who notice something weird and refuse to stop at:
"Huh. That's strange."
They ask:
"What happens if�"
What happens if I add a slash?
What happens if I encode the path?
What happens if the proxy sees a different URL?
What happens if the application sees a different HTTP method?
What happens if Burp and the browser negotiate the connection differently?
And eventually:
"What exactly is the WAF seeing that I'm not?"
That's where things get interesting.
So the next time you see:
403 Forbidden403 Forbiddendon't close the tab.
Open Burp.
Send it to Repeater.
Change one thing.
Watch what happens.
Because sometimes Forbidden isn't the answer.
It's the question.
π΅οΈ Your Turn
Pick an authorized target or lab, fire up Burp, find a 403, and start experimenting.
And when you discover that the response changes?
Don't celebrate yet.
Figure out why.
That's where the real bug is hiding.
Disclaimer:_ Only test systems you have explicit permission to test. Bug bounty programs authorize testing only within their stated scope and rules. Keep testing non-destructive, respect rate limits, and follow the program's disclosure requirements._