Criar programas C++ confiáveis e seguros

A publicação do governo dos Estados Unidos NISTIR 8397: Guidelines on Minimum Standards for Developer Verification of Software contém excelentes orientações sobre como construir software confiável e seguro em qualquer linguagem de programação.

Este documento segue a mesma estrutura do NISTIR 8397. Cada seção:

  • resume como usar os produtos de desenvolvedor da Microsoft para C++ e outras linguagens para atender às necessidades de segurança dessa seção, e
  • fornece orientação para obter o máximo de valor em cada área.

2.1 Modelagem de ameaças

Resumo

A modelagem de ameaças é um processo valioso, especialmente quando aplicada de uma forma que seja dimensionada para atender às suas necessidades de desenvolvimento e que reduza o ruído.

Recomendações

A modelagem de ameaças deve ser uma parte do SDL (Security Development Lifecycle - ciclo de vida de desenvolvimento de segurança) dinâmico. Sugerimos que, para o seu produto como um todo, para um recurso específico ou para uma grande alteração de design ou implementação:

  • Tenha um SDL sólido e dinâmico que permita o envolvimento antecipado com as equipes de desenvolvedores e a abordagem de direitos.
  • Aplique a modelagem de ameaças de forma direcionada. Aplique a modelagem de ameaças a todos os recursos, mas comece taticamente com recursos expostos, complexos ou críticos. Aplique-o regularmente como parte de uma revisão de produto de cima para baixo.
  • Aplique a modelagem de ameaças com antecedência (como acontece com todos os requisitos de segurança), quando ainda houver oportunidade de alterar o design. Além disso, os modelos de ameaças servem como entrada para outros processos, como redução da superfície de ataque ou design para segurança. Os modelos de ameaças que são criados posteriormente são, na melhor das hipóteses, "pesquisas" para teste de caneta (teste de penetração) ou áreas que precisam de testes de segurança, como fuzzing. Depois de criar um modelo de ameaça de linha de base, planeje continuar iterando nele à medida que a superfície de ataque for alterada.
  • Use o inventário de ativos e a conformidade para rastrear adequadamente o que compõe um produto e rastrear artefatos de segurança (incluindo modelos de ameaças) junto com os ativos aos quais eles se aplicam. Essa abordagem permite uma melhor avaliação automatizada de riscos e a concentração dos esforços de segurança nos componentes ou recursos específicos que mudam.
  • No Azure, a Ferramenta de Modelagem de Ameaças da Microsoft foi atualizada em 2022 para o desenvolvimento do Azure. Para obter mais informações, consulte Visão geral da Ferramenta de Modelagem de Ameaças da Microsoft - Azure

Fatores e práticas de apoio

Para aplicar corretamente a modelagem de ameaças e evitar o subuso/uso excessivo, descobrimos que os seguintes conceitos principais precisam ser abordados primeiro.

Abordagem de desenvolvimento

Primeiro, entenda a abordagem de desenvolvimento da equipe. Para equipes com fluxos de trabalho de desenvolvimento ágil que enviam dezenas de alterações para a produção diariamente, não é prático ou razoável exigir uma atualização do modelo de ameaça para cada alteração funcional. Em vez disso, desde o início, ao escrever os requisitos funcionais de um recurso, considere incluir um questionário de requisitos de segurança. O questionário deve se concentrar em perguntas específicas sobre o recurso para determinar quais aspectos futuros do seu SDL se aplicam. Por exemplo:

  • O recurso faz uma grande mudança no design de como fornecemos isolamento do cliente em um ambiente multilocatário? Em caso afirmativo, considere executar um modelo de ameaça completo.
  • Um novo recurso permite uploads de arquivos? Em caso afirmativo, talvez o mais apropriado seja uma avaliação de segurança de aplicativos Web.
  • Essa alteração é principalmente apenas uma alteração funcional da interface do usuário? Se assim for, talvez nada seja necessário além de suas ferramentas automatizadas tradicionais.

Os resultados do questionário de segurança informam quais técnicas de SDL vincular a qual unidade de desenvolvimento. Ele também informa os parceiros de desenvolvimento sobre os cronogramas de SDL do recurso, para que eles possam colaborar nos momentos certos.

Inventário de produtos

Em segundo lugar, mantenha um forte estoque de ativos dos produtos que você tem a tarefa de avaliar. Os produtos estão crescendo em complexidade. É comum escrever software para dispositivos conectados que tenham:

  • sensores (tais como carris de passageiros e veículos),
  • redes baseadas em barramento que conversam com outros componentes do veículo (como CANBUS ou PROFIBUS),
  • sem fio/celular/Bluetooth para comunicação com dispositivos do cliente e back-ends na nuvem,
  • aprendizado de máquina na nuvem alimentando o dispositivo ou um aplicativo de gerenciamento de frota,
  • e mais.

Em produtos tão complexos, a modelagem de ameaças é fundamental. Ter um inventário de ativos forte permite que você visualize toda a pilha de produtos para ver o quadro completo e ver os principais locais que precisam ser avaliados sobre como um recurso novo ou alterado afeta a segurança do produto.

Granularidade e integração

Estabeleça sistemas para medir a conformidade usando métricas claras.

  • Meça regularmente a conformidade para o desenvolvimento em nível de recurso. A conformidade de recursos geralmente deve ser medida com maior frequência e menor granularidade, às vezes até mesmo no sistema do desenvolvedor ou no momento da confirmação/mesclagem de código.
  • Avalie periodicamente a segurança do produto mais amplo no qual um recurso ou componente está sendo consumido. Avaliações mais amplas normalmente são feitas com menor frequência e granularidade mais ampla, como no momento do teste do módulo ou do sistema.

Escala

Mantenha um sistema de inventário de ativos adequado que capture e preserve artefatos de segurança e a saída de revisões de modelos de ameaças. Ter um inventário claro permite que você avalie os resultados de revisão de padrões e tome decisões inteligentes sobre como refinar o programa de segurança do produto regularmente.

Tente combinar questionários de segurança da fase de requisitos, resultados de modelagem de ameaças, resultados de avaliação de segurança e resultados de ferramentas automatizadas. Combiná-los permite automatizar um ponto de vista do risco relativo de um determinado produto, idealmente como um "painel", para informar às equipes de segurança no que se concentrar para obter o melhor valor da modelagem de ameaças.

2.2 Testes automatizados

Resumo

Os testes automatizados são uma maneira importante de garantir a qualidade e a segurança do seu código. Eles são parte integrante no suporte a outras áreas mencionadas neste documento, como a modelagem de ameaças. Quando combinados com outras práticas de codificação segura, eles ajudam a proteger contra bugs e vulnerabilidades que estão sendo introduzidos na base de código.

Atributos de chave

Os testes devem ser confiáveis, consistentes e isolados. Esses testes devem abranger o máximo possível do código. Todos os novos recursos e correções de bugs devem ter testes correspondentes para garantir a segurança e confiabilidade do código a longo prazo, quando possível. Execute testes automatizados regularmente e no maior número possível de ambientes, para garantir que eles sejam executados e que cubram todas as áreas:

  • O primeiro lugar que eles devem executar é na máquina que está fazendo as alterações. A execução de testes é feita mais facilmente dentro do IDE que está sendo usado para edição ou como um script na linha de comando à medida que o desenvolvedor faz as alterações.
  • O próximo local em que eles devem ser executados é como parte do processo de confirmação/mesclagem da solicitação pull.
  • O último local para executar testes é como parte de um pipeline de Integração Contínua e Implantação Contínua (CI/CD) ou em suas compilações candidatas à versão.

O escopo dos testes deve aumentar a cada etapa, com a última etapa fornecendo cobertura total para qualquer coisa que as outras etapas possam perder.

Uso e manutenção contínuos

A confiabilidade do teste é uma parte importante da manutenção da eficácia do conjunto de testes. As falhas de teste devem ser atribuídas e investigadas, com possíveis problemas de segurança recebendo alta prioridade e sendo atualizados dentro de um prazo rápido e predeterminado. Ignorar falhas em testes não deve ser uma prática comum, mas deve exigir forte justificativa e aprovação. Falhas de teste devido a problemas dentro do próprio conjunto de testes devem ser tratadas da mesma forma que outras falhas, para evitar um lapso na cobertura em que os problemas do produto poderiam ser perdidos.

Tipos de testes, especialmente testes unitários

Existem vários tipos de testes automatizados e, embora nem todos sejam aplicáveis a todos os aplicativos, um bom conjunto de testes contém uma seleção de vários tipos diferentes. Casos de teste baseados em código, como testes de unidade, são os mais comuns e mais integrais, sendo aplicáveis a todos os aplicativos e cobrindo intencionalmente o maior número possível de caminhos de código para correção. Esses testes devem ser pequenos, rápidos e não afetar o estado da máquina, para que o conjunto completo de testes possa ser executado com rapidez e frequência. Se possível, execute testes em muitas máquinas que têm configurações de hardware diferentes para detectar problemas que não são reproduzíveis em um único tipo de máquina.

Visual Studio

O Visual Studio Test Explorer oferece suporte nativo a muitas das estruturas de teste C++ mais populares e tem opções para instalar extensões para mais estruturas. Essa flexibilidade é útil para executar um subconjunto de testes que abrangem o código no qual você está trabalhando e facilita a depuração de falhas de teste à medida que elas surgem. O Visual Studio também facilita a configuração de novos conjuntos de testes para projetos existentes e fornece ferramentas úteis, como o CodeLens, para facilitar o gerenciamento desses testes. Para obter mais informações sobre como gravar, executar e gerenciar testes C/C++ com o Visual Studio, consulte Gravar testes de unidade para C/C++ - Visual Studio (Windows).

No Azure e no GitHub CI/CD

Testes que fazem uma verificação mais profunda e levam mais tempo para serem executados, como análise estática, detecção de componentes e assim por diante, são bons candidatos para testes de solicitação pull ou testes de integração contínua. As Ações de DevOps e GitHub do Azure facilitam a execução automática de validações e bloqueiam check-ins de código se uma validação falhar. A imposição automatizada ajuda a garantir que todo o código que está sendo verificado seja seguro com base nessas verificações mais rigorosas que estão sendo executadas. Os Pipelines do Azure e a Validação de Criação do Azure DevOps são descritos aqui:

2.3 Análise baseada em código ou estática

Resumo A análise de código/binário estático deve ser habilitada por padrão, para ser segura por padrão. A análise estática analisa um programa em busca das políticas de segurança necessárias no momento em que ele está sendo criado, e não no momento da execução, quando uma exploração pode ocorrer na máquina do cliente. A análise estática pode analisar o programa em forma de código-fonte ou em forma de executável compilado.

Recomendações A Microsoft recomenda que você:

  • Habilite a análise estática para todos os programas C++, tanto para o código-fonte de entrada (antes da compilação) quanto para os binários executáveis (após a compilação). "Habilitar" pode significar executar a análise durante cada compilação na máquina do desenvolvedor ou como uma compilação separada para inspecionar o código posteriormente ou como um requisito de check-in.
  • Incorporar análise estática em pipelines de CI como forma de teste.
  • A análise estática por definição vem com falsos positivos, e esteja preparado para incorporar esse fato em seu ciclo de feedback de qualidade. Seja rápido para ativar todos os avisos de baixo falso-positivo com antecedência. Em seguida, seja proativo para aumentar gradualmente o número de regras para as quais sua base de código compila avisos limpos à medida que você adiciona regularmente mais regras que sinalizam bugs importantes às custas de falsos positivos gradualmente mais altos (inicialmente, antes que a base de código tenha sido limpa para essas regras também).
  • Sempre use as versões mais recentes com suporte do Visual Studio e configure seu ambiente de engenharia para consumir rapidamente as versões de patch mais recentes assim que elas estiverem disponíveis, sem atrasar para o próximo estágio/ciclo de desenvolvimento.

Principais ferramentas Esteja ciente e use o seguinte:

Observações:

  • /analyze permite a análise estática de código C++ em tempo de compilação para identificar vulnerabilidades críticas de código de segurança e confiabilidade. Ele deve ser habilitado em toda a linha do tempo de desenvolvimento de um programa C++. Comece habilitando pelo menos o "Microsoft Native Recommended" por padrão como uma linha de base mínima. Em seguida, consulte a documentação para saber como especificar mais regras, especialmente as regras do C++ Core Guidelines, conforme exigido por suas políticas de engenharia. O recurso de análise estática do código-fonte está disponível no IDE do Visual C++ e nas ferramentas de compilação de linha de comando.
  • /W4e deve ser ativado sempre que possível, para garantir que você compile seu código de forma limpa em altos níveis de aviso () e /WX trate os avisos como erros que devem ser corrigidos (W4WX). Essas opções permitem localizar erros de dados não inicializados que outras ferramentas de análise estática não podem verificar, porque os erros só se tornam visíveis depois que o back-end do compilador executa a análise interprocessual e o inlineing.
  • A análise binária BinSkim garante que os projetos habilitem uma ampla gama de recursos de segurança. BinSkim gera PDBs e outras saídas que facilitam a verificação da cadeia de custódia e a resposta eficiente a problemas de segurança. A Microsoft recomenda executar a ferramenta BinSkim para analisar todos os binários executáveis (.sysou .exe.dll ) produzidos ou consumidos por seus programas. O Guia do Usuário BinSkim inclui uma lista de padrões de segurança suportados. A Microsoft recomenda que você corrija todos os problemas relatados como "erros" pela ferramenta BinSkim. Problemas relatados como "avisos" devem ser avaliados seletivamente, pois resolvê-los pode ter implicações de desempenho ou pode não ser necessário.

No Azure e no GitHub CI/CD, a Microsoft recomenda sempre habilitar o código-fonte e a análise estática binária em cenários de CI/CD de versão. Execute a análise de origem imediatamente na máquina do desenvolvedor local, ou pelo menos para cada solicitação de confirmação ou pull, para detectar bugs de origem o mais cedo possível e minimizar os custos gerais. Bugs de nível binário tendem a ser introduzidos mais lentamente, então pode ser suficiente executar a análise binária em cenários de CI/CD de pré-lançamento menos frequentes (como compilações noturnas ou semanais).

2.4 Revisão de segredos codificados

Resumo

Não codifice segredos dentro do software. Você pode encontrar e remover segredos do código-fonte de forma eficiente usando ferramentas confiáveis que podem verificar toda a sua base de código-fonte. Depois de encontrar segredos, mova-os para um local seguro seguindo a diretriz de armazenamento seguro e uso de segredos.

Problema

"Segredos" significa entidades que estabelecem identidade e fornecem acesso a recursos, ou que são usadas para assinar ou criptografar dados confidenciais. Os exemplos incluem senhas, chaves de armazenamento, cadeias de conexão e chaves privadas. É tentador manter segredos no produto de software para que eles possam ser prontamente obtidos quando necessário pelo software. No entanto, esses segredos codificados podem levar a incidentes de segurança graves ou catastróficos, pois são facilmente descobertos e podem ser usados para comprometer seu serviço e dados.

Prevenção

Segredos codificados no código-fonte (como texto sem formatação ou blob criptografado) são uma vulnerabilidade de segurança. Aqui estão as diretrizes gerais sobre como evitar segredos no código-fonte:

  • Use uma ferramenta de pré-check-in para verificar e capturar possíveis segredos codificados no código antes de enviar para o controle do código-fonte.
  • Não coloque credenciais de texto não criptografado no código-fonte ou nos arquivos de configuração.
  • Não armazene credenciais de texto não criptografado no SharePoint, OneNote, compartilhamentos de arquivos e assim por diante. Ou compartilhe-os por e-mail, mensagens instantâneas e assim por diante.
  • Não criptografe um segredo com uma chave de descriptografia facilmente detectável. Por exemplo, não armazene um arquivo PFX junto com um arquivo que contenha sua senha.
  • Não criptografe um segredo com uma descriptografia fraca. Por exemplo, não criptografe um arquivo PFX com uma senha fraca ou comum.
  • Evite colocar credenciais criptografadas no código-fonte. Em vez disso, use espaços reservados em sua origem e permita que seu sistema de implantação os substitua por segredos de repositórios aprovados.
  • Aplique os mesmos princípios a segredos em ambientes como teste, preparação e assim por diante, como você faz em implantações de produção. Os adversários geralmente têm como alvo sistemas que não são de produção, pois são menos bem gerenciados, e então os usam para pivotar na produção.
  • Não compartilhe segredos entre implantações (por exemplo, teste, preparação, produção).

Embora não esteja diretamente relacionado a segredos codificados, lembre-se também de garantir segredos para seu teste, desenvolvimento e produção:

  • Gire seus segredos periodicamente e sempre que eles possam ter sido expostos. Ter uma capacidade demonstrada de girar/reimplantar segredos é evidência de um sistema seguro. Mais notavelmente, a ausência dessa capacidade é uma evidência ainda mais forte de uma vulnerabilidade inevitável.
  • Não ceda ao raciocínio comum do desenvolvedor de que "minhas credenciais de teste não criam risco". Na prática, quase sempre o fazem.
  • Considere afastar-se de segredos (por exemplo, senhas, chaves de portador) inteiramente em preferência a soluções RBAC/orientadas por identidade como uma boa solução de engenharia que pode evitar totalmente o mau gerenciamento secreto.

Detecção

Os componentes herdados do seu produto podem conter segredos ocultos codificados em seu código-fonte. Às vezes, segredos das máquinas desktop dos desenvolvedores podem se infiltrar na ramificação remota e se fundir na ramificação de lançamento, vazando segredos sem querer. Para descobrir segredos que podem estar escondidos em seu código-fonte, você pode usar ferramentas que podem verificar seu código em busca de segredos codificados:

Remediação

Quando credenciais são encontradas em seu código-fonte, a necessidade urgente imediata é invalidar a chave exposta e realizar uma análise de risco com base na exposição. Mesmo que o sistema precise permanecer em execução, você pode habilitar um gerenciador secreto para correção usando estas etapas:

  1. Se a correção permitir a alternância para identidades gerenciadas ou exigir o descarte de um gerenciador de segredos, como o Azure Key Vault (AKV), faça isso primeiro. Em seguida, reimplante com a identidade ou chave atualizada.
  2. Invalidar o segredo exposto.
  3. Realizar auditoria/avaliação de risco de danos potenciais devido a comprometimento.

Para proteger chaves criptográficas e outros segredos usados por aplicativos e serviços de nuvem, use o Cofre de Chaves do Azure com uma política de acesso apropriada.

Se uma exposição comprometer determinados dados/PII do cliente, ela poderá exigir outros requisitos de conformidade/relatórios.

Remova os segredos agora invalidados do código-fonte e substitua-os por métodos alternativos que não exponham os segredos diretamente no código-fonte. Procure oportunidades para eliminar segredos sempre que possível usando ferramentas como o Azure AD. Você pode atualizar seus métodos de autenticação para aproveitar as identidades gerenciadas por meio do Active Directory do Azure). Use apenas armazenamentos aprovados para armazenar e gerenciar segredos, como o Azure Key Vault (AKV). Para saber mais, veja:

Azure DevOps (AzDO)

Os usuários do AzDO podem digitalizar seu código por meio do GitHub Advanced Security for Azure DevOps (GHAzDO). O GHAzDO também permite que os usuários evitem exposições secretas, ativando o Push Protection em seus repositórios, capturando exposições potenciais antes que elas sejam vazadas. Para obter mais informações sobre como detectar segredos codificados no código no Azure DevOps, consulte Secret Scanning for Github Advanced Security for Azure DevOps em cada um dos seguintes links:

No GitHub

A varredura secreta está disponível no GitHub.com de duas formas:

  • Alertas de varredura secreta para parceiros. É executado automaticamente em todos os repositórios públicos. As cadeias de caracteres que correspondem aos padrões fornecidos por parceiros da verificação de segredos são relatadas diretamente ao parceiro relevante.
  • Alertas de varredura secreta para usuários. Você pode habilitar e configurar a varredura extra para repositórios de propriedade de organizações que usam o GitHub Enterprise Cloud e têm uma licença para o GitHub Advanced Security. Essas ferramentas também oferecem suporte a repositórios privados e internos.

O GitHub fornece padrões conhecidos de segredos para parceiros e usuários que podem ser configurados para atender às suas necessidades. Para mais informações, consulte:

Observação

O GitHub Advanced Security for Azure DevOps traz as mesmas soluções de varredura de segredo, varredura de dependência e verificação de código CodeQL já disponíveis para usuários do GitHub e os integra nativamente ao Azure DevOps para proteger seus Repositórios e Pipelines do Azure.

Recursos adicionais

2.5 Executar com verificações e proteção fornecidas pelo idioma e pelo sistema operacional

Resumo

A proteção binária é feita aplicando controles de segurança em tempo de compilação. Isso inclui mitigações que:

  • impedir vulnerabilidades exploráveis no código,
  • habilitar detecções de tempo de execução que acionam defesas de segurança na exploração, e
  • Habilite a produção e o arquivamento de dados para ajudar a limitar os danos causados por incidentes de segurança.

Os consumidores binários devem optar pelos recursos de segurança do Windows para obter o benefício total da proteção.

A Microsoft fornece um conjunto de recursos específicos para projetos C++ para ajudar os desenvolvedores a escrever e enviar código mais seguro e protegido. Os desenvolvedores de C++ também devem aderir a padrões de segurança comuns a linguagens que geram código executável. A Microsoft mantém o BinSkim, um verificador binário OSS público que ajuda a impor o uso de muitas proteções descritas nesta seção. Para obter mais informações sobre BinSkim, consulte Guia do usuário Binskim | GitHub

Os controles de nível binário diferem de acordo com o local onde são aplicados no processo de engenharia. Você deve distinguir entre as opções de compilador e vinculador que: são estritamente tempo de compilação, alteram a geração de código com sobrecarga de tempo de execução e alteram a geração de código para obter compatibilidade com as proteções do sistema operacional.

As configurações do desenvolvedor devem preferir habilitar o máximo possível de análise estática, habilitar a produção de dados privados para acelerar a depuração e assim por diante. As compilações de versão devem ser ajustadas para uma combinação apropriada de segurança, desempenho e outras preocupações de geração de código. Os processos de liberação devem ser configurados para gerar e gerenciar corretamente dados de compilação consumidos publicamente versus privados (por exemplo, símbolos públicos versus privados).

Mantenha-se atualizado: use sempre compiladores e ferramentas atualizados

Compile todo o código com conjuntos de ferramentas atuais para se beneficiar de suporte a linguagem atualizada, análise estática, geração de código e controles de segurança. Como os compiladores afetam todos os componentes gerados, o potencial de regressão na atualização da ferramenta é relativamente alto. O uso de compiladores desatualizados cria um risco específico de ação corretiva ao responder a um incidente de segurança, porque as equipes podem não ter tempo suficiente para atualizar os compiladores. A Microsoft recomenda que as equipes desenvolvam o recurso para atualizar e testar regularmente as atualizações do compilador.

Use métodos de desenvolvimento seguros, versões de linguagem, frameworks/APIs

O código deve utilizar metodologias de desenvolvimento, versões de linguagem, estrutura, APIs e assim por diante, que minimizam os riscos, promovendo segurança e simplicidade em C++, incluindo:

  • Consulte a Biblioteca de Suporte de Diretrizes Principais do C++ (GSL) para obter orientação para escrever código C++ moderno, seguro e consistente que siga as práticas recomendadas e evite armadilhas comuns.
  • Consulte Implementação do Microsoft GSL para funções e tipos que as Diretrizes principais do C++ sugerem que você use.
  • Contêineres C++ seguros para recursos, proteções de estouro de memória CRT (biblioteca de tempo de execução C): Prefira std::vector e std::string, que são seguras para recursos. Se você precisar usar dados C, use as versões seguras das funções CRT, que são projetadas para ajudar a evitar a corrupção de memória devido ao uso indevido do buffer e comportamentos de linguagem indefinidos.
  • A biblioteca SafeInt protege contra estouro de inteiros em operações matemáticas e de comparação.

Consumir dependências seguras

Os binários não devem ser vinculados a bibliotecas e dependências inseguras. As equipes de desenvolvimento devem rastrear todas as dependências externas e resolver vulnerabilidades de segurança CVEs/identificadas nesses componentes, atualizando para versões mais seguras quando sujeitas a essas vulnerabilidades.

Maximize as garantias de procedência do código e a eficiência da resposta de segurança

A compilação deve permitir garantias de procedência de código fortes para ajudar a detectar e evitar a introdução de backdoors e outros códigos maliciosos. Os dados resultantes, também críticos para depuração e investigação, devem ser arquivados para todas as versões de software para gerar uma resposta de segurança eficiente se forem comprometidos. As seguintes opções de compilador geram informações críticas para uma resposta de segurança:

  • /ZH:SHA_SHA256 no Visual C++ - Garante que um algoritmo criptograficamente seguro seja usado para gerar todos os hashes de arquivo de origem PDB.
  • /Zi, (Debug Information Format) no Visual C++ - Além de publicar símbolos removidos para coletar dados de falha e outros cenários de uso público, /ZI certifique-se de que as compilações produzam e arquivem PDBs privados para todos os binários liberados. As ferramentas de análise binária exigem símbolos completos para verificar se muitas mitigações de segurança foram habilitadas em tempo de compilação. Os símbolos privados são fundamentais na resposta de segurança e reduzem os custos de depuração e investigação quando os engenheiros estão correndo para avaliar e limitar os danos quando uma exploração acontece.
  • /SOURCELINK no Visual C++ Linker - Incluir arquivo Sourcelink no PDB: O link de origem é um sistema independente de controle de idioma e código-fonte que fornece depuração de código-fonte para binários. A depuração de origem aumenta consideravelmente a eficiência, o intervalo de validações de segurança de pré-lançamento e a resposta a incidentes pós-lançamento.

Habilitar erros do compilador para evitar problemas no momento da criação do código

A compilação deve permitir verificações do compilador relevantes para a segurança como erros de quebra, por exemplo:

Marcar binários como compatíveis com mitigações de segurança de tempo de execução do sistema operacional

As configurações do compilador e do vinculador devem optar por recursos de geração de código que detectam e atenuam a execução de códigos mal-intencionados, incluindo:

Impedir a divulgação de informações confidenciais

As configurações do compilador devem optar pela prevenção de descoberta de informações confidenciais. Nos últimos anos, pesquisadores descobriram vazamento não intencional de informações que se origina com recursos de hardware, como execução especulativa.

No nível do software, dados confidenciais podem ser transmitidos a invasores se vazarem inesperadamente. A falha na inicialização zero de buffers e outro uso indevido de buffer pode vazar dados confidenciais privados para invasores que chamam API confiável. Essa classe de problema é melhor tratada habilitando a análise estática extra e usando contêineres de recursos seguros, conforme descrito anteriormente.

  • /Qspectre - Mitigar ataques de canal lateral de execução especulativa - Insere instruções de barreira que ajudam a impedir a divulgação de dados sensíveis produzidos pela execução especulativa. Essas atenuações devem ser habilitadas para código que armazena dados confidenciais na memória e opera em um limite de confiança. A Microsoft sempre recomenda medir o impacto no desempenho em relação aos benchmarks apropriados ao habilitar as mitigações do Spectre devido à possibilidade de introduzir verificações de tempo de execução em blocos ou loops críticos de desempenho. Esses caminhos de código podem desabilitar atenuações por meio do spectre(nomitigation)declspec modificador. Os projetos que habilitam '/Qspectre'' também devem ser vinculados a bibliotecas que também são compiladas com essas atenuações, incluindo as bibliotecas de tempo de execução da Microsoft.

2.6 Casos de teste de caixa preta

Resumo

Os testes de caixa preta não dependem de conhecer o funcionamento interno do componente testado. Os testes de caixa preta são projetados para testar a funcionalidade de ponta a ponta dos recursos do produto em qualquer camada ou nível. Os testes de caixa preta podem ser testes funcionais, testes de interface do usuário, testes de desempenho e testes de integração. Os testes de caixa preta são valiosos para medir a confiabilidade geral e a correção funcional e garantir que o produto se comporte conforme o esperado.

Relação com outras seções

Esses tipos de testes baseados em requisitos são úteis para validar as suposições feitas no Modelo de Ameaças e cobrir ameaças potenciais, conforme mencionado nessa seção. Esses testes são úteis para testar a integração entre componentes separados do produto, especialmente aqueles que ultrapassam os limites de confiança, conforme descrito no modelo de ameaça. Os casos de teste de caixa preta também são úteis para testar todos os tipos de casos de borda para validação de entrada do usuário. Testar casos de borda conhecidos e casos de erro são úteis. Fuzzing também é útil para testar casos menos óbvios.

Automação e regressão

Execute esses testes regularmente e compare os resultados com execuções anteriores para detectar alterações de quebra ou regressões de desempenho. Além disso, a execução desses testes em muitas máquinas diferentes e configurações de instalação pode ajudar a cobrir quaisquer problemas que possam surgir de diferentes arquiteturas ou alterações de instalação.

Despejos de memória

Esses testes ajudam a encontrar problemas com a confiabilidade, sendo capazes de testar muitos cenários diferentes que podem encontrar falhas, travamentos, deadlocks e assim por diante. Coletando despejos de memória como parte de falhas de teste, você pode importar os despejos diretamente para o Visual Studio para investigar melhor quais partes do código estão atingindo esses problemas. Se você executar testes funcionais de dentro do Visual Studio, você pode facilmente replicar e depurar falhas vendo exatamente onde dentro da caixa preta o teste falha, e você pode testar correções rapidamente.

Para começar a depurar testes, consulte Depurar testes de unidade com o Gerenciador de Testes - Visual Studio (Windows)

No Azure

O Azure DevOps também pode ajudar a gerenciar e validar esses testes com o uso de Planos de Teste. Esses testes podem ser usados para garantir a aprovação com validação manual e para executar testes automatizados associados aos requisitos do produto. Mais informações sobre os Planos de Teste do Azure e como usá-los para executar testes automatizados podem ser encontradas aqui:

2.7 Casos de teste baseados em código

Resumo

Os casos de teste baseados em código são parte integrante da manutenção da segurança e confiabilidade do seu produto. Esses testes devem ser pequenos e rápidos e não devem ter impacto uns nos outros para que possam ser executados em paralelo. Os testes baseados em código são fáceis para os desenvolvedores executarem localmente em sua máquina de desenvolvimento sempre que fizerem alterações no código sem se preocupar em desacelerar seu ciclo de desenvolvimento.

Tipos e relação com outras seções

Os tipos comuns de casos de teste baseados em código incluem:

  • ensaios unitários,
  • testes parametrizados para cobrir funções com vários tipos de entrada,
  • testes de componentes para manter cada componente de teste separado, e
  • teste simulado para validar partes do código que se comunicam com outros serviços, sem expandir o escopo do teste para incluir esses serviços em si.

Esses testes são baseados no código interno que é escrito, enquanto os testes de caixa preta são baseados nos requisitos funcionais externos do produto.

Meta

Por meio desses testes, o objetivo é alcançar um alto nível de cobertura de teste sobre seu código. Você deve acompanhar ativamente essa cobertura e onde existem lacunas. À medida que você adiciona mais testes que exercem mais caminhos de código, a confiança geral na segurança e confiabilidade do código aumenta.

Visual Studio

As ferramentas do explorador de teste no Visual Studio facilitam a execução desses testes com frequência e obtêm comentários sobre taxas de aprovação/falha e locais de falha rapidamente. Muitas das estruturas de teste também oferecem suporte a recursos do CodeLens para ver o status do teste no local do próprio teste, facilitando a adição e a manutenção do conjunto de testes. O Gerenciador de Testes também facilita o gerenciamento desses testes, permitindo grupos de teste, listas de reprodução de teste personalizadas, filtragem, classificação, pesquisa e muito mais.

Para saber mais, veja:

O Visual Studio também vem com ferramentas para controlar a cobertura de código. Essas ferramentas permitem garantir que as alterações de código feitas sejam cobertas por testes existentes ou adicionar novos testes para cobrir caminhos de código novos e não testados. As ferramentas também mostram a porcentagem de cobertura de código para garantir que ele seja mantido acima de um nível de destino para confiança na qualidade geral do código.

Para obter informações sobre essas ferramentas, consulte Teste de cobertura de código - Visual Studio (Windows)

No Azure

O Azure DevOps também pode ajudar no acompanhamento de resultados de cobertura de código para todo o produto como parte do processo de pipeline de compilação. Para obter mais informações, consulte Revisar cobertura de código - Pipelines do Azure.

2.8 Casos de teste históricos

Resumo

Casos de teste históricos, também conhecidos como casos de teste de regressão, evitam que problemas antigos ressurjam novamente e aumentam a cobertura geral de teste do produto. Você deve garantir que, quando um bug for corrigido, o projeto também adicione um caso de teste correspondente. Com o tempo, à medida que as correções são feitas, a robustez geral do conjunto de testes continuará melhorando, dando melhores garantias de confiabilidade e segurança.

Principais qualidades e relação com outras seções

Como eles testam regressões de bugs, esses testes devem ser rápidos e fáceis de executar, para que possam ser executados junto com os [Casos de Teste Baseados em Código] e contribuir para a cobertura geral de código do produto. Junto com isso, usar exemplos reais de clientes para inspirar novos casos de teste é uma ótima maneira de melhorar a cobertura e a qualidade dos testes.

Visual Studio

O Visual Studio permite que você adicione facilmente testes ao conjunto enquanto faz as alterações para corrigir o bug e executar rapidamente os testes e a cobertura de código para garantir que todos os novos casos sejam considerados. Referenciar o ID de bug do seu sistema de controle de problemas no código onde você escreve o teste é uma boa maneira de conectar os testes de regressão aos problemas correspondentes. Prefira usar os Quadros do Azure e os planos de teste junto com o Visual Studio:

  • associar testes, casos de teste e problemas; e
  • para acompanhar todos os aspectos de um problema e seus testes correspondentes.

Para saber mais, veja:

Eventualmente, a integração desses testes na área de teste de unidade que deve cobrir a seção de código ajuda a manter o conjunto de testes organizado e mais fácil de gerenciar. Você pode usar o agrupamento de teste do Gerenciador de Testes para controlar efetivamente os testes que pertencem juntos. Para obter mais informações, consulte Executar testes de unidade com o Gerenciador de Testes - Visual Studio (Windows)

2.9 Fuzzing

Resumo Fuzzing (também conhecido como fuzz testing) é uma técnica de teste de software automatizado que envolve o fornecimento de dados inválidos, inesperados ou aleatórios como entrada para um programa. O programa é então monitorado para exceções, como falhas, falhas internas ou de asserções de código injetadas pelo compilador e possíveis vazamentos de memória.

Diretrizes

Use o fuzzing em todos os softwares que possam processar entradas não confiáveis que um invasor possa controlar. Se você estiver criando um novo aplicativo e seu conjunto de testes associado, inclua o fuzzing para módulos-chave o mais cedo possível. Executar o fuzzing pela primeira vez em um software quase sempre revela vulnerabilidades reais que antes eram desconhecidas. Uma vez que você começar a fuzzing, nunca pare.

Relação com outras seções

Quando o fuzzing relata uma falha, ele sempre fornece naturalmente um caso de teste reproduzível que demonstra o bug. Esse caso de teste pode ser reproduzido, resolvido e, em seguida, adicionado aos casos de teste históricos.

Ao usar ambos os sanitizantes, como o Address Sanitizer (ASan) e fuzzing:

  • Primeiro execute seus testes normais com sanitizantes habilitados para ver se há problemas, em seguida, uma vez que o código é limpo sanitizer começar a fuzzing.
  • Para C ou C++, há compiladores que automatizam a injeção de asserções de tempo de execução e metadados que habilitam o ASan. Quando compilados para ASan, os binários resultantes se vinculam a uma biblioteca de tempo de execução que pode diagnosticar com precisão 15+ categorias de erros de segurança de memória com zero falsos positivos. Para C ou C++ quando você tiver código-fonte, use LibFuzzer, que requer que o ASan seja habilitado primeiro.
  • Para bibliotecas escritas em Java, C#, Python, Rust e assim por diante, use a estrutura AFL++.

Qualidades-chave

  • O Fuzzing encontra vulnerabilidades muitas vezes perdidas pela análise estática de programas, testes exaustivos de recursos e inspeção manual de código.
  • O fuzzing é uma maneira eficaz de encontrar bugs de segurança e confiabilidade em software, tanto que o Ciclo de Vida de Desenvolvimento de Segurança da Microsoft requer fuzzing em cada interface não confiável de cada produto (consulte também Modelagem de ameaças).
  • Sempre use fuzzing para software que possa processar entradas não confiáveis.
  • O fuzzing é eficaz para aplicativos autônomos com grandes analisadores de dados.

CI/CD do Azure e do GitHub

Modifique sua(s) compilação(ões) para suportar a criação contínua de executáveis que usam LibFuzzer ou AFL++. Você pode adicionar recursos de computação extras necessários para fuzzing em serviços como OSS-Fuzz ou OneFuzz.

2.10 Varredura de aplicativos Web

Resumo

No âmbito do Microsoft Visual C++ no Windows, a Microsoft recomenda:

  • Prefira TypeScript, JavaScript e ASP.NET para aplicativos Web.
  • Não escreva extensões da Web em C++. A Microsoft substituiu o ActiveX.
  • Quando o código é compilado para Emscripten/WASM, ele não é mais C++ e outras ferramentas se aplicam.
  • A Microsoft fornece o RESTler, um fuzzer de API REST com monitoração de estado.

Visão geral e principais qualidades

Um mecanismo de varredura de aplicativos da Web explora um aplicativo da Web rastreando suas páginas da Web e examinando-o em busca de vulnerabilidades de segurança. Esse rastreamento envolve a geração automática de entradas maliciosas e a avaliação das respostas do aplicativo. Criticamente, a varredura de aplicativos da Web deve abranger/suportar:

  • Cataloga todos os aplicativos Web em sua rede, incluindo os novos e desconhecidos, e dimensiona de um punhado de aplicativos para milhares.
  • Varredura profunda de versões de software, serviços de API SOAP e REST e APIs usadas por dispositivos móveis.
  • Inserção de primitivas de segurança no desenvolvimento e implantação de aplicações em ambientes DevOps. Esses primitivos funcionam com o rastreador.
  • Detecção de malware.

2.11 Verificar componentes de software incluídos

Resumo

Manipule seu código C++ da mesma forma que o código escrito em outras linguagens de programação e aplique qualquer ferramenta de Análise de Composição de Software (SCA) e Análise de Origem (OA) adotada por sua empresa ao seu código C++. Os fluxos de trabalho e a varredura de segurança devem ser projetados como parte dos sistemas de CI/CD (integração contínua e entrega contínua).

Defesa a montante

Para reduzir o risco de ataques em dependências upstream, fontes/componentes de terceiros devem ser armazenados em um ativo controlado pela empresa, no qual as ferramentas SCA e OA são executadas.

  • As ferramentas devem verificar e alertar quando vulnerabilidades são identificadas (incluindo bancos de dados públicos), como: Início | CVE
  • Execute análise estática em todos os componentes de software incluídos em seu aplicativo/repositório para identificar padrões de código vulneráveis.

Defesa de dependência

Execute e mantenha uma auditoria de dependências para validar se todas essas ocorrências são contabilizadas e cobertas por suas ferramentas SCA e OA.

  • Os componentes devem ser regularmente auditados e atualizados para as versões verificadas mais recentes.
  • Dependências de feed de pacotes.
  • As ferramentas SCA/OA cobrem e auditam todas as dependências de pacote provenientes de um único feed.

SBOM

Produza uma SBOM (lista de materiais de software) com seu produto listando todas as dependências, como:

  • origin (por exemplo, URL (Uniform Resource Locator))
  • version
  • consistência (por exemplo, hash de origem SHA-256) e outros meios para validar a consistência, como compilações determinísticas.
  • Exigir e auditar arquivos SBOM em dependências de software ou produzidos como parte de uma compilação, incluindo OSS (software de código aberto).
  • A Microsoft está padronizando e recomenda o SPDX (Software Package Data Exchange) versão 2.2 ou posterior | Linux Foundation como o formato de documento SBOM.
  • O determinismo de compilação pode ser usado para produzir independentemente binários idênticos em bits e fornecer verificações independentes de integridade:
    • Atestado de reprodutibilidade próprio ou de terceiros
    • Outras técnicas, como a assinatura binária por meio de uma fonte de certificado confiável, também podem fornecer algumas garantias de integridade binária.

Recursos adicionais

As soluções da Microsoft incluem as seguintes diretrizes e produtos: