Prototype Pollution in JavaScript
In 2018, the npm package event-stream
was compromised due to a prototype pollution vulnerability in JavaScript, affecting millions of users.
Let’s understand what prototype pollution is and how we can prevent it.
What is Prototype Pollution?
Prototype pollution is a JavaScript vulnerability that occurs when a user can modify prototype properties. By changing the prototype of an object, a user can impact all other objects that inherit from it.
JavaScript have prototype feature, which enables objects to inherit properties from other objects. If you want to create a common function that is shared by all objects, you can add it to Object.prototype
, and all objects will have access to it.
For a detailed understanding of prototypes, refer to this article.
How Prototype Pollution Happens
Let’s look at an example:
const obj = {
name: "Saurav",
country: "India",
};
Object.prototype.toString = function () {
return "12345";
};console.log("SK@", obj.toString());
In the code snippet above, obj
has a basic structure. When we create an object, it inherits some default properties from Object.prototype
. However, we modify the toString()
method to return "12345"
instead of its original behavior. Now, whenever toString()
is accessed in the script, it returns "12345"
, making the code vulnerable to prototype pollution. This example is harmless, but a real attack can be much more dangerous.
A More Dangerous Example
Consider the following React component:
const user = { name: "John Doe", age: 25 };
return (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
</div>
);
Now, suppose an attacker injects the following malicious code:
Object.prototype.name = "You are hacked";
Object.prototype.age = 1000;
Now, everywhere in the application where user.name
and user.age
are displayed, it will show "You are hacked"
and 1000
, respectively. This kind of attack can occur both on the frontend and backend, making it essential to implement preventive measures.
How to Prevent Prototype Pollution
1. Use Object.create(null)
Creating objects without a prototype ensures they do not inherit any properties, thereby preventing prototype pollution.
const obj = {
name: "Saurav",
country: "India",
};
Object.prototype.toString = function () {
return "12345ty";
};Object.prototype.isAdmin = true;let objWithoutProto = Object.create(null);console.log("SK@", obj.toString()); // Output: 12345ty
console.log("SK@", obj.isAdmin); // Output: trueObject.assign(objWithoutProto, obj);
console.log("SK@", objWithoutProto.isAdmin); // Output: undefined
As you can see, isAdmin
is undefined
, meaning we successfully avoided the prototype pollution attack.
If you do not want to copy an object, you can simply create an empty object without a prototype:
let obj = Object.create(null); // No prototype
2. Use Object.freeze()
We can freeze critical objects to prevent modifications, ensuring no injections are possible.
const obj = {
name: "Saurav",
country: "India",
};
Object.freeze(Object.prototype);
Object.freeze(Object.__proto__);Object.prototype.toString = function () {
return "12345ty";
};console.log("SK@", obj.toString()); // Output: [object Object]
3. Filter User Input
Always sanitize user input and remove any keys that contain __proto__
, constructor
, or prototype
.
By implementing these precautions, we can effectively prevent prototype pollution and keep our applications secure.
I hope this article help you to understand the vulnerability of prototype pollution and how can we prevent this effectively.