Como evitar vulnerabilidades de atribuição em massa no Node.js
28 de março de 2023
0 minutos de leituraA atribuição em massa é uma vulnerabilidade que permite aos invasores explorar padrões previsíveis de registros e executar ações não autorizadas. Ela geralmente ocorre quando as propriedades não são filtradas ao vincular dados fornecidos pelo cliente a modelos de dados. Vulnerabilidades desse tipo permitem que um invasor crie objetos adicionais nos payloads de requisições POST e modifique propriedades que deveriam ser imutáveis.
Node.js é um ambiente de servidor de código aberto que pode ser usado em diferentes plataformas para desenvolver aplicações web. Ele permite que desenvolvedores criem aplicações poderosas e escaláveis com rapidez. Em sua essência, o Node.js é uma plataforma testada em campo e pronta para produção — mas os desafios de segurança das aplicações também envolvem a segurança da cadeia de suprimentos, associada a pacotes de terceiros instalados para criá-las. Às vezes, uma aplicação Node.js pode usar milhares de pacotes npm de código aberto do registro npm, ficando exposta a diversos vetores de ataque.
Vulnerabilidades de atribuição em massa são comuns em aplicações Node.js que enviam payloads ao banco de dados em requisições POST. Um invasor pode explorar essas vulnerabilidades para orquestrar ataques de injeção de SQL ou outros ataques direcionados aos dados armazenados nos bancos de dados. Também pode usar uma vulnerabilidade de atribuição em massa para assumir o controle total de um sistema ou roubar dados confidenciais. Por isso, é essencial se proteger contra esse tipo de ataque.
Este artigo demonstra uma vulnerabilidade de atribuição em massa em um projeto Node.js, como um invasor pode explorá-la e como proteger uma aplicação web.
Como eliminar vulnerabilidades de atribuição em massa no Node.js
Este exemplo usa Mongoose, uma biblioteca de mapeamento de objetos para Node.js e MongoDB (ODM). Ela coordena os objetos no código e no MongoDB, valida o esquema e ajuda a gerenciar as relações entre os dados.
Com base nas informações acima, para proteger uma aplicação Node.js com MongoDB contra atribuição em massa, você precisa fazer isso no Mongoose. Veja abaixo um exemplo de exploração de uma vulnerabilidade de atribuição em massa em uma aplicação Node.js.
Pré-requisitos
Para acompanhar este projeto, você precisará de:
Node.js instalado
Acesso a um banco de dados MongoDB e uma string de conexão válida. Este tutorial usa um ambiente na nuvem do MongoDB Atlas, mas você também pode configurar um servidor MongoDB local e atualizar a string de conexão conforme necessário.
Atribuição em massa no Node.js
Para começar este projeto, abra o terminal e execute este comando:
Em seguida, instale as dependências:
Para lidar com os dados do formulário acima, precisamos criar um arquivo newUser.js na pasta models para gerenciar o esquema do usuário.
Este modelo reúne todos os campos do formulário de cadastro. O campo extra isAdmin, do tipo Boolean, define a função do usuário. Se você definir esse campo como true, criará um usuário com privilégios de administrador; se definir como false, criará um usuário sem esses privilégios.
Crie um novo diretório chamado routes e adicione o arquivo routes.js para gerenciar os dados do usuário:
O código acima captura os dados do formulário e, em seguida, os estrutura com o modelo newUSer.
Este código tem duas falhas que introduzem uma vulnerabilidade de atribuição em massa. Primeiro, ele usa um nome e um tipo comuns para um campo confidencial: isAdmin. Segundo, o modelo de usuário contém um campo confidencial que não é usado. Para explorar a vulnerabilidade, vamos iniciar um servidor.
Crie um arquivo chamado server.js na pasta raiz do projeto e adicione o código a seguir, substituindo <APP_ID> pelo valor da sua string de conexão:
Inicie o servidor executando node server.js.
Um agente malicioso pode explorar essa vulnerabilidade criando uma requisição curl POST que contenha o campo isAdmin definido como true, conforme mostrado abaixo:
A requisição acima cria um usuário, Mike, com privilégios de administrador. Esse usuário pode usar suas credenciais para entrar no sistema e fazer tudo o que um administrador pode fazer.
Como se defender contra a atribuição em massa
Podemos fazer três ajustes para aumentar a proteção da aplicação contra ataques de atribuição em massa.
Crie modelos enxutos
Modelos enxutos incluem apenas os dados que se espera que o usuário forneça e excluem campos confidenciais.
No exemplo acima, a primeira e mais importante medida é remover o campo confidencial do modelo newUser, como mostrado abaixo:
Assim, se os invasores conseguirem acessar o código-fonte, não poderão ver o campo confidencial nem usá-lo para orquestrar um ataque. Evite também usar termos previsíveis no banco de dados para definir a função do usuário — como isAdmin, admin e role —, pois um invasor ainda pode adivinhá-los e lançar um ataque.
Valide o esquema dos dados de entrada do usuário
Validação com underscore
Outra forma de proteger sua aplicação é especificar e limitar os campos que uma requisição POST pode processar. Essa técnica é conhecida como lista de permissões e usa a biblioteca underscore. Primeiro, instale a biblioteca:
Acesse a pasta routes e substitua o código de route.js por este:
No código acima, usamos a função pick para especificar as variáveis extraídas de uma requisição POST. Isso impede que um invasor use o campo confidencial isAdmin.
O desafio de usar underscore é que a aplicação precisa processar os dados para saber se são válidos. Em outras palavras, os erros não são detectados com antecedência suficiente.
Validação com Zod
Para validar esquemas com rigor, use Zod. Ele valida o esquema para garantir que os dados sigam à risca a estrutura, o padrão e o tipo de dado especificados. O Zod identifica dados incompletos ou incorretos com antecedência, evitando erros na aplicação. Além disso, você pode criar facilmente uma mensagem de erro que oriente o usuário sobre os dados que causaram o erro e o tipo de erro.
O trecho de código demonstra como o Zod funciona. Observe que o trecho a seguir está em TypeScript e exige algumas configurações adicionais.
Se o usuário tentar inserir dados que não correspondam ao esquema, receberá um erro invalid_type_error. Se deixar um campo vazio, receberá um erro required_error.
Mais exemplos de vulnerabilidades de atribuição em massa em ORMs
Vamos ver mais três exemplos de vulnerabilidades de atribuição em massa em soluções ORM comuns.
Vulnerabilidade de atribuição em massa em código Sequelize
Veja um exemplo de código Sequelize vulnerável à atribuição em massa. O código define um modelo de usuário, cria um usuário e salva os dados dele no banco de dados:
Ao criar um novo usuário, o controlador chama o modelo acima, como mostrado a seguir:
O código acima é vulnerável porque expõe o campo confidencial is_verified. Um invasor pode criar uma requisição POST com o campo is_verified definido como true e, assim, ignorar a etapa de verificação.
Podemos eliminar a vulnerabilidade de atribuição em massa neste código removendo o campo is_verified ao criar o usuário. O código seguro seria assim:
Isso limita as variáveis aceitas na requisição POST a nome de usuário, e-mail e senha.
Vulnerabilidade de atribuição em massa em código Prisma
Abaixo está um exemplo de código Prisma vulnerável à atribuição em massa, que expõe o campo confidencial Role.
Para adicionar um usuário, criamos uma rota add_user.
Esta requisição POST espera um objeto de usuário no corpo. O código acima é vulnerável a ataques de atribuição em massa porque um hacker pode definir o campo role como ADMIN para obter privilégios de administrador.
Podemos eliminar a vulnerabilidade no código acima excluindo do modelo o campo confidencial Role.
Vulnerabilidade de atribuição em massa em código MySQL
Veja um exemplo de código MySQL vulnerável à atribuição em massa que expõe o campo confidencial isAdmin:
Este código adiciona o usuário definido acima:
O código acima tem uma vulnerabilidade de atribuição em massa porque expõe um campo confidencial — isAdmin — e permite incluí-lo em uma requisição POST. Um invasor pode criar uma requisição POST que defina o campo isAdmin como true, criando um usuário administrador.
Podemos corrigir essa vulnerabilidade excluindo o campo confidencial ao criar o objeto user. Veja o código seguro:
O valor padrão false do campo isAdmin pode ser definido alterando a tabela com o seguinte comando:
Mantenha seu código seguro no Node.js
O Node.js é seguro, mas às vezes os pacotes necessários para criar uma aplicação web não são. Em aplicações Node.js que enviam requisições a um banco de dados, a vulnerabilidade de atribuição em massa é um dos vetores de ataque mais comuns. Ela permite que usuários maliciosos realizem ataques de injeção de SQL, que envolvem o envio de requisições maliciosas a um banco de dados.
No entanto, é fácil evitar a atribuição em massa no Node.js higienizando os dados de entrada. Você pode fazer isso deixando de expor campos confidenciais, limitando os campos aceitos ou impedindo a edição de determinados campos.
A falta de uma validação rigorosa dos dados de entrada do usuário expõe o sistema a ataques como poluição de protótipo. Esses ataques podem contornar mecanismos de validação fracos, como o underscore. Por isso, use sempre soluções robustas, como o Zod.
Confira o exemplo completo do código-fonte em nosso repositório do GitHub e faça seus próprios testes de segurança locais para ver como evitar vulnerabilidades de atribuição em massa no Node.js.
Siga as 10 principais práticas recomendadas de segurança para Node.js da Snyk para otimizar a segurança da sua aplicação Node.js.
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.
