Este post no blog oferece uma visão geral dos ataques de Confusão de Dependência e explica detalhadamente como eles podem ser explorados na prática, com exemplos usando pacotes NPM e dicas para prevenir a ocorrência dessas vulnerabilidades.
Autor: Lucas Morais
Introdução
No desenvolvimento de software, dependências são os softwares necessários em um programa para fazê-lo funcionar. Normalmente, essas peças de software realizam uma tarefa comum e necessária que é frequentemente desenvolvida por comunidades inteiras ou até mesmo dentro de empresas.
Esses códigos essenciais para um sistema são centralizados e podem ser importados pelos programadores no projeto. Gerenciar os pacotes (como as dependências também são conhecidas) pode ser trabalhoso em projetos grandes, por isso é comum usar gerenciadores de pacotes.
Um gerenciador de pacotes é um sistema usado para realizar tarefas relacionadas ao uso de dependências, como publicar pacotes, instalá-los e removê-los, entre outras tarefas associadas ao gerenciamento dessas dependências.
Usar código de terceiros é uma atividade comum e necessária no desenvolvimento de software, mas como é possível explorar de alguma forma toda essa estrutura já consolidada no processo de criação de software?
No dia 9 de fevereiro de 2021, houve uma publicação intitulada Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies, onde Alex Birsan nos mostrou que algumas configurações relacionadas a dependências podem representar riscos ao sistema e como ele explorou isso para obter acesso à infraestrutura interna de grandes empresas após descobrir pacotes usados internamente.
Como funciona a confusão de dependência?
A confusão de dependência surge quando um gerenciador de pacotes instala um pacote de uma fonte pública em vez do repositório privado habitual. Em grandes empresas, é comum usar pacotes criados internamente que só podem ser usados em sua infraestrutura.
Assim, repositórios internos são usados para armazenar esses códigos e só podem ser importados pelos programadores da empresa, portanto, não há registro público desse pacote no gerenciador de pacotes da linguagem.
Linguagens de programação possuem diferentes gerenciadores de pacotes; alguns dos mais conhecidos são o pip para Python, npm para Node.JS e RubyGems para Ruby.

O que se notou foi que, como o pacote não está registrado publicamente no gerenciador de pacotes da linguagem, se um atacante souber o nome da dependência usada internamente e o ambiente não estiver configurado da melhor forma, é possível criar um registro público com o mesmo nome e com uma versão superior. Assim, ao instalar ou solicitar uma atualização desse pacote dentro da empresa, o gerenciador de pacotes baixará a versão pública, que é superior, mas contém o código criado pelo atacante.

Um atacante pode encontrar nomes de dependências em arquivos package.json; além disso, outro arquivo interessante para ter no dicionário é o package-lock.json, que é gerado automaticamente após alguma operação executada pelo NPM.
Nomes de dependências também podem ser encontrados em mensagens de erro e arquivos Javascript da aplicação. Para a verificação automática de vazamento de nomes dessas dependências, você pode usar a extensão JS Miner do Burp Suite, que já verifica a existência de um registro público do pacote e da organização. Abaixo, alguns exemplos de uso em pentests reais:


As respostas de erro da aplicação também podem vazar o nome dos módulos utilizados, como mostra esta imagem do fórum Stack Overflow:

Explorando a Confusão de Dependências
Para validar a vulnerabilidade, um repositório interno foi criado usando a ferramenta Verdaccio, e um pacote foi desenvolvido sem registro público, podendo ser utilizado apenas internamente.
sudo docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio

Após descobrir o nome do pacote, um atacante pode verificar o registro público no gerenciador de pacotes da linguagem utilizada pela aplicação, neste caso, o NPM.

Ao constatar a ausência de um registro público, é possível criar o pacote usando o comando npm init com uma versão superior à utilizada. Assim, ao instalar ou atualizar a dependência, o gerenciador de pacotes buscará a versão mais recente caso haja uma configuração incorreta.

No caso do pacote NPM, existe uma propriedade chamada scripts no arquivo package.json gerado. Utilizaremos a opção preinstall passando um comando para verificar a possibilidade de execução remota de código.
Para esta verificação, foi utilizado o Interactsh, uma ferramenta empregada para extração de dados fora de banda (out-of-band).

Em seguida, é possível inserir um comando para verificar a execução do código, extraindo, neste caso, o conteúdo do arquivo passwd do diretório /etc e incluindo o hostname no subdomínio.

É necessário criar uma conta em https://www.npmjs.com/ e, somente após a verificação da conta por e-mail, é possível publicar o pacote, como visto na imagem abaixo:

Desta vez, ao consultar o pacote no npm, é possível verificar sua existência.
Portanto, após a publicação, é necessário aguardar que um desenvolvedor ou um sistema de integração contínua (CI) que não tenha configurado corretamente o registro privado atualize ou instale este pacote para receber a comunicação de pingback no servidor.

Ao instalar o pacote, aparentemente não há nenhum problema:

No entanto, a máquina que realizou a instalação executou os comandos inseridos no arquivo package.json e comunicou-se com o servidor configurado. Ao final da imagem, é possível ver que o hostname blaze-machine foi passado como um subdomínio.

O conteúdo do arquivo passwd também é exibido na requisição feita pela máquina que instalou o pacote.

A mesma vulnerabilidade pode ser explorada em Python ao incluir código no arquivo setup.py do pacote; ao instalar o pacote usando o parâmetro –extra-index-url, o gerenciador baixa a versão que deve ser superior no registro público.
Prevenindo a Confusão de Dependências
O Node.js permite a criação de pacotes com escopo e sem escopo. Ao criar pacotes com escopo, apenas os membros da organização podem publicar nesse escopo. Para a publicação de pacotes com escopo, utiliza-se a linha npm init –scope=@my-org, onde my-org refere-se à organização.
Definir o escopo no .npmrc melhora sua configuração. Também é necessário configurar o registro privado em sistemas de CI e aplicar a definição no arquivo .npmrc. Outra medida importante é não referenciar múltiplos feeds, mas apenas um registro privado.

No Python, é necessário alterar os argumentos de –extra-index-url para –index-url para instalar o pacote. Você também pode especificar a versão utilizada no arquivo package.json ou requirements.txt, evitando configurações que utilizem latest e >= antes das versões, como no exemplo abaixo.

O arquivo de bloqueio (lockfile) é outra medida importante, pois as dependências são especificadas com a versão exata a ser utilizada; assim, em caso de comandos de atualização, a versão mais recente do registro público não será buscada. Este arquivo deve ser incluído no projeto.
Gerenciadores de pacotes podem oferecer formas de proteção contra esse tipo de ataque. Outra funcionalidade interessante é o Modo de Verificação de Hash (Hash-Checking Mode) disponível no pip; esse recurso verifica os pacotes baixados em relação a hashes locais, protegendo contra adulterações remotas, conforme mencionado na documentação que pode ser encontrada nesta URL: https://pip.pypa.io/en/stable/cli/pip_install/#hash-checking-mode
Conclusão
Ao analisar uma vulnerabilidade de confusão de dependência, é possível compreender o enorme impacto que ela pode ter na segurança se explorada. Se um atacante explorar uma falha de execução remota de código, como neste caso, ele pode obter acesso a arquivos internos e realizar alterações que impactam as operações da empresa, gerando prejuízos.
Dessa forma, percebe-se a importância de cuidar da segurança desde o início dos projetos, já que é nessa fase que são feitas as escolhas de pacotes existentes ou criados internamente, as melhores práticas em relação às tecnologias utilizadas, a comunicação com novos membros da infraestrutura interna que utilizarão os recursos e também a necessidade de conscientização dos desenvolvedores e dos responsáveis pela configuração dos ambientes, além da importância de testes contínuos para verificar tais falhas.
Referências
https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610 – Dependency Confusion: Como invadi a Apple, a Microsoft e dezenas de outras empresas
https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management#a_dependency_in_your_project – Noções básicas de gerenciamento de pacotes
https://azure.microsoft.com/mediahandler/files/resourcefiles/3-ways-to-mitigate-risk-using-private-package-feeds/3%20Ways%20to%20Mitigate%20Risk%20When%20Using%20Private%20Package%20Feeds%20-%20v1.0.pdf
https://dhiyaneshgeek.github.io/web/security/2021/09/04/dependency-confusion/
https://snyk.io/blog/detect-prevent-dependency-confusion-attacks-npm-supply-chain-security/
https://arxiv.org/pdf/1902.09217.pdf – Small World with High Risks: Um estudo sobre ameaças à segurança no ecossistema npm
https://appcheck-ng.com/dependency-confusion/#
https://blog.packagist.com/preventing-dependency-hijacking/
https://docs.npmjs.com/creating-and-publishing-private-packages
https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json




