Melhores práticas para uma cadeia de fornecedores de software segura
O código aberto está em toda parte. Ele está em muitas bases de código proprietárias e projetos comunitários. Para organizações e indivíduos, a questão hoje não é se você está ou não usando código aberto, mas qual e quanto código aberto você está usando.
Se você não estiver ciente do que está em sua cadeia de suprimentos de software, uma vulnerabilidade upstream em uma de suas dependências poderá ser fatal, tornando você e seus clientes vulneráveis a um possível comprometimento. Neste documento, vamos nos aprofundar no que o termo "cadeia de suprimentos de software" significa, por que ele é importante e como você pode ajudar a proteger a cadeia de suprimentos do seu projeto seguindo as melhores práticas.
Dependências
O termo cadeia de suprimentos de software é usado para se referir a tudo o que faz parte do seu software e de onde isso vem. São das dependências e propriedades de suas dependências que sua cadeia de suprimentos de software depende. Uma dependência é o que seu software precisa para executar. Ela pode existir na forma de código, binários ou outros componentes, e de onde eles vêm, como um repositório ou gerenciador de pacotes.
Ela inclui quem escreveu o código, quando ele foi contribuído, como ele foi revisado em busca de problemas de segurança, vulnerabilidades conhecidas, versões compatíveis, informações de licença e praticamente qualquer coisa que a toque em qualquer ponto do processo.
Sua cadeia de suprimentos também abrange outras partes de sua pilha além de um único aplicativo, como seus scripts de compilação e empacotamento ou o software que executa a infraestrutura da qual seu aplicativo depende.
Vulnerabilidades
Hoje, as dependências de software são generalizadas. É bastante comum que seus projetos usem centenas de dependências de código aberto para funcionalidades que você mesmo não precisa escrever. Isso pode significar que a maior parte do seu aplicativo consiste em código que não foi criado por você.
Possíveis vulnerabilidades em suas dependências de terceiros ou de código aberto são, presumivelmente, dependências que você não pode controlar tão rigorosamente quanto o código que você escreve, o que pode criar riscos potenciais de segurança em sua cadeia de suprimentos.
Se uma dessas dependências apresenta uma vulnerabilidade, há grandes chances de você também ter uma vulnerabilidade. Isso pode ser assustador, pois uma de suas dependências pode mudar sem que você saiba. Mesmo que uma vulnerabilidade exista em uma dependência hoje, mas não seja explorável, ela pode ser explorável no futuro.
Ser capaz de reutilizar o trabalho de milhares de desenvolvedores de código aberto e autores de bibliotecas significa que milhares de estranhos podem efetivamente contribuir diretamente para o seu código de produção. Seu produto, por meio de sua cadeia de suprimentos de software, é afetado por vulnerabilidades não corrigidas, erros inocentes ou até mesmo ataques maliciosos contra dependências.
Compromissos da cadeia de suprimentos
A definição tradicional de cadeia de suprimentos vem da manufatura; é a cadeia de processos necessários para fazer e fornecer algo. Inclui planejamento, fornecimento de materiais, fabricação e varejo. Uma cadeia de suprimentos de software é semelhante, exceto que, em vez de materiais, é composta por código. Em vez de fabricação, é desenvolvimento. Em vez de extrair minério do solo, o código é proveniente de fornecedores, comerciais ou de código aberto, e, em geral, o código-fonte aberto vem de repositórios. Adicionar código de um repositório significa que seu produto passa a depender desse código.
Um exemplo de ataque à cadeia de suprimentos de software ocorre quando um código mal-intencionado é adicionado propositalmente a uma dependência, usando a cadeia de suprimentos dessa dependência para distribuir o código para suas vítimas. Os ataques à cadeia de suprimentos são reais. Existem muitos métodos para atacar uma cadeia de suprimentos, desde inserir diretamente código malicioso como um novo contribuidor, assumir a conta de um contribuidor sem que outros percebam ou até mesmo comprometer uma chave de assinatura para distribuir software que não é oficialmente parte da dependência.
Um ataque à cadeia de suprimentos de software raramente é o objetivo final, mas sim o início de uma oportunidade para um invasor inserir malware ou fornecer um backdoor para acesso futuro.
Software sem patches
O uso de código aberto hoje é significativo e não deve desacelerar tão cedo. Considerando que não vamos parar de usar software de código aberto, a ameaça à segurança da cadeia de suprimentos é o software sem patch. Sabendo disso, como você pode lidar com o risco de uma dependência do seu projeto ter uma vulnerabilidade?
- Sabendo o que está no seu ambiente. Isso requer descobrir suas dependências e quaisquer dependências transitivas para entender os riscos dessas dependências, como vulnerabilidades ou restrições de licenciamento.
- Gerencie suas dependências Quando uma nova vulnerabilidade de segurança é descoberta, determine se você é afetado e, em caso afirmativo, atualize para a versão e o patch de segurança mais recentes disponíveis. Isso é especialmente importante para revisar alterações que introduzem novas dependências ou auditar periodicamente dependências antigas.
- Monitore sua cadeia de suprimentos. Isso ocorre auditando os controles vigentes para gerenciar suas dependências. Isso ajudará você a impor condições mais restritivas que devem atendidas para suas dependências.
Abordaremos várias ferramentas e técnicas que o NuGet e o GitHub fornecem, as quais você pode usar hoje para abordar riscos potenciais dentro do seu projeto.
Sabendo o que está no seu ambiente
Pacotes com vulnerabilidades conhecidas
📦 Consumidor de pacotes| 📦🖊 Autor de pacotes
O .NET 8 e o Visual Studio 17.8 adicionaram NuGetAudit, que avisará sobre pacotes diretos com vulnerabilidades conhecidas durante a restauração. O .NET 9 e o Visual Studio 17.12 alteraram o padrão para avisar sobre pacotes transitivos também.
NuGetAudit requer uma fonte para fornecer um banco de dados de vulnerabilidades conhecidas, portanto, se você não estiver usando nuget.org como uma fonte de pacote, deverá adicioná-lo como uma fonte de auditoria.
No momento em que o NuGet avisa você, a vulnerabilidade já é de conhecimento público. Os invasores podem usar essa divulgação pública para desenvolver ataques para alvos que não aplicaram patches em seus aplicativos. Portanto, quando recebe um aviso de que um pacote que seu projeto está usando tem uma vulnerabilidade conhecida, você deve agir rapidamente.
Grafo de dependência do NuGet
📦 Consumidor de pacotes
Você pode exibir suas dependências do NuGet em seu projeto observando diretamente o respectivo arquivo de projeto.
Isso normalmente é encontrado em um dos dois lugares:
packages.config
– Localizado na raiz do projeto.<PackageReference>
– Localizado no arquivo do projeto.
Dependendo do método usado para gerenciar suas dependências do NuGet, você também pode usar o Visual Studio para exibir suas dependências diretamente no Gerenciador de Soluções ou no Gerenciador de Pacotes NuGet.
Para ambientes de CLI, você pode usar o comando dotnet list package
para listar as dependências do projeto ou da solução.
Você também pode usar o comando dotnet nuget why
para entender por que os pacotes transitivos (aqueles não referenciados diretamente pelo seu projeto) estão sendo incluídos no gráfico de pacotes do seu projeto.
Para obter mais informações sobre como gerenciar dependências do NuGet, consulte a documentação a seguir.
Grafo de dependência do GitHub
📦 Consumidor de pacotes| 📦🖊 Autor de pacotes
Você pode usar o grafo de dependência para ver os pacotes dos quais o projeto depende e os repositórios que dependem dele. Isso pode ajudar você a ver todas as vulnerabilidades detectadas em suas dependências.
Para obter mais informações sobre as dependências do repositório do GitHub, consulte a documentação a seguir.
Versões de dependências
📦 Consumidor de pacotes| 📦🖊 Autor de pacotes
Para garantir uma cadeia de suprimentos de dependências segura, é necessário garantir que todas as suas dependências e ferramentas sejam atualizadas regularmente para a versão estável mais recente, pois elas geralmente incluem a funcionalidade e os patches de segurança para as vulnerabilidades conhecidas mais recentes. Suas dependências podem incluir código do qual você depende, binários que você consome, ferramentas que você usa e outros componentes. Isso pode incluir:
Gerenciar as dependências
Dependências obsoletas e vulneráveis do NuGet
📦 Consumidor de pacotes| 📦🖊 Autor de pacotes
Você pode usar a CLI do dotnet para listar quaisquer dependências obsoletas ou vulneráveis conhecidas que você possa ter dentro de seu projeto ou solução.
Você pode usar o comando dotnet list package --deprecated
ou dotnet list package --vulnerable
para fornecer uma lista de obsolescências ou vulnerabilidades conhecidas.
O NuGetAudit pode avisar você sobre dependências vulneráveis conhecidas e é habilitado por padrão quando uma fonte fornece um banco de dados de vulnerabilidades.
Dependências vulneráveis do GitHub
📦 Consumidor de pacotes| 📦🖊 Autor de pacotes
Se o seu projeto estiver hospedado no GitHub, você poderá utilizar o GitHub Security para encontrar vulnerabilidades e erros de segurança em seu projeto e o Dependabot os corrigirá abrindo uma pull request em sua base de código.
Capturar dependências vulneráveis antes que elas sejam introduzidas é um dos objetivos do movimento "Shift Left". Ser capaz de ter informações sobre suas dependências, como sua licença, dependências transitivas e a idade das dependências ajuda você a fazer exatamente isso.
Para obter mais informações sobre alertas do Dependabote e atualizações de segurança, confira a documentação a seguir.
Feeds NuGet
📦 Consumidor de pacotes
Use fontes de pacotes de sua confiança. Quando vários feeds de origem NuGet públicos e privados são usados, um pacote pode ser baixado de qualquer um dos feeds. Para garantir que sua compilação seja previsível e segura contra ataques conhecidos, como Confusão de dependência, saber de quais feeds específicos seus pacotes estão vindo é uma melhor prática. Você pode usar um único feed ou feed privado com recursos de upstreaming para proteção.
Para obter mais informações sobre como proteger seus feeds de pacotes, consulte Três Maneiras de Atenuar o Risco ao Usar Feeds de Pacotes Privados.
Ao usar um feed privado, confira as melhores práticas de segurança para gerenciar credenciais.
Políticas de confiança do cliente
📦 Consumidor de pacotes
Há políticas que você pode aceitar nas quais você exige que os pacotes usados sejam assinados. Isso permite que você confie em um autor de pacote, desde que ele seja assinado pelo autor ou confie em um pacote se ele for pertencente a um usuário ou conta específica que é repositório assinado por NuGet.org.
Para configurar políticas de confiança do cliente, consulte a documentação a seguir.
Arquivos de bloqueio
📦 Consumidor de pacotes
Os arquivos de bloqueio armazenam o hash do conteúdo do seu pacote. Se o hash de conteúdo de um pacote que você deseja instalar corresponder ao arquivo de bloqueio, ele garantirá a repetibilidade do pacote.
Para habilitar arquivos de bloqueio, consulte a documentação a seguir.
Mapeamento de origens de pacotes
📦 Consumidor de pacotes
O mapeamento de origens de pacotes permite que você declare centralmente em seu arquivo nuget.config de qual origem cada pacote em sua solução deve ser restaurado.
Para habilitar o mapeamento de origem do pacote, consulte a documentação a seguir.
Computadores seguros
Permissões do diretório
📦 Consumidor de pacotes
No Windows e Mac, e em algumas distribuições Linux, os diretórios base da conta de usuário são privados por padrão. No entanto, algumas distribuições do Linux tornam os diretórios de usuário legíveis por outras contas no mesmo computador por padrão. Além disso, há várias opções de configuração para redirecionar a pasta de pacotes globais do NuGet e o cache HTTP para locais não padrão. Soluções, projetos e repositórios também podem ser criados fora do diretório base do usuário.
Se você usar pacotes que não estejam no nuget.org, se qualquer outra conta no computador puder ler os pacotes globais do NuGet ou os diretórios de cache HTTP ou o diretório de saída de build do projeto, esses pacotes poderão ser divulgados para pessoas que não devem ter acesso a eles.
No Linux, o dotnet nuget update source
alterará as permissões de arquivo nuget.config para torná-lo legível apenas pelo proprietário do arquivo.
No entanto, se você editar o arquivo nuget.config de qualquer outra maneira e o arquivo estiver em uma localização em que outras contas possam ler o arquivo, poderá haver divulgação de informações confidenciais sobre a URL de origem do pacote ou as credenciais de origem do pacote.
Você deve garantir que qualquer arquivo nuget.config não possa ser lido por outros usuários do mesmo computador.
Soluções no diretório de downloads
📦 Consumidor de pacotes
Cuidado extra deve ser tomado se estiver trabalhando em soluções ou projetos em seu diretório de downloads. O NuGet acumulará configurações de vários arquivos de configuração e o MSBuild normalmente importará Directory.Build.props, Directory.NuGet.props, Directory.Build.targets e potencialmente outros arquivos, de qualquer diretório pai, até a raiz do sistema de arquivos.
A pasta de downloads tem risco adicional, pois geralmente é a localização padrão em que os navegadores da Web baixam arquivos da Internet
Agentes de Build
📦 Consumidor de pacotes
Os agentes de build (operadores de CI) que não são restaurados para um estado inicial após cada build têm vários riscos que devem ser considerados.
Para saber mais sobre maneiras seguras de gerenciar credenciais, consulte os documentos sobre como consumir pacotes de feeds autenticados.
Para saber mais sobre como modificar os diretórios nos quais o NuGet armazena dados, consulte os documentos sobre como gerenciar os pacotes globais, o cache e as pastas temporárias. Esses diretórios devem ser configurados para um diretório que o operador de CI limpa após cada build.
Observe que todos os pacotes usados pelo projeto podem ser deixados no diretório de saída de build do projeto. Se o projeto usar pacotes de fontes autenticadas, outros usuários do mesmo operador de CI poderão obter acesso não autorizado aos assemblies de pacote. Portanto, você também deve limpar seu repositório no final do build, mesmo quando o build falhar ou for cancelado.
Monitore sua cadeia de suprimentos
Verificação de segredo do GitHub
📦🖊 Autor do pacote
O GitHub verifica os repositórios em busca de chaves de API do NuGet para evitar o uso fraudulento de segredos cujo commit foi feito acidentalmente.
Para saber mais sobre a verificação secreta, consulte Sobre a verificação secreta.
Assinatura do pacote do autor
📦🖊 Autor do pacote
A assinatura do autor permite que um autor de pacote carimbe sua identidade em um pacote e que um consumidor verifique se ele veio de você. Isso protege você contra adulteração de conteúdo e serve como uma única fonte de verdade sobre a origem do pacote e a autenticidade do pacote. Quando combinado com políticas de confiança do cliente, você pode verificar se um pacote veio de um autor específico.
Para assinar um pacote com o autor, consulte Assinar um pacote.
Compilações reproduzíveis
📦🖊 Autor do pacote
Compilações reproduzíveis criam binários que são byte por byte idênticos cada vez que você os compila e contêm links de código-fonte e metadados do compilador que permitem que um consumidor de pacote recrie o binário diretamente e valide que o ambiente de compilação não foi comprometido.
Para saber mais sobre compilações reproduzíveis, consulte Produzindo pacotes com link de origem e a especificação Validação de compilação reproduzível.
Autenticação de dois fatores (2FA)
📦🖊 Autor do pacote
Toda conta no nuget.org tem a 2FA habilitada. Isso adiciona uma camada extra de segurança ao fazer login em sua conta do GitHub ou em sua conta do NuGet.org.
Reserva de prefixo da ID do pacote
📦🖊 Autor do pacote
Para proteger a identidade de seus pacotes, você pode reservar um prefixo de ID de pacote com seu respectivo namespace para associar um proprietário correspondente se o prefixo de ID do pacote se enquadrar corretamente nos critérios especificados.
Para saber mais sobre como reservar prefixos de ID, consulte Reserva de prefixo de ID de pacote.
Substituindo e removendo um pacote vulnerável da lista
📦🖊 Autor do pacote
Para proteger o ecossistema de pacotes do .NET quando você estiver ciente de uma vulnerabilidade em um pacote que criou, faça o possível para preterir e remover da lista o pacote para que ele seja ocultado dos usuários que procuram pacotes. Se você estiver consumindo um pacote que foi preterido e não listado, evite usá-lo.
Para saber como preterir e remover da lista de um pacote, consulte a documentação a seguir sobre como preterir e remover pacotes da lista.
Considere também relatar o conhecido ao Banco de Dados de Avisos do GitHub.
Resumo
Sua cadeia de suprimentos de software é qualquer coisa que entra ou afeta seu código. Mesmo que os comprometimentos da cadeia de suprimentos sejam reais e cresçam em popularidade, eles ainda são raros. Portanto, a coisa mais importante que você pode fazer é proteger sua cadeia de suprimentos é estar ciente de suas dependências, gerenciar suas dependências e monitorar sua cadeia de suprimentos.
Você aprendeu sobre vários métodos que o NuGet e o GitHub fornecem e que estão disponíveis hoje para serem mais eficazes na exibição, no gerenciamento e no monitoramento da sua cadeia de suprimentos.
Para obter mais informações sobre como proteger software no mundo, consulte o Relatório de Segurança O Estado do Octoverse 2020.