We recommend patching any prototype vulnerabilities that you identify on your websites, regardless of whether they are coupled with exploitable gadgets. Even if you’re sure you haven’t missed anything, there’s no guarantee that future updates of your own code or the libraries you use won’t introduce new gadgets and pave the way for viable exploits.
In this section, we provide general advice on some of the steps you can take to protect your own websites from the threats we’ve covered in our labs. We’ll also cover some common pitfalls to avoid.
Clean property key
One of the more obvious ways to prevent prototype pollution vulnerabilities is to clean property keys before merging them with existing objects. This way you can prevent an attacker from stealing keys such as __proto__
pointing to the prototype of the object.
Using an allow list of allowed keys is the most effective way to do this. However, since this is not feasible in many cases, it is common to use a blacklist instead, which removes potentially dangerous strings from user input.
While this is a quick solution to implement, a truly robust blocklist is inherently difficult, as demonstrated by sites that successfully block __proto__
, but do not take into account that an attacker has polluted an object’s prototype via its constructor. For this reason, we only recommend this as a temporary solution and not as a long-term solution.
Weak implementations can also be circumvented. A common mistake is not to recursively clean up the input string. For example, consider the following URL:
vulnerable-website.com/?__pro__proto__to__.gadget=payload
If the disinfection process only removes the cord __proto__
Without repeating this process more than once, this would result in the following URL, which is a potentially valid prototype pollution source:
vulnerable-website.com/?__proto__.gadget=payload
Prevent changes to global prototypes
A more robust approach to avoiding prototype pollution vulnerabilities is to prevent global prototypes from being modified in the first place.
Citing the Object.freeze()
method on an object ensures that its properties and their values can no longer be modified and no new properties can be added. Since prototypes themselves are just objects, you can use this method to proactively truncate potential sources:
Object.freeze(Object.prototype);
That Object.seal()
The method is similar, but still allows changing the values of existing properties. This can be a good compromise if you can’t use it Object.freeze()
for any reason.
Prevent an object from inheriting properties
While you can use Object.freeze()
To block potential sources of prototype pollution, you can also take steps to eliminate gadgets. Even if an attacker identifies a prototypical pollution vulnerability, it is unlikely to be exploitable in this way.
By default, all objects inherit from the global Object.prototype
either directly or indirectly through the prototype chain. However, you can also set the prototype of an object manually by creating it using Object.create()
Method. This not only allows you to assign any object as a prototype of the new object, but also to create the object with one null
Prototype ensuring it doesn’t inherit any properties at all.
let myObject = Object.create(null);
Object.getPrototypeOf(myObject); // null
Use safer alternatives whenever possible
Another robust protection against contamination from prototypes is to use objects that provide built-in protection. For example, if you are defining an option object, you could use a Map
instead of this. Although a card can still inherit malicious traits, they have one built in get()
Method that returns only properties defined directly on the card itself:
Object.prototype.evil="polluted";
let options = new Map();
options.set('transport_url', 'https://normal-website.com');
options.evil; // 'polluted'
options.get('evil'); // undefined
options.get('transport_url'); // 'https://normal-website.com'
A Set
is another alternative if you are only storing values key:value
couples. Just like maps, sets provide built-in methods that only return properties defined directly on the object itself:
Object.prototype.evil="polluted";
let options = new Set();
options.add('safe');
options.evil; // 'polluted';
option.has('evil'); // false
options.has('safe'); // true