Ataque a Node.js mediante a manipulación de prototipos de obxectos JavaScript

Investigadores do Centro Helmholtz para a Seguridade da Información (CISPA) e do Instituto Real de Tecnoloxía (Suecia) analizaron a aplicabilidade da técnica de contaminación do prototipo de JavaScript para crear ataques á plataforma Node.js e ás aplicacións populares baseadas nela, levando á execución de código.

O método de contaminación de prototipos utiliza unha característica da linguaxe JavaScript que permite engadir novas propiedades ao prototipo raíz de calquera obxecto. As aplicacións poden conter bloques de código (gadgets) cuxo funcionamento se ve afectado por unha propiedade substituída; por exemplo, o código pode conter unha construción como 'const cmd = options.cmd || "/bin/sh"', cuxa lóxica se cambiará se o atacante consegue substituír a propiedade "cmd" no prototipo raíz.

Un ataque exitoso require que a aplicación poida usar datos externos para crear unha nova propiedade no prototipo raíz do obxecto e que a execución atope un gadget que depende da propiedade modificada. O cambio do prototipo realízase procesando as propiedades do servizo "__proto__" e "construtor" en Node.js. A propiedade "__proto__" devolve o prototipo da clase do obxecto e a propiedade "construtor" devolve a función utilizada para crear o obxecto.

Se o código da aplicación contén a asignación "obj[a][b] = valor" e os valores se definen a partir de datos externos, un atacante pode establecer "a" no valor "__proto__" e conseguir a instalación da súa propia propiedade co nome "b" e o valor "valor" no prototipo raíz do obxecto (obj.__proto__.b = valor;), e a propiedade definida no prototipo estará visible en todos os obxectos. Do mesmo xeito, se o código contén expresións como "obj[a][b][c] = valor", configurando "a" no valor "construtor" e "b" en "prototipo" en todos os obxectos existentes, pode definir unha nova propiedade co nome "c" e o valor "valor".

Exemplo de cambio de prototipo: const o1 = {}; const o2 = new Object(); o1.__proto__.x = 42; // crea a propiedade “x” no prototipo raíz console.log (o2.x); // accede á propiedade “x” desde outro obxecto // a saída será 42, xa que o prototipo raíz cambiouse a través do obxecto o1, que tamén se usa no obxecto o2

Exemplo de código vulnerable: función entryPoint (arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; retorno p; }

Se os argumentos da función entryPoint se forman a partir de datos de entrada, entón un atacante pode pasar o valor "__proto__" a arg1 e crear unha propiedade con calquera nome no prototipo raíz. Se pasas a arg2 o valor "toString" e arg3 o valor 1, podes definir a propiedade "toString" (Object.prototype.toString=1) e bloquear a aplicación durante a chamada a toString().

Exemplos de situacións que poden levar á execución de código do atacante inclúen a creación das propiedades "main", "shell", "exports", "contextExtensions" e "env". Por exemplo, un atacante pode crear unha propiedade "principal" no prototipo raíz dun obxecto, escribindo nel o camiño ao seu script (Object.prototype.main = "./../../pwned.js") e esta propiedade chamarase no momento da execución no código do constructo require("meu-paquete"), se o paquete incluído non define explícitamente a propiedade "principal" en package.json (se a propiedade non está definida, obterase a partir do prototipo raíz). As propiedades "shell", "exports" e "env" pódense substituír de xeito similar: let rootProto = Object.prototype; rootProto["exports"] = {".":"./changelog.js"}; rootProto["1"] = "/ruta/a/npm/scripts/"; // trigger call require("./target.js"); Object.prototype.main = "/path/to/npm/scripts/changelog.js"; Object.prototype.shell = "nodo"; Object.prototype.env = {}; Object.prototype.env.NODE_OPTIONS = "—inspect-brk=0.0.0.0:1337"; // activar a chamada require("bytes");

Os investigadores analizaron 10 paquetes NPM co maior número de dependencias e descubriron que 1958 deles non teñen unha propiedade principal en package.json, 4420 usan camiños relativos nas súas instrucións require e 355 usan directamente a API de substitución de comandos.

Un exemplo de traballo é un exploit para atacar o backend do Parse Server que anula a propiedade evalFunctions. Para simplificar a identificación de tales vulnerabilidades, desenvolveuse un conxunto de ferramentas que combina métodos de análise estático e dinámico. Durante a proba de Node.js, identificáronse 11 gadgets que se poden usar para organizar ataques que levan á execución do código do atacante. Ademais de Parse Server, tamén se identificaron dúas vulnerabilidades explotables na CLI de NPM.

Fonte: opennet.ru

Engadir un comentario