Isaac Burton //
For as long as we have known about prototype pollution vulnerabilities, there has been confusion on what they are and how they can be exploited. We’re going to discuss some of the easiest ways to identify a prototype pollution vulnerability in the wild, which can lead to all kinds of exploitation!
Prototype pollution can exist server side or client side. Server-side prototype pollution can be used to modify application control flow, which can be fun and rewarding to exploit. When a prototype pollution vulnerability is present client-side, it can be leveraged to perform cross-site scripting (XSS). In both cases, exploiting prototype pollution is heavily dependent on context. Fortunately, identifying the vulnerability is often not so difficult.
We’re going to jump into a couple examples just to show how easy identifying this vulnerability can be. Then, we will dig into why these vulnerabilities occur and explore other methods of identification and exploitation.
Finding Server-Side Prototype Pollution
One of the easiest ways to identify this vulnerability is to send an API method to Burp Suite’s Repeater and wrap a JSON formatted request body in a ‘__proto__’ object.
The example below shows a request that is valid for an imaginary pizza restaurant’s website. As an example, the user is supplying their favorite pizza type and phone number so that they can sign up for a rewards program.
On the backend, the API parses the request body and makes sure that all the required parameters are present. If we sent the request below, we would receive a 400 Bad Request response.
We can use this input validation to our advantage. We then wrap the valid request body in a ‘__proto__’ object as shown below, to which the application returns a 200 OK response.
It may seem strange that this works, but it’s our first indicator that there is a vulnerability here. At this point, there are two potential reasons that this request was accepted:
1. The API searches the request body for valid request keys.
2. An unsafe merge is being performed (more on this later.)
We can send the request below to rule out the first option. If the application is searching for valid keys, then we should be able to change the ‘__proto__’ object to anything else and the application will respond 200 OK. As an example, we will change ‘__proto__’ to `false_positive`.
Since the application rejected this request, we can assume that the ‘__proto__’ object has a special meaning in the application. Feel free to try variations such as ‘_proto_’ or ‘__proto’, the responses should all return 400 Bad Request.
If you only receive 200 OK responses when the object’s name is ‘__proto__’, then congratulations, you just found a server-side prototype pollution vulnerability! Later, we will dive deeper into why this works and how this can be exploited.
Testing for Client-Side Prototype Pollution
PortSwigger has added automated prototype pollution identification and exploitation into their browser tool, DOM Invader. The tool can identify sinks and gadgets, and even create a proof-of-concept exploit!
Sinks are places in the code where you can modify the prototype object, such as a URL parameter that is unsafely handled by the application. Gadgets are locations where polluted objects can be leveraged for exploitation. DOM Invader makes finding sinks and gadgets easy, just be sure that you have an updated version of Burp Suite and follow the steps below:
1. Open DOM Invader in Burp (Proxy > Intercept > Open Browser).
2. Go to extensions in the browser, enable the Burp Suite extension:
3. Turn on DOM Invader and prototype pollution in the extension.
4. Reload the page and open the Inspector, then navigate to the newly added ‘DOM Invader’ tab.
5. If the tool identifies sinks, then open the extension back up and enable gadget scanning.
6. Reload and navigate back to the Inspector’s ‘DOM Invader’ tab. You should see a progress bar at the top. If any gadgets are identified for the previously found sinks, then you should see an option to generate a proof-of-concept exploit as shown below.
The DOM Invader extension is quite powerful and effective at searching through client-side code, which is often minified and difficult to read in the wild. If you are interested in the manual approach, I highly recommend checking out PortSwigger Academy’s course on prototype pollution (https://portswigger.net/web-security/prototype-pollution).
How Does Prototype Pollution Work?
Consider a server-side merge function which takes the properties from one object and updates them in another. You may want to save some of the properties stored in the destination object and only update values that are described in the source object. Furthermore, objects contained inside other objects need to be copied in the same manner. The code snippet below could be a solution to this problem.
The vulnerability here may not be obvious. If an attacker includes a ‘__proto__’ key with the value set to an object, the recursion will not only write to the current object’s prototype, but also the global object’s prototype. This means that all newly created objects will inherit properties defined by the attacker. The consequences of this are only limited by the attacker’s imagination.
Server-Side Prototype Pollution Exploitation
Since we cannot go over every possible situation, we will just look at a couple examples of how we can leverage this vulnerability.
Context is king when it comes to prototype pollution. The difficulty with exploiting anything server-side is that you typically cannot see what’s on the other side. First, do as much discovery as you can. Use Gobuster (https://github.com/OJ/gobuster) and discovery wordlists from SecLists (https://github.com/danielmiessler/SecLists) to find hidden files and locations on the server. If you’re lucky, you may find a source code repository. If the project itself is open-source, you’re all set to start digging.
Since the global prototype will be polluted for the life of the thread, you may be able to take advantage of open-source libraries used by the application. Try to force the server to return error messages through fuzzing. One of my favorite tool choices for this is with Burp Suite Intruder and the wordlists provided in wfuzz (https://github.com/xmendez/wfuzz). Once the attack is completed, you can sort the responses by length and status code, and even search for error message keywords.
The error messages you receive may return partial — if not detailed — stack traces that can help you map the backend source code. Also, if you can identify any libraries used by the application, you may be able to take advantage of the context provided by the sources.
As an example, let’s assume that the application performs the following steps upon receiving a POST request:
- The server checks the session token provided in the request headers and accepts or rejects the request. The user’s ID is stored in the thread’s memory for later.
- The URL is matched to a function which updates the user’s profile.
- The function (unsafely) merges the request data into an object to hold the user-supplied data.
- The server verifies that all required properties are included.
- The application creates a user object from information stored in a database.
- A privilege check is performed on the user object, where a property is only set for administrative users.
- The function completes its routine by writing back to the user database and returning 200 OK.
In this scenario, an attacker could simply add the administrative property to the ‘__proto__’ object, which elevates privilege for the request. Remember that every object which is created after prototype pollution is exploited is affected.
Another fantastic writeup on this topic is from Changhui Xu, who describes the issue very clearly and provides more examples:
Both of the previous sources are referenced by the official CWE: Improperly Controlled Modification of Object Prototype Attributes (‘Prototype Pollution’).
PortSwigger has a new course (as of this writing) on server-side prototype pollution, which is available on their academy website:
Lastly, I’ve created a GitHub repository for the example described in the section above. The example is roughly 100 lines of code in one file with no dependencies and can be run with Node.js or in a browser console. Feel free to use this as a reference or a playground:
Ready to learn more?
Level up your skills with affordable classes from Antisyphon!
Available live/virtual and on-demand