Ataque a Node.js mediante manipulaciones de prototipos de objetos JavaScript

Los investigadores del Centro Helmholtz de Seguridad Informática (CISPA) y del Instituto Tecnológico Real (Suecia) han analizado la aplicabilidad de la técnica de contaminación del prototipo de objetos JavaScript para llevar a cabo ataques en la plataforma Node.js y en aplicaciones populares basadas en ella, lo que puede resultar en la ejecución de código.

El método de contaminación del prototipo aprovecha una característica del lenguaje JavaScript que permite agregar nuevas propiedades al prototipo raíz de cualquier objeto. En las aplicaciones pueden existir bloques de código (gadgets) cuyo funcionamiento se ve afectado por una propiedad insertada, por ejemplo, en el código puede haber una construcción como ‘const cmd = options.cmd || "/bin/sh"’, cuya lógica de funcionamiento se modificaría si un atacante logra insertar la propiedad ‘cmd’ en el prototipo raíz.

Para que un ataque tenga éxito, es necesario que los datos externos en la aplicación puedan ser usados para crear una nueva propiedad en el prototipo raíz del objeto, así como que durante la ejecución exista un gadget que dependa de la propiedad modificada. La modificación del prototipo se realiza a través del manejo en Node.js de las propiedades «__proto__» y «constructor». La propiedad «__proto__» devuelve el prototipo de la clase del objeto, mientras que la propiedad «constructor» devuelve la función utilizada para crear el objeto.

Si en el código de la aplicación se encuentra la asignación «obj[a][b] = value» y los valores se establecen a partir de datos externos, un atacante puede establecer «a» en el valor «__proto__» y lograr que su propiedad con el nombre «b» y el valor «value» se instale en el prototipo raíz del objeto (obj.__proto__.b = value;), entendiéndose que la propiedad establecida en el prototipo será visible en todos los objetos. De manera similar, si en el código hay expresiones como «obj[a][b][c] = value», al establecer «a» en el valor «constructor» y «b» en «prototype», se puede definir una nueva propiedad con el nombre «c» y el valor «value» en todos los objetos existentes.

Ejemplo de modificación del prototipo: const o1 = {}; const o2 = new Object(); o1.__proto__.x = 42; // creamos en el prototipo raíz la propiedad «x» console.log(o2.x); // accedemos a la propiedad «x» desde otro objeto // obtenemos 42, ya que a través del objeto o1 se modificó el prototipo raíz, utilizado también en el objeto o2.

Ejemplo de código vulnerable: function entryPoint(arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; return p; }

Si los argumentos de la función entryPoint se generan a partir de datos de entrada, un atacante puede pasar el valor «__proto__» a arg1 y crear una propiedad con cualquier nombre en el prototipo raíz. Si se pasa a arg2 el valor «toString» y a arg3 el valor 1, se puede definir la propiedad «toString» (Object.prototype.toString=1) y provocar un fallo en la aplicación durante la llamada a la función toString().

Como ejemplo de situaciones que pueden llevar a la ejecución de código del atacante, se presentan la creación de propiedades como «main», «shell», «exports», «contextExtensions» y «env». Por ejemplo, un atacante puede crear en el prototipo raíz del objeto una propiedad «main», asignándole la ruta a su script (Object.prototype.main = «.\/..\/..\/pwned.js») y dicha propiedad será llamada en el momento de ejecutar el código de la instrucción require(«my-package»), si el paquete importado no define explícitamente la propiedad «main» en package.json (si la propiedad no está definida, se obtendrá del prototipo raíz). De manera similar, se pueden sustituir las propiedades «shell», «exports» y «env»: let rootProto = Object.prototype; rootProto[«exports»] = {«.»:«.\/changelog.js»}; rootProto[«1»] = «\/path\/to\/npm\/scripts\/»; \/\/ llamada desencadenada require («.\/target.js»); Object.prototype.main = «\/path\/to\/npm\/scripts\/changelog.js»; Object.prototype.shell = «node»; Object.prototype.env = {}; Object.prototype.env.NODE_OPTIONS = «—inspect-brk=0.0.0.0:1337»; \/\/ llamada desencadenada require («bytes»);

Investigadores analizaron 10 mil paquetes de NPM con el mayor número de dependencias y encontraron que 1958 de ellos no tienen la propiedad main en package.json, 4420 utilizan rutas relativas en la expresión require, y 355 utilizan directamente la API para sustituir comandos.

Como un ejemplo funcional, se puede mencionar un exploit para atacar el backend de Parse Server, que redefine la propiedad evalFunctions. Para facilitar la identificación de vulnerabilidades como estas, se ha desarrollado un conjunto de herramientas que combina métodos de análisis estático y dinámico. Durante las pruebas de Node.js se identificaron 11 gadgets que pueden utilizarse para llevar a cabo ataques que resultan en la ejecución de código del atacante. Además de Parse Server, también se identificaron dos vulnerabilidades explotables en NPM CLI.

Fuente: opennet.ru

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster