Introduction#

  • Prototype pollution allowd bad actors to manipulate and exploit the inner workings of Javascript applications and enables attackers to gain access to sensitive data and application backend.
  • While this vulnerability is mostly discussed in context of Javascript, the concept can apply to any system that uses similar Prototype based inheritance model.
  • However, Javascript’s widespread use, particularly in web development, and its flexible and dynamic object model make prototype pollution a more prominent and relevant concern in this language.
  • In contrast, class-based inheritance languages like Java, C++ have a different model of inheritance where classes (blueprints of objects) are typically static, and altering a class at a runtime to affect all its instances is not a common practice or straight forward task.

Basics#

  • Classes: Classes are like blueprints that help create multiple objects with similar structures and behaviours.
  • Prototype: Every object linked to prototype object, and these prototypes from a chain reffered as the prototype chain. The prototype serves as template or blueprint for objects. When you create an object using a constructor function or a class, javascript automatically setup a link between the object and its prototype.
  • Difference between class and prototype:
    • With Classes, We will be having a clear, structured way to create objects that share the same properties and methods, making them easy to understand and use.
    • With Prototypes, We can start with simple object and then add behaviours to it by linking it to prototype object that already has those behaviours. Objects created this way are linked though a prototype chain, allowing them to inherit behaviours from other objects. This method is more dynamic and flexibel but can be harder to manage and understand than the structured approach of classes.
    • Prototype object creation: object.create()
  • Common Example:
    • Lets understand how attacker can manipulate the behaviour of the introduce method across all instances by altering prototypes.
    // 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');
    
    // Attakcer's payload
    ben.__proto__.introduce = fuction(){console.log("You have been hacked, I'm bob");}
    console.log(ben.introduce());
    // this not only changes ben's introduce, but also its prototype, and whichever object has this prototype chain link.
    // lets say, if its create josh, whats it introduce name will be?
    let josh = Person('Josh');
    console.log(josh.introduce()); // hope you have got it??
    

Exploitation - Standard approach#

  • As we know, constructor and __proto__ properties present on object stand out as a notable targets for exploitation by threat actors.
  • constructor property points to function, that constructs an object’s prototype.
  • While __proto__, is a reference to the prototype object that the current object directly inherits from.
  • Golden Rule:
    • The concept hinges on an attacker’s ability to influence certain key parameters, such as x and val in expresions similar to Person[x][y] = val. Suppose an attacker assigns __proto__ to x. In that case, the attribute indentified by y is universally set across all objects shareing the class as the object with the value denoted by val.
    • In a more intricate scenario, when an attacker has control over x, y, and val in a structure like Person[x][y][z] = val, assigning x as a constuctor and y as prototype leads to a new property defined by z being established across all objects in the application with the assigned val. This latter approach necessitates a more complex arrangement of object properties, making it less prevalent in practice.

Few importan Fuctions#

  • When identifying the prototype pollution vulnerabilities, penetration testers should focus on commonly used vectors/fuctions susceptible to prototype pollution. A thorough examination of how an application handles object manipulation is crucial. We will understand few important fuctions that an attacker exploit, and then we will practically perform exploitation.
  • Property Definition by Path: FUctions that set object properties based on a given path (like object[a][b][c] = value) can be dengerous if the path components are controlled by user input. These functions should be inspected to ensure they dont inadvertently modify the object’s prototype.
  • Example:
    • Intial object structure
      let friends = [ { id: 1, name: "testuser", age: 25, country: "UK", reviews: [], albums: [{ }], password: "xxx", } ]; 
      _.set(friend, input.path, input.value);
      
    • Input received from user: User wants to add a review for their fried. They provide a payload containing the path where the review should be added (review.content) and the review content (<script>alert(anycontent)</script>). An attacker now updates path to target the prototype
      {"path": "reviews[0].content", "value": "&#60;script&#62;alert('anycontent')&#60;/script&#62;" };
      // we use the _set function from lodash to apply the payload and add the review content to specified path with in friend's profile object.
      
    • Resulting object structure: After executing code, the friends array will be modified to include the user’s review. However, due to lack of proper input validation, the review content provided by user (<script>alert('anycontent')</script>) was directly added to profile object without proper sanitization.
      let friends = [
          {
              id: 1,
              name: "testuser",
              age: 25,
              countrty: "UK",
              review: [
                  "<script>/alert('anycontent')</script>"
              ],
              albums: [{}],
              password: "xxx"
          }
      ]
      
    • Similarly, suppose the attacker wants to insert a malicious property into the friend’s profile. In that case, they provide a payload containing the path where the property should be added (isAdmin) and the value for the malicious property (true).
      const paylaod = { "path": "isAdmin", "value": true}
      
    • AFter executing the code, the friends array will be modified to include the malicious property isAdmin in the friend’s profile object.

Exploitation - Property Injection#

Few Important fuctions#

  • Object Recursive merge: This function involves recursively merging properties from source objects into a target object. An attacker can exploit this functionality if the merge function does not validate its inputs and allows merging properties into the prototype chain. Considering the same example, lets assume following code.

    //vulnerable recursicve merge function
    function recursiveMerge(target, source) {
        for (let key in source) {
            if (source[key] instaceof Object) {
                if (!target[Key]) target[key] = {};
                recursiveMerge(target[key], source[key]);
            } else {
                target[key] = source[key]
            }
        }
    }
    
    // endpoint to update user settings
    app.post('/updateSettings', (req, res) => {
        const userSettings = req.body; // user controlled input
        recursiveMerge(globalUserSettings, userSettings);
        res.send('Settings updated');
    })
    
    • An attacker sends a request with a nested object containing __proto__
    { "__proto__": {"newProperty": "value"}}
    
  • Object Cloning: It is similar functionality that allows deep clone operations to copy properties from the prototype chain to another one inadvertently. Testing should ensure that these functions only clone the user-defined properties of an object and filter special keywords like proto, constructor, etc. A possible use case is that application backend clones objects to creates new user profiles.

  • Example:

    • Lets consider the server side code for specific api, responsible for cloning the albums from profile to other. API accepts profile id in url, and selected album, new album name as in payload.
    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. This means newProperty is not visibly part of the friend’s properties (like name, age, etc.) but is still accessible.

Exploitation - Denial of service#

  • Prototype pollution, a critical vulnerability in Java script applications, can lead to DoS attack, among other severe consequences. THis occurs when an attacker manipulates the prototype of a widely used object, causing the application to behave unexpectedly or crash altogether. In JS, object inherit properties and methods from their prototype, and altering this prototype impacts all objects that share it.
  • For example, if an attacker pollutes the Object.prototype.toString method, every subsequent call to this method by any object will execute the altered behaviour.
  • In a complex application where toString is frequently used, this can lead to unexpectd results, potentially causing the application to malfunction. The toString method is universally used, and automatically invoked in many contexts, especially when an object needs to be converted to string.
  • If the polluted methods leads to inefficient processing or an infinite loop, it can exhaust system resources, effectively causing a DoS condition.
  • Morever, prototype pollution can also interfere with the applicaitons bussiness logic. Altering essential methods or properties might trigger unhandled exceptions or errors, leading to the termination of processes or services. This could render the server unresponsive in web applications, denying service to legimate users.

Automating the process#

  • Identifying prototype pollution is a tricky problem in any language particularly in javascript, because of the way JavaScript lets one object share its features with another. Detecting this problem automatically with software tools is really hard because it’s not straight forward like other common website security problems. Each website or web application is different, and figuring out where the prototype pollution might be requires someone to look closely at website’s code, understand how it works, and see where mistakes might made.
  • Unlike other security issues, that can be found by looking for specific patterns or signs, finding prototype pollution needs a dep into the website’s code by a pentester/developer.
  • It’s all about understanding the complex ways objects in JS affect each other and spotting where something might go wrong. Security tools can help point out possible issues, but they can’t catch everything. That’s why having people who know how to read and analyze code is carefully is so important.

Few important scripts#

  • NodeJsScan: its a static security code scanner for Node.js applications. It includes checks for various security vulnerabilities, including prototype pollution. Integrating NodeJsScan into your development workflow can help automatically identify potential security issues in your codebase.
  • Prototype Pollution scanner: Its tool designed to scan JS code for prototype pollution vulnerabilities, it can be used to analyse codebasese for patterns that are susceptible to pollution, helping devs to identify and address potential security issues.
  • PPFuzz: Its another fuzzer designed to automate the process of detecting prototype pollution vulnerabilities by fuzzing input vectors that might interact with object properties.
  • Client-side detection by BlackFan: its focused on identifying this vulnerabilities in client-side js code. It includes examples of how prototype pollution can be exploited in browsers to perform XSS attacks and other malicious activities.

Mitigation Measures#

For pentesters:#

  • Input fuzzing and manipulation: interact with inputs, fuzz them with variety of payloads and look for suspects.
  • Context Analysis and Payload injections: Analyze Applicaiton code, understand user inputs.
  • CSP Bypass and Payload injection: Evaluate the effectiveness of security headers such as CSP in mitigating prototype pollution. Attempt to bypass CSP restrictions and inject payloads to manipulates prototypes.
  • Dependency Analyis and Exploitation: Conduct a thorough analysis of third party libraries and dependencies used by application. Identify outdated or vulnerable libraries that may introduce prototype pollution.
  • Static code analyis: Use static code analysis tools to identify potential prototype pollution vulnerabilities during the development phase.

Secure code developers:#

  • Avoid using proto
  • Desing objects with Immutable objects as possible, prevents modifications to the prototype
  • Encapsulate objects and their functionalities, exposing only interfaces, preventing unauthorized access to prototypes
  • When creating objects, establish safe default values and avoid relying on user inputs to set prototype properties
  • Sanitize and validate inputs thoroughly
  • Regularly update and monitor dependencies, chose ony well-maintained libraries and frameworks, stay informed about security updates
  • Implements security headers such as Content Security Polity (CSP) to control the sources from which resources can be loaded