Whitebox Attacks  

Exploitation Remarks & Prevention


Now that we have examined and exploited various prototype pollution vulnerabilities, let us discuss some remarks regarding identifying and exploiting them in real-world engagements. Additionally, this section will end with how to prevent prototype pollution vulnerabilities.


Exploitation Remarks

As discussed previously, polluting prototypes can result in unforeseen and undesired side effects that potentially break the entire web application. Therefore, it is ill-advised to throw prototype pollution payloads on a production web application and hope for the best. Testing and fine-tuning the prototype pollution payload is recommended on a local copy of the target web application. While we focused on detecting and exploiting prototype pollution vulnerabilities from a whitebox approach in this module, we do not always have access to the source code of the target web application and need to be able to identify them black-box. Fortunately, a few techniques exist to detect prototype pollution using a black-box approach as safely as possible.

Status Code

The first and most universal technique is manipulating the status code returned when the web application encounters an issue. First, we need to determine how the web application reacts if we provide an invalid JSON request body:

image

The web application responds with an HTTP 400 status code. To confirm prototype pollution, we can manipulate the returned status code by polluting the status property of the Object.prototype object using a payload similar to the following:

{
	"__proto__":{
		"status":555
	}
}

Depending on the web application's implementation, we might need to traverse multiple steps up the prototype chain to reach the Object.prototype object. When we now send the above request again, the server returns the custom-set status code 555:

image

Thus, we successfully confirmed prototype pollution. We can utilize this technique universally as it does not require any reflection of user input.

Parameter Limiting

The second technique requires that the web application contains an endpoint that reflects GET parameters in any way. In our simple example below, the response body reflects the GET parameters in a JSON object:

image

We can manipulate the number of GET parameters returned by the web application by polluting the parameterLimit property of the Object.prototype object using a payload similar to the following:

{
	"__proto__":{
		"parameterLimit":1
	}
}

When we send the above request again, the web application responds with only the first GET parameter since we limited the number of parameters to one. Thus, all parameters after the first one are ignored:

image

Therefore, we successfully confirmed prototype pollution. We can only utilize this technique if the target web application provides an endpoint that reflects GET parameters.

Content-Type

Our last example requires the reflection of a JSON object. We can force the web application to accept other encodings without breaking the web application. We will use the UTF-7 encoding for this since it does not break the web application's default UTF-8 encoding. First, we need to encode a test string in UTF-7, which we can do using iconv:

[!bash!]$ echo -n 'HelloWorld!!!' | iconv -f UTF-8 -t UTF-7

HelloWorld+ACEAIQAh-

If we send the test string to the web application, it is reflected as-is. In particular, it was not UTF-7 decoded:

image

We can manipulate the value of the Content-Type Header used by the web application by polluting the content-type property of the Object.prototype object using a payload similar to the following:

{
	"__proto__":{
		"content-type":"application/json; charset=utf-7"
	}
}

When we now send the above request again, the web application accepts the UTF-7 encoding as well, such that the test string is decoded to display the exclamation marks in the response:

image

Thus, we successfully confirmed prototype pollution. We can only utilize this technique if the target web application provides an endpoint that reflects JSON input.

For more details on black-box detection of prototype pollution without breaking the web application, take a look at this paper.


Prevention & Patching

There are multiple ways of tackling prototype pollution vulnerabilities.

The most obvious is sanitizing keys to ensure an attacker cannot inject keys referencing the prototype. However, while such an approach is simple in theory, implementing such a sanitizer is no easy task. As we have seen in previous sections, blocking the obvious key __proto__ is insufficient to prevent prototype pollution entirely. There are other ways of obtaining a reference to an object's prototype using the keys constructor and prototype. Thus, a sanitizer should block at least these three keys. However, a more secure approach would be implementing a whitelist approach that consists of a list of explicitly whitelisted keys. These keys need to be chosen carefully for the corresponding context and may even help to prevent further vulnerabilities such as Mass Assignment.

Another way to prevent prototype pollution is by freezing an object, meaning it cannot be modified. This can be done using the Object.freeze() function. If we call the function on the global Object.prototype object that all objects inherit from, any modifications to it are prevented. As an example, consider the following steps:

image

As we can see, the property module.polluted is undefined. That is because we froze the Object.prototype object using the Object.freeze function. Therefore, we prevented prototype pollution by disallowing the polluted property from being set.

However, this is not a universal fix since freezing the Object.prototype property alone may be insufficient. Recall the prototype pollution vulnerability we exploited to gain remote code execution in a previous sections. In that case, we polluted a property in the User.prototype object and did not modify the Object.prototype object. Therefore, in order to prevent that prototype pollution vulnerability, the User.prototype object needs to be frozen.

Lastly, we can also manipulate inheritance to set the prototype to null. This can be achieved using Object.create(null) to create the object, which sets the prototype of the newly created object to null. Thus, there are no inherited properties and no possibility of prototype pollution. However, since there is no prototype, the object does not contain properties like toString() and other useful properties provided by the global Object.prototype object. It only contains properties explicitly added to the object. While this can prevent prototype pollution vulnerabilities, it is probably impractical in many use cases.

Prototype pollution vulnerabilities arise when recursively manipulating an object's properties from user input, a functionality we should import from available libraries. As such, patching prototype pollution vulnerabilities is often as simple as using secure libraries and keeping them updated. An additional line of defense is provided by packages like nopp which ensure some of the defenses discussed are implemented.

Previous

+10 Streak pts

Next