| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Server-Side Prototype Pollution |
| Difficulty | Practitioner |
| Objective | Confirm server-side prototype pollution using a non-destructive status code injection technique when injected properties are not reflected in the response |
Detecting Server-Side Prototype Pollution Without Polluted Property Reflection¶
I logged in as wiener:peter and found the address update form:
Injecting a test property via __proto__:
{"__proto__": {"teto": "miku"}}
teto didn't appear in the response — this server doesn't echo back inherited prototype properties in the success response. But the absence of reflection doesn't mean there's no pollution. The server may filter inherited properties before serializing the user object while still using those same properties in other code paths.
Error objects in Express are the reliable oracle. Express constructs error objects dynamically — if status or statusCode isn't explicitly set on the error, the object falls through to the prototype. Injecting a distinctive status code and comparing error responses before and after is the standard non-destructive detection technique.
First, I intentionally broke the JSON syntax to get a baseline error:
...,"sessionId":"LrT49NNxpGxYe9v02xEShQk3SBkUGQgM}
Error response body:
{"error":{"expose":true,"statusCode":400,"status":400,"body":"...","type":"entity.parse.failed","teto":"miku"}}
teto: "miku" appeared here even though it wasn't in the success response — the error object does inherit from the polluted prototype. It just wasn't the reflection surface we were watching. And status: 400 is the baseline for a parse error.
With the JSON fixed, I injected status into the prototype:
{
...,
"__proto__": {
"status": 500
}
}
Normal 200 response — no visible effect on success. Breaking the JSON syntax again:
{"error":{"expose":false,"statusCode":500,"status":500,"body":"...","type":"entity.parse.failed","teto":"miku"}}
status and statusCode both changed from 400 to 500 — matching exactly what was injected into Object.prototype. The error object inherited the polluted value instead of using the parse error default. Pollution confirmed without any reflected property in the success response. The canary property needs to be something that appears in the response — status in Express error objects is the canonical choice precisely because it always appears in structured error responses and its change is unambiguous.
Lab solved :P