Skip to main content

5 dicas essenciais de segurança para C++

Escrito por
Headshot of DeveloperSteve Coochin

DeveloperSteve Coochin

feature plus plus

19 de julho de 2022

0 minutos de leitura

C++ se tornou uma parte fundamental do setor de tecnologia atual. A linguagem é usada para diversas finalidades, como aplicações para desktop e servidores, jogos, realidade virtual, firmware para a Internet das Coisas e até como base para muitas linguagens de programação modernas.

Desde seu lançamento inicial, em 1985, como uma extensão da linguagem de programação C, o C++ foi projetado com foco na programação de sistemas e no desenvolvimento de recursos embarcados.

Como acontece com qualquer tecnologia de desenvolvimento amplamente adotada ou voltada para usuários, é importante incorporar medidas de segurança em toda a aplicação. Confira algumas dicas de segurança para ter em mente ao desenvolver com C++.

1. Valide e higienize as entradas nas aplicações C++

Lidar com entradas fornecidas por usuários é fundamental para proteger uma aplicação contra acessos indevidos. O C++ tem sido muito usado ao longo dos anos por permitir a manipulação de memória orientada a objetos — recurso bastante útil no desenvolvimento de aplicações, mas que também pode ser explorado como vetor de ataque por agentes maliciosos.

Uma opção é usar a função std:cin para lidar com variáveis. Ela pode ser usada de algumas maneiras, por exemplo:

int main()
{
    int varnum;

    while (true) {
        cout << "Enter a number: ";
        if (cin >> varnum) {
            break;
        } else {
            cout << "Enter a valid number!\n";
            cin.clear();
            cin.ignore(numeric_limits<std::streamsize>::max(), '\n');
        }
    }

    return EXIT_SUCCESS;
}

Nesta função, cin.clear() limpa os sinalizadores de status de erro, e cin.ignore() remove todos os caracteres restantes no fluxo até o caractere de nova linha. É importante tratar essas mensagens de erro para que elas não apareçam na saída exibida ao usuário final. Mensagens de erro e alerta podem revelar vetores de ataque indesejados.

2. Estouro ou subfluxo de buffer

Ao lidar com entradas de usuários em C++, é importante verificar o comprimento, o intervalo, o formato e o tipo dos dados. A interação mais granular com o sistema operacional significa que, quando funções recebem um buffer de entrada muito grande, pode ocorrer um ataque de estouro de buffer.

O estouro aritmético ocorre quando o resultado de uma operação excede os valores que o tipo da expressão pode representar. Por exemplo:

uint16_t x = 65535;
x++;

std::cout << x;

A função acima retornará 0, pois x é um inteiro sem sinal que pode representar valores entre 0 e 65535. Quando esse intervalo é excedido, o valor não pode ser representado por unit16_t, o que causa um estouro.

Uma forma de lidar com estouros e subfluxos é definir uma captura ao processar um tipo de variável, verificando se o valor está entre os limites mínimo e máximo — e detectando, assim, qualquer valor que ultrapasse esses limites. Com templates, você define esse intervalo e cria uma captura genérica reutilizável para essas ocorrências.

#include <limits>

template <typename V>
constexpr bool AdditionOverflows(const V& a, const V& b) {
    return (b >= 0) && (a > std::numeric_limits<V>::max() - b);
}

template <typename V>
constexpr bool AdditionUnderflows(const V& a, const V& b) {
    return (b < 0) && (a < std::numeric_limits<V>::min() - b);
}

É importante evitar comportamentos indefinidos ao usar C++. A maioria dos compiladores avisa quando há comportamentos indefinidos durante a compilação da aplicação. O GCC e o Clang, por exemplo, oferecem a -fsanitize=undefined flag.

Além disso, tenha cuidado ao usar funções como strcpy() e strcat() na biblioteca do C++. Embora sejam úteis para lidar com variáveis, elas podem causar estouros e subfluxos se o valor armazenado exceder a capacidade do tipo de armazenamento.

3. Operações incorretas no sistema de arquivos podem gerar vulnerabilidades de travessia de diretórios

O tratamento inadequado de arquivos pode levar a vulnerabilidades de travessia de diretórios. Isso é perigoso em qualquer framework, mas especialmente em um sistema operacional com uma linguagem granular como C++. Em C++, os arquivos são tratados como fluxos e como abstrações que representam a entrada e a saída de dados. Um fluxo pode ser representado por uma sequência de caracteres de tamanho indefinido, dependendo da estrutura do código.

O C++ oferece várias técnicas para manipular arquivos, incluindo ifstream(), ofstream() , e fstream(). Essas funções derivam dos grupos de classes fstreambase e iostream e foram projetadas para gerenciar os arquivos fisk declarados em fstream.

As classes de manipulação de arquivos se diferenciam da seguinte forma:

  • ofstream: essa classe representa o fluxo de saída e é usada para criar arquivos e gravar informações neles.

  • ifstream: essa classe representa o fluxo de entrada de arquivos e é usada para ler informações de arquivos.

  • fstream: essa classe pode ser usada para ler e gravar arquivos.

É importante garantir que os fluxos de arquivos sejam tratados com segurança. Comece usando new_file.open() para iniciar um novo arquivo e, em seguida, grave o fluxo no arquivo. Por fim, feche o arquivo quando terminar e inclua uma instrução catch para tratar erros que possam ocorrer durante a operação.

Por exemplo:

#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    fstream newFile; 
    newFile.open("file_write.txt",ios::out);  

    if(!newFile) 
    {
        cout<<"File failed";
    }
    else
    {
        cout<<"File created";
        newFile<<"File write";    //File write
        newFile.close(); // Close file 
    }   
    return 0;
}

4. Limite o uso de system() e de chamadas de execução de processos para evitar injeção de comandos

Assim como em muitas outras linguagens, é preciso ter muito cuidado ao interagir com o sistema por meio de funções no código. Em C++, funções como system() devem ser evitadas ou usadas com cautela. Essas funções de sistema variam de acordo com o chipset e o hardware em que a aplicação é executada.

Também é importante garantir que a aplicação seja instalada e executada de modo a permitir funções executáveis adicionais, como exec(), execp() , e CreateProcess(). No entanto, elas também devem ser usadas com cautela, especialmente porque podem dar acesso a partes essenciais do sistema operacional.

Lembre-se de que aplicações em C++ têm acesso muito mais granular a informações críticas do sistema, o que pode expor mais atividades maliciosas e caminhos vulneráveis.

5. Analise as bibliotecas do projeto com uma ferramenta abrangente de análise estática de código

Acompanhar os caminhos potencialmente vulneráveis nas bibliotecas e dependências do projeto é essencial para manter a segurança. Esses caminhos podem permitir ações indevidas por agentes maliciosos, especialmente em projetos C++ instalados em diferentes sistemas e níveis de arquitetura.

A análise estática de código, ou SCA, é útil porque pode alertar você sobre possíveis vulnerabilidades durante o desenvolvimento de uma aplicação e avisar quando uma vulnerabilidade é identificada no projeto implantado.

Como começar a analisar código C++ com SCA no Snyk

Para começar a usar o Snyk, instale o Snyk CLI no seu ambiente de desenvolvimento. Há várias maneiras de instalar a CLI, incluindo npm, Yarn e executáveis independentes.

Em seguida, conecte a CLI à sua conta do Snyk. Se ainda não tiver uma conta, é rápido e fácil se cadastrar. Acesse a página de cadastro e siga as etapas para começar.

Depois de instalar a CLI e entrar na sua conta do Snyk, execute snyk auth na linha de comando para concluir a conexão da conta. Isso vai gerar um link de autenticação e abrir automaticamente a página de autenticação no navegador. Clique em Authenticate e pronto: você já pode usar o Snyk CLI.

Página de autenticação da CLI do Snyk com o título “Autenticar na CLI”, instruções e um botão Autenticar.

Agora, para concluir, acesse o diretório raiz do projeto que você quer analisar e execute snyk test –unmanaged. O processo de análise localizará a pasta na raiz do projeto que contém as bibliotecas de código aberto e fará a verificação.

Saída do terminal listando vulnerabilidades altas e críticas no FFmpeg, incluindo gravações fora dos limites, estouro de inteiro e referência a ponteiro nulo.

Ao concluir, você verá todos os resultados encontrados e poderá investigá-los em mais detalhes. Um recurso de que gosto bastante é poder explorar os problemas identificados pelos links da análise e entender melhor cada vulnerabilidade.

Página da Snyk Vulnerability Database sobre uma vulnerabilidade de valor de retorno não verificado no ffmpeg, classificada como Crítica, com nota 9,8.

Cadastre-se agora e comece a analisar

Para quem desenvolve, trabalha com DevOps ou atua em tecnologia, priorizar a segurança e fazer tudo ao nosso alcance para proteger as aplicações é fundamental. Incorporar a segurança ao desenvolvimento de aplicações ajuda a proteger os usuários finais e manter seguros tanto eles quanto seus dados.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo sob demanda ao nosso workshop virtual introdutório.