Os riscos de segurança de um sandbox JavaScript com o módulo VM do Node.js
22 de fevereiro de 2023
0 minutos de leituraVocê recebeu a tarefa de criar um produto que precisa executar JavaScript dinâmico fornecido por usuários finais? Talvez você ache que usar o módulo VM do Node.js seja uma forma viável de criar um sandbox JavaScript. Neste artigo, vamos entender por que essa abordagem está longe de ser recomendada e quais são as implicações de segurança.
De vez em quando, surge um projeto que desafia o desenvolvimento de back-end rudimentar e rotineiro. APIs? Filas de mensagens? Processamento pesado e requisitos computacionais? Nada disso. Veja uma história do backlog para você considerar:
O caso de uso é um tanto ambíguo, pois se trata de executar código não confiável fornecido por usuários. Mas há exemplos reais em que isso pode ser necessário, como:
O produto replit permite programar na nuvem e executar código em um IDE personalizado
Várias plataformas de prática de programação e entrevistas, como as de “leet code”, permitem escrever código personalizado, por exemplo, para implementar um algoritmo ou escrever testes para ele.
Conheça o módulo VM do Node.js.
O módulo VM do Node.js
O módulo principal node:vm permite que desenvolvedores compilem e executem código fornecido dinamicamente em contextos da máquina virtual V8. A princípio, isso pode lembrar a função JavaScript eval, que é um risco de segurança conhecido. Mas será que o módulo VM do Node.js é mais seguro? Afinal, ele se chama “máquina virtual”.
Vamos ver um exemplo prático:
No trecho de código acima, a entrada fornecida pelo usuário com código JavaScript personalizado (representada pela variável userInputCustomJavaScriptCode) é executada especificamente no contexto definido pelas variáveis vm.runInContext(). Esse trecho é executado com sucesso e gera os seguintes resultados:
No entanto, atacantes podem tentar abusar desse tipo de código JavaScript dinâmico manipulando outras variáveis além das que foram definidas originalmente. Neste exemplo hipotético em Node.js, há uma variável chamada productExpirationDays que define o prazo de expiração para o usuário. E se um atacante alterasse o código JavaScript dinâmico para aumentar esse prazo?
Se substituirmos o exemplo de entrada do usuário anterior por este — que tenta aumentar o prazo de expiração —, veremos o seguinte erro:
O erro ocorre porque não há nenhuma variável productExpirationDays definida ou existente no escopo do contexto da máquina virtual em que o código JavaScript dinâmico é executado.
No fim das contas, parece que encontramos uma forma de executar código JavaScript dinamicamente com segurança em um sandbox JavaScript isolado.
Um sandbox JavaScript inseguro
O exemplo de código que usa createContext() e vm.runInContext() do módulo VM do Node.js era simplista demais. Infelizmente, na prática, entradas maliciosas de usuários costumam usar métodos mais inteligentes, criativos e eficazes para escapar do sandbox JavaScript.
Vamos ver como um atacante pode fornecer código inseguro capaz de causar uma negação de serviço em um aplicativo em execução. Considere o seguinte script em Node.js:
No exemplo de código acima, o usuário adicionou um loop infinito, while(true) {}, à entrada. Embora o código não altere outras variáveis do aplicativo, ele provoca um ataque de negação de serviço.
Os riscos de um sandbox JavaScript inseguro também incluem a execução remota de código. Com this.constructor.constructor, podemos nos referir ao objeto JavaScript Function. Uma função JavaScript aceita código como string e depois o executa. Nosso ataque explora esse comportamento e, junto com uma função de execução imediata, conhecida como IIFE, imprime as variáveis de ambiente do processo Node.js em execução:
A execução do trecho de código acima imprime o conteúdo de process.env, que lista todas as variáveis de ambiente.
A esta altura, você já deve perceber o impacto que a execução remota de código pode ter quando se permite executar código não confiável no módulo VM do Node.js. Se os usuários puderem executar código personalizado, terão acesso total ao ambiente de execução do servidor Node.js e poderão iniciar processos, acessar o sistema de arquivos e muito mais.
Resumo
As consequências de um sandbox JavaScript inseguro em um ambiente Node.js são graves e podem paralisar um aplicativo inteiro. Vimos como um atacante pode abusar do sandbox JavaScript e fornecer entradas maliciosas que degradam um aplicativo Node.js, causando uma vulnerabilidade de negação de serviço. Mas a superfície de ataque não para por aí. Também vimos como pode ocorrer a execução remota de código, permitindo que atacantes executem código personalizado em ambientes de servidor Node.js e coloquem toda a plataforma do aplicativo em risco.
Na verdade, a brecha de segurança criada por um sandbox JavaScript que usa o módulo VM do Node.js é apenas o começo de uma violação de dados mais ampla. Quando um único servidor de aplicativo Node.js é comprometido, informações confidenciais, como credenciais de acesso a bancos de dados ou serviços de nuvem, podem ser reveladas e expostas. Isso pode permitir que um atacante amplie a exploração e se movimente lateralmente pela rede implantada.
Portanto, a melhor prática é não confiar no módulo VM do Node.js como um sandbox seguro para executar código JavaScript não confiável. A documentação da API do Node.js deixa isso bem claro: “O módulo node:vm não é um mecanismo de segurança. Não o use para executar código não confiável.”
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.
