In JS, Prototype functions similarly to Classes
Difference Between Class and Prototype in JavaScript
Classes
- Act like blueprints for creating objects.
- Ensure that all objects created from the class share the same properties and methods.
- Provide a structured and easy-to-understand way of object creation.
Prototypes
- Allow objects to inherit behaviors by linking them to a prototype object.
- More dynamic and flexible than classes, as methods can be added or modified at runtime.
- Objects are connected via the prototype chain, enabling inheritance without explicitly defining classes.
Analogy
- Class: Like a car blueprint; all cars built from it have the same structure.
- Prototype: Like modifying a basic car model by adding custom features directly.
Example of Prototype Pollution
Let's assume, we have a basic prototype for Person
with an introduce
method. The attacker aims to manipulate the behaviour of the introduce
method across all instances by altering the prototype.
// Base Prototype for Persons
let personPrototype = {
introduce: function() {
return `Hi, I'm ${this.name}.`;
}
};
// Person Constructor Function
function Person(name) {
let person = Object.create(personPrototype);
person.name = name;
return person;
}
// Creating an instance
let ben = Person('Ben');
When we create a new object, ben
, and call the introduce
method, it displays Hi, I'm Ben
, as shown in the following figure.
In JavaScript, the __proto__
property is a common way to access the prototype of an object, essentially pointing to the object from which it inherits properties and methods.
// Attacker's Payload
ben.__proto__.introduce=function(){console.log("You've been hacked, I'm Bob");}
console.log(ben.introduce());
Breakdown of whole process:
-
Prototype Definition: The Person prototype (personPrototype) is initially defined with a harmless
introduce
method, introducing the person. -
Object Instantiation: An instance of Person is created with the name
'Ben' (let ben = Person('Ben');)
. -
Prototype Pollution Attack: The attacker injects a malicious payload into the prototype's
introduce
method, changing its behaviour to display a harmful message. -
Impact on Existing Instances: As a result, even the existing instance (
ben
) is affected, and callingben.introduce()
now outputs the attacker's injected message.
This example shows how an attacker can alter the behaviour of shared methods across objects, potentially causing security risks. Preventing prototype pollution involves carefully validating input data and avoiding directly modifying prototypes with untrusted content.
Exploitation - XSS
Overview
The submit review feature allows users to submit a review for a friend, which is then saved in the database. Below is a breakdown of the client-side and server-side code along with potential security risks.
Client-Side Code
The review form:
<form action="/submit-friend-review" method="post">
<h2>Submit a Review</h2>
<input type="hidden" name="friendId" value="1">
<div class="form-group">
<textarea class="form-control" name="reviewContent" placeholder="Write your review here" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary">Submit Review</button>
</form>
- The form submits a
POST
request to/submit-friend-review
. - It includes a hidden
friendId
field, which can be manipulated by an attacker. - The review content is sent as input.
Server-Side Code
app.post("/submit-friend-review", (req, res) => {
if (!req.session.user) {
return res.redirect("/signin");
}
const { friendId, reviewContent } = req.body;
const friend = friends.find((f) => f.id === parseInt(friendId));
if (!friend) {
return res.status(404).send("Friend not found");
}
try {
const input = JSON.parse(reviewContent);
_.set(friend, input.path, input.value);
} catch (e) {}
res.redirect(`/friend/${friendId}`);
});
Key Actions:
- Session Validation – Ensures the user is logged in.
-
Friend Validation – Checks if the
friendId
exists. -
Review Insertion – Uses
_.set(friend, input.path, input.value)
from Lodash to insert the review. -
Potential Vulnerability – Since Lodash’s
_.set
function dynamically sets properties, malicious input can modify unintended properties.
Potential Exploit: Prototype Pollution
The attacker can deliver a paylaod such as {"path": "reviews[0].content", "value": "<script>alert('Hacked')</script>"}
that alerts whenever someone visits the page.
Other parameter such as isAdmin
and isloggedIn
are potential attack vectors too.
Security Risks
- Prototype Pollution – Allows modifying global object properties.
- Privilege Escalation – Attackers could gain unauthorized access.
- Data Tampering – Malicious users could inject unexpected properties.
Next Steps
- Test the vulnerability by adding a simple review and checking if it's successfully inserted.
- Explore further exploits like XSS by injecting script tags in the review content.
-
Mitigation: Implement strict input validation and avoid direct JSON parsing with Lodash’s
_.set()
.
Exploitation - Property Injection
1. Object Recursive Merge (Merging Objects Safely)
Imagine you have a settings page where users can update their preferences (like changing themes or notification settings). The application might use a function to merge the new settings with the existing ones.
Here's a vulnerable merge function:
// Vulnerable recursive merge function
function recursiveMerge(target, source) {
for (let key in source) {
if (source[key] instanceof Object) {
if (!target[key]) target[key] = {};
recursiveMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
- This function takes two objects:
-
target
(existing settings) -
source
(new settings from user input)
-
- It recursively copies each property from
source
intotarget
.
✅ If used correctly, this function updates user settings normally.
❌ If misused, an attacker can inject a property into the prototype, affecting all objects.
How an Attacker Exploits This
An attacker sends this input:
jsonCopyEdit{ "__proto__": { "isAdmin": true }}
- Since the function does not validate inputs, it merges this into the global settings.
- Now, all objects in the app inherit
isAdmin: true
, potentially giving unauthorized admin access.
2. Object Cloning (Copying Objects Safely)
Another risky function is object cloning, where one object is copied to create another.
✅ Normal Use: When creating a new user profile, the system might copy default settings.
❌ Attack Risk: If cloning does not filter out special properties like __proto__
, constructor
, etc., an attacker can inject malicious properties.
For example, if the app clones an object like this:
jsonCopyEdit{ "__proto__": { "hasFreeSubscription": true }}
Now, every cloned user object inherits hasFreeSubscription: true
, allowing free access to premium features.
Example
app.post("/clone-album/:friendId", (req, res) => {
const { friendId } = req.params;
const { selectedAlbum, newAlbumName } = req.body;
const friend = friends.find((f) => f.id === parseInt(friendId));
if (!friend) {
console.log("Friend not found");
return res.status(404).send("Friend not found");
}
const albumToClone = friend.albums.find(
(album) => album.name === selectedAlbum
);
if (albumToClone && newAlbumName) {
let clonedAlbum = { ...albumToClone };
try {
const payload = JSON.parse(newAlbumName);
merge(clonedAlbum, payload);
} catch (e) {
}
function merge(to, from) {
for (let key in from) {
if (typeof to[key] == "object" && typeof from[key] == "object") {
merge(to[key], from[key]);
} else {
to[key] = from[key];
}
}
return to;
}
In the above code, the servers receive a JSON object containing the album's name, copy the album that needs to be copied into another object, and change the name of the newly created copy by calling the merge function.
We know the merge function is an ideal candidate for prototype pollution if it blindly copies all the objects and properties without sanitising based on keys. We can see that the merge function made by the developer lacked any such sanitisation filters. What if we send a request that contains __proto__
with a newProperty
and value as mentioned below:
{"__proto__": {"newProperty": "hacked"}}
The merge function will consider the __proto__
as a property and will call obj.__proto__.newProperty=value
. By doing this, newProperty
is not added directly to the friend object. Instead, it's added to the friend object's prototype.
Exploitation - Denial of Service
Causes of DoS via Prototype Pollution
-
Altering a Critical Function – If an attacker changes
Object.prototype.toString
, any part of the app that relies on it may start behaving incorrectly. -
Unexpected Crashes – Many parts of a complex app call
toString
automatically. If the function is modified, the app might crash or enter an infinite loop, using up system resources. - Breaking Business Logic – Changing built-in functions can cause unexpected errors, stopping the app from working and making the server unresponsive to real users.
Bottom line: Prototype pollution can disrupt services by modifying essential functions, leading to crashes or slowdowns that prevent users from accessing the app. 🚨
Example
Server-side code:
<form action="/clone-album/1" method="post" class="mb-4">
<h2 class="mb-3">Clone Album of Josh</h2>
<div class="form-group">
<label for="selectedAlbum">Select an Album to Clone:</label>
<select class="form-control" name="selectedAlbum" id="selectedAlbum">
<option value="Trip to US">
Trip to US
</option>
</select>
</div>
<div class="form-group">
<label for="newAlbumName">New Album Name:</label>
<input type="text" class="form-control" name="newAlbumName" id="newAlbumName"
placeholder="Enter new album name">
</div>
<button type="submit" class="btn btn-primary">Clone Album</button>
</form>
Example payload:
{"__proto__": {"toString": "Just crash the server"}}
- Let's decode the payload once the
app.js
receives the request, parses the JSON, and assigns thetoString
function value in the__proto__
property of the friend object. - This creates an abrupt behaviour as
toString
is widely used among different objects. When we click on Clone Album, the application crashes, as shown below:
- The
TypeError
we get isObject.prototype.toString.call
is not a function, as we have already overridden that function using Prototype pollution. - You can override several other built-in objects/functions like
toJSON
,valueOf
,constructor
, etc., but the application won't crash in all behaviours. It entirely depends on the function that you are overriding.
Automating Vulnerability Detection Process
Important Scripts for Detecting Prototype Pollution
Several tools and open-source projects help automate the detection of prototype pollution vulnerabilities in JavaScript applications. Below are some key tools available on GitHub:
- NodeJsScan – A static security scanner for Node.js applications that detects various vulnerabilities, including prototype pollution.
- Prototype Pollution Scanner – Scans JavaScript codebases for patterns vulnerable to prototype pollution, helping developers find and fix issues.
- PPFuzz – A fuzzer that automates testing for prototype pollution in web applications by injecting inputs that interact with object properties.
- BlackFan’s Client-Side Detection – Focuses on client-side prototype pollution, demonstrating how it can be exploited for XSS attacks and other browser-based threats.
How Pentesters Can Use These Tools:
Pentesters should analyze how user input influences object properties in JavaScript applications. The key is to check whether inputs are properly sanitized and validated to prevent unauthorized modifications to the prototype chain. 🚨
Mitigation Measures
Prototype pollution allows attackers to manipulate an object's prototype, leading to unexpected behavior and security vulnerabilities. Below are key mitigation measures for pentesters and secure code developers.
For Pentesters
- Fuzzing & Input Manipulation – Test user inputs extensively with different payloads to identify prototype pollution vulnerabilities.
- Context Analysis & Payload Injection – Analyze how user inputs interact with prototype-based structures and inject payloads to test for vulnerabilities.
- CSP Bypass & Payload Injection – Assess Content Security Policy (CSP) restrictions and attempt to bypass them for prototype manipulation.
- Dependency Analysis & Exploitation – Check third-party libraries for outdated or vulnerable dependencies that could introduce prototype pollution risks.
- Static Code Analysis – Use static analysis tools to detect insecure coding patterns during development.
For Secure Code Developers
-
Avoid
__proto__
– UseObject.getPrototypeOf()
instead to prevent direct prototype manipulation. - Use Immutable Objects – Prevent unintended prototype modifications by designing immutable objects.
- Encapsulation – Restrict access to object prototypes by exposing only necessary functionalities.
- Use Safe Defaults – Initialize objects securely without relying on user input for prototype properties.
- Input Sanitization – Validate and sanitize user inputs to prevent prototype pollution attacks.
- Manage Dependencies – Regularly update libraries and frameworks to prevent vulnerabilities.
- Implement Security Headers – Use CSP and other security headers to mitigate the risk of loading malicious scripts.
By combining rigorous testing, secure coding, and ongoing security awareness, both pentesters and developers can effectively mitigate prototype pollution risks. 🚨
Top comments (0)