Why is Server-side Prototype Pollution More Difficult to Detect?
For a number of reasons, server-side prototype pollution is generally more difficult to detect than its client-side variant:
No source code access - There’s no easy way to get an overview of which sinks are present or spot potential gadget properties.
Lack of developer tools - As the JavaScript is running on a remote system, you don’t have the ability to inspect objects at runtime like you would when using your browser’s DevTools to inspect the DOM.
The DoS problem - Successfully polluting objects in a server-side environment using real properties often breaks application functionality or brings down the server completely.
Pollution persistence - Once you pollute a server-side prototype, this change persists for the entire lifetime of the Node process and you don’t have any way of resetting it.
Detecting Server-side Prototype Pollution via Polluted Property Reflection
An easy trap for developers to fall into is forgetting or overlooking the fact that a JavaScript for...in loop iterates over all of an object’s enumerable properties, including ones that it has inherited via the prototype chain.
const myObject = { a: 1, b: 2 };// pollute the prototype with an arbitrary propertyObject.prototype.foo = 'bar';// confirm myObject doesn't have its own foo propertymyObject.hasOwnProperty('foo'); // false// list names of properties of myObjectfor(const propertyKey in myObject){ console.log(propertyKey);}// Output: a, b, foo
If the application later includes the returned properties in a response, this can provide a simple way to probe for server-side prototype pollution. POST or PUT requests that submit JSON data to an application or API are prime candidates for this kind of behavior as it’s common for servers to respond with a JSON representation of the new or updated object.
POST /user/update HTTP/1.1Host: vulnerable-website.com...{ "user":"wiener", "firstName":"Peter", "lastName":"Wiener", "__proto__":{ "foo":"bar" }}
If the website is vulnerable, your injected property would then appear in the updated object in the response:
Any features that involve updating user data are worth investigating as these often involve merging the incoming data into an existing object that represents the user within the application. If you can add arbitrary properties to your own user, this can potentially lead to a number of vulnerabilities, including privilege escalation.
Lab: Privilege Escalation via Server-side Prototype Pollution
The original request used for updating address of a user:
POST /my-account/change-address HTTP/2Host: 0a92007803c2d47d809e0d7f00e8007c.web-security-academy.netCookie: session=1d9xQCX3IsMagk4vkY9V0lTCJgW67NZ4Content-Length: 168Content-Type: application/json;charset=UTF-8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36Origin: https://0a92007803c2d47d809e0d7f00e8007c.web-security-academy.netReferer: https://0a92007803c2d47d809e0d7f00e8007c.web-security-academy.net/my-account?id=wiener{ "address_line_1": "Wiener HQ", "address_line_2": "One Wiener Way", "city": "Wienerville", "postcode": "BU1 1RP", "country": "UK", "sessionId": "1d9xQCX3IsMagk4vkY9V0lTCJgW67NZ4"}
We now can access /admin endpoint and delete carlos user.
Detecting Server-side Prototype Pollution without Polluted Property Reflection
One approach is to try injecting properties that match potential configuration options for the server. You can then compare the server’s behavior before and after the injection to see whether this configuration change appears to have taken effect. If so, this is a strong indication that you’ve successfully found a server-side prototype pollution vulnerability.
We’ll look at the following techniques:
Status code override
JSON spaces override
Charset override
Info
You can use any of the techniques covered in this section to solve the accompanying lab.
Status Code Override
Server-side JavaScript frameworks like Express allow developers to set custom HTTP response statuses. In the case of errors, a JavaScript server may issue a generic HTTP response, but include an error object in JSON format in the body.
HTTP/1.1 200 OK...{ "error": { "success": false, "status": 401, "message": "You do not have permission to access this resource." }}
Node’s http-errors module contains the following function for generating this kind of error response:
function createError () { //... if (type === 'object' && arg instanceof Error) { err = arg status = err.status || err.statusCode || status // first line } else if (type === 'number' && i === 0) { //... if (typeof status !== 'number' || (!statuses.message[status] && (status > 400 || status >= 600))) { // second line status = 500 } //...
The first commented line attempts to assign the status variable by reading the status or statusCode property from the object passed into the function (arg). If the website’s developers haven’t explicitly set a status property for the error, you can potentially use this to probe for prototype pollution as follows:
Find a way to trigger an error response.
Try polluting the prototype with your own status property (without trigger the error response).
Trigger the error response again and check whether you’ve successfully overridden the status code.
Note
You must choose a status code in the 400-599 range. Otherwise, Node defaults to a 500 status regardless, as you can see from the second commented line, so you won’t know whether you’ve polluted the prototype or not.
JSON Spaces Override
The Express framework provides a json spaces option, which enables you to configure the number of spaces used to indent any JSON data in the response. In many cases, developers leave this property undefined as they’re happy with the default value, making it susceptible to pollution via the prototype chain.
Info
Although the prototype pollution has been fixed in Express 4.17.4, websites that haven’t upgraded may still be vulnerable.
Charset Override
Notice that the following code passes an options object into the read() function, which is used to read in the request body for parsing.
One of these options, encoding, determines which character encoding to use. This is either derived from the request itself via the getCharset(req) function call, or it defaults to UTF-8:
If you look closely at the getCharset() function, it looks like the developers have anticipated that the Content-Type header may not contain an explicit charset attribute, so they’ve implemented some logic that reverts to an empty string in this case.
If you can find an object whose properties are visible in a response, you can use this to probe for sources:
Add an arbitrary UTF-7 encoded string to a property that’s reflected in a response. For example, foo in UTF-7 is +AGYAbwBv-.
To avoid overwriting properties when a request contains duplicate headers, the _addHeaderLine() function checks that no property already exists with the same key before transferring properties to an IncomingMessage object:
If it does, the header being processed is effectively dropped. Due to the way this is implemented, this check (presumably unintentionally) includes properties inherited via the prototype chain. This means that if we pollute the prototype with our own content-type property, the property representing the real Content-Type header from the request is dropped at this point, along with the intended value derived from the header.
Lab: Detecting Server-side Prototype Pollution without Polluted Property Reflection
Status Code Override
First, we try the status code override approach by delete the last curly bracket in the request body of change address endpoint:
POST /my-account/change-address HTTP/2{ "address_line_1": "Wiener HQ", "address_line_2": "One Wiener Wayaaaa", "city": "Wienerville", "postcode": "BU1 1RP", "country": "UK", "sessionId": "xy5OPCgzfUd9qXASWVs0CgYDQijCXxcZ"
Scanning for Server-side Prototype Pollution Sources
Explore the target website using Burp’s browser to map as much of the content as possible and accumulate traffic in the proxy history.
In Burp, go to the Proxy > HTTP history tab.
Filter the list to show only in-scope items.
Select all items in the list.
Right-click your selection and go to Extensions > Server-Side Prototype Pollution Scanner > Server-Side Prototype Pollution, then select one of the scanning techniques from the list.
When prompted, modify the attack configuration if required, then click OK to launch the scan.
Info
In Burp Suite Professional, the extension reports any prototype pollution sources it finds via the Issue activity panel on the Dashboard and Target tabs. If you’re using Burp Suite Community Edition, you need to go to the Extensions > Installed tab, select the extension, then monitor its Output tab for any reported issues.
Note
If you’re unsure which scanning technique to use, you can also select Full scan to run a scan using all of the available techniques. However, this will involve sending significantly more requests.
Bypassing Input Filters for Server-side Prototype Pollution
Node applications can delete or disable __proto__ altogether using the command-line flags --disable-proto=delete or --disable-proto=throw respectively. However, this can also be bypassed by using the constructor technique.
Lab: Bypassing Flawed Input Filters for Server-side Prototype Pollution
The objective of this lab is privilege escalation.