Ataque ao Node.js por meio da manipulação de protótipos de objetos JavaScript

Pesquisadores do Centro Helmholtz para Segurança da Informação (CISPA) e do Royal Institute of Technology (Suécia) analisaram a aplicabilidade da técnica de poluição de protótipos JavaScript para criar ataques à plataforma Node.js e aplicativos populares baseados nela, levando à execução de código.

O método poluente de protótipo usa um recurso da linguagem JavaScript que permite adicionar novas propriedades ao protótipo raiz de qualquer objeto. Os aplicativos podem conter blocos de código (gadgets) cuja operação é afetada por uma propriedade substituída; por exemplo, o código pode conter uma construção como 'const cmd = options.cmd || "/bin/sh"', cuja lógica será alterada se o invasor conseguir substituir a propriedade “cmd” no protótipo raiz.

Um ataque bem-sucedido requer que o aplicativo possa usar dados externos para criar uma nova propriedade no protótipo raiz do objeto e que a execução encontre um gadget que dependa da propriedade modificada. A alteração do protótipo é realizada processando as propriedades de serviço “__proto__” e “construtor” em Node.js. A propriedade "__proto__" retorna o protótipo da classe do objeto, e a propriedade "construtor" retorna a função usada para criar o objeto.

Se o código do aplicativo contém a atribuição “obj[a][b] = value” e os valores são definidos a partir de dados externos, um invasor pode definir “a” como o valor “__proto__” e conseguir a instalação de sua própria propriedade com o nome “b” e o valor “valor” no protótipo raiz do objeto (obj.__proto__.b = valor;), e a propriedade definida no protótipo ficará visível em todos os objetos. Da mesma forma, se o código contém expressões como “obj[a][b][c] = valor”, definindo “a” como o valor “construtor” e “b” como “protótipo” em todos os objetos existentes, você pode defina uma nova propriedade com o nome "c" e o valor "valor".

Exemplo de alteração do protótipo: const o1 = {}; const o2 = novo Objeto(); o1.__proto__.x = 42; // cria a propriedade “x” no protótipo raiz console.log (o2.x); // acessa a propriedade “x” de outro objeto // a saída será 42, pois o protótipo raiz foi alterado através do objeto o1, que também é utilizado no objeto o2

Exemplo de código vulnerável: function entryPoint (arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; retornar p; }

Se os argumentos da função entryPoint forem formados a partir de dados de entrada, um invasor poderá passar o valor “__proto__” para arg1 e criar uma propriedade com qualquer nome no protótipo raiz. Se você passar para arg2 o valor "toString" e para arg3 o valor 1, você pode definir a propriedade "toString" (Object.prototype.toString=1) e travar a aplicação durante a chamada para toString().

Exemplos de situações que podem levar à execução do código do invasor incluem a criação das propriedades "main", "shell", "exports", "contextExtensions" e "env". Por exemplo, um invasor pode criar uma propriedade “principal” no protótipo raiz de um objeto, escrevendo nela o caminho para seu script (Object.prototype.main = “./../../pwned.js”) e esta propriedade será chamada no momento da execução no código da construção require("my-package"), se o pacote incluído não definir explicitamente a propriedade "main" em package.json (se a propriedade não estiver definida, será obtido a partir do protótipo raiz). As propriedades “shell”, “exports” e “env” podem ser substituídas de forma semelhante: let rootProto = Object.prototype; rootProto["exportações"] = {".":"./changelog.js"}; rootProto["1"] = "/caminho/para/npm/scripts/"; // aciona a chamada require("./target.js"); Object.prototype.main = "/caminho/para/npm/scripts/changelog.js"; Object.prototype.shell = "nó"; Object.prototype.env = {}; Object.prototype.env.NODE_OPTIONS = "—inspecionar-brk=0.0.0.0:1337"; // aciona a chamada require("bytes");

Os pesquisadores analisaram 10 pacotes NPM com o maior número de dependências e descobriram que 1958 deles não possuem uma propriedade principal em package.json, 4420 usam caminhos relativos em suas instruções require e 355 usam diretamente a API de substituição de comando.

Um exemplo prático é uma exploração para atacar o back-end do Parse Server que substitui a propriedade evalFunctions. Para simplificar a identificação de tais vulnerabilidades, foi desenvolvido um kit de ferramentas que combina métodos de análise estáticos e dinâmicos. Durante os testes do Node.js, foram identificados 11 gadgets que podem ser usados ​​para organizar ataques que levam à execução do código do invasor. Além do Parse Server, duas vulnerabilidades exploráveis ​​também foram identificadas no NPM CLI.

Fonte: opennet.ru

Adicionar um comentário