Skip to main content

Conheça 3 tipos de vulnerabilidade de directory traversal em C/C++

Escrito por
Headshot of Kirill Efimov

Kirill Efimov

blog feature security beta

4 de abril de 2022

0 minutos de leitura

As vulnerabilidades de directory traversal (também conhecidas como vulnerabilidades de travessia de caminho) permitem que agentes mal-intencionados acessem pastas que não deveriam acessar. Neste artigo, vamos ver como funcionam as vulnerabilidades de directory traversal em servidores web escritos em C/C++ e como evitá-las.

Para nós, da equipe de Pesquisa de Segurança, os ecossistemas C e C++ ficaram fora do escopo por muito tempo, mas isso mudou quando a FossID se juntou à Snyk. A FossID era especialista em C/C++, o que nos permitiu iniciar alguns projetos para entender melhor a postura geral de segurança da informação dessas linguagens. Como resultado, encontramos muitas vulnerabilidades de alta gravidade e estamos animados para começar a compartilhar nossas descobertas.

Neste artigo, vamos nos concentrar na limitação inadequada de um nome de caminho a um diretório restrito (CWE-22). Há muitas variantes dessa vulnerabilidade e, segundo a lista CWE das 25 vulnerabilidades de software mais perigosas, ela continuará sendo muito disseminada e perigosa em 2022. Vamos explorar três tipos diferentes de directory traversal e relacionar as vulnerabilidades a exemplos de código vulnerável, correções e possíveis consequências.

Leitura arbitrária de arquivos

É bastante comum que servidores web implementem funcionalidades para servir recursos estáticos, como arquivos HTML, CSS e JS. Em geral, esses recursos ficam em pastas específicas do sistema de arquivos (por exemplo, /static, /www ou /assets). Uma vulnerabilidade de leitura arbitrária de arquivos ocorre quando o aplicativo web não higieniza corretamente o caminho até o arquivo estático, permitindo que o usuário use segmentos de caminho “../” para sair da pasta prevista e, eventualmente, ler arquivos arbitrários do disco.

Vamos direto ao exemplo dessa vulnerabilidade que descobrimos recentemente no framework web Crow. O Crow é um microframework C++ para executar serviços web.

Neste exemplo, vamos criar um aplicativo simples de “hello world”, composto por um único arquivo main.cpp:

#define CROW_MAIN
#include <crow_all.h>

int main()
{
    crow::SimpleApp app;

    CROW_ROUTE(app, "/")
    ([]() {
        return "Hello world!";
    });

    app.port(8000).run();
    return 0;
}

Para compilar o exemplo, usamos o Ubuntu: g++ -pthread -o server main.cpp. Depois disso, é possível executar o servidor digitando ./server no terminal.

Segundo a documentação, por padrão, o Crow serve arquivos estáticos da pasta /static, localizada no mesmo diretório do arquivo executável server. Podemos testar isso criando a pasta /static e um arquivo de texto dentro dela: mkdir static && echo “test” > static/test.txt

Agora, em outra janela do terminal, podemos executar curl para verificar se está funcionando:

curl http://localhost:8000/ retorna Hello world!.

curl http://localhost:8000/static/test.txt exibe test — que é, de fato, o conteúdo do arquivo static/test.txt.

Para explorar a vulnerabilidade, podemos executar curl --path-as-is "http://localhost:8000/static/../../etc/passwd". Na resposta, veremos o conteúdo do arquivo /etc/passwd. O truque está na flag --path-as-is, que desativa a normalização do caminho em URLs. Isso significa que o servidor receberá exatamente a URL informada, incluindo os segmentos “../”.

Impactos para a segurança

O vazamento de arquivos arbitrários do servidor de produção costuma ser um problema crítico: chaves SSH, várias credenciais e o código-fonte do servidor podem permitir que um agente mal-intencionado amplie o ataque e assuma o controle do servidor ou acesse dados confidenciais dos usuários.

O mesmo vale para dispositivos embarcados, nos quais servidores C++ são muito usados. Essa vulnerabilidade de directory traversal é comum em roteadores Wi-Fi, como os da NETGEAR, Belkin, TP-Link e outras marcas. Nesse caso, uma possível consequência é o roubo das credenciais do painel de administração e a obtenção de controle total da rede local.

Em alguns casos, o problema de directory traversal pode ser perigoso mesmo quando o servidor vulnerável está sendo executado localmente na sua máquina. Servidores web locais são usados com frequência em aplicativos híbridos para desktop e web, como os feitos com Electron. Para saber mais sobre esse tipo de vetor de ataque, leia nossa pesquisa sobre plugins vulneráveis do VS Code.

Como corrigir

A versão 0.3+4 do Crow corrigiu essa vulnerabilidade de directory traversal. O problema foi introduzido no arquivo app.h, onde o código simplesmente concatena parte da URL ao caminho da pasta estática e chama o método set_static_file_info para servir o arquivo:

res.set_static_file_info(CROW_STATIC_DIRECTORY + file_path_partial);

Nesse caso, a correção foi adicionar uma chamada a utility::sanitize_filename(path) no início do método set_static_file_info. sanitize_filename substitui todas as ocorrências de “..” por “_”, corrigindo a vulnerabilidade. A correção não é perfeita, pois o servidor não poderá servir arquivos cujo nome contenha “..”, mas isso provavelmente é raro e não deve causar grandes problemas.

Gravação arbitrária de arquivos

Vulnerabilidades de gravação arbitrária de arquivos são muito comuns em bases de código com funcionalidades de upload. A causa raiz é bastante parecida com a do caso anterior, mas um pouco menos evidente. Imagine um formulário HTML simples para enviar arquivos:

<form method=”post”>
<input type="file" name="avatar">
<input type="submit" value="submit">
</form>

Vamos selecionar um arquivo de texto chamado “test.txt” com o conteúdo “test”. O navegador fará uma requisição HTTP parecida com esta:

POST /upload HTTP/1.1
Host: localhost:8000
Content-Length: 150
Content-Type: multipart/form-data; boundary=----xxx
Connection: close

------xxx
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

test

------xxx--

Se você analisar o corpo da requisição com mais atenção, verá que ele contém mais informações do que apenas o conteúdo do arquivo. Em particular, há um campo de nome de arquivo, que pode conter segmentos “../” e precisa ser tratado corretamente pelo servidor.

Para demonstrar o problema, vamos usar uma vulnerabilidade de gravação arbitrária descoberta recentemente no popular framework de servidor web embarcado Mongoose.

O exemplo de upload de arquivos do repositório do Mongoose permite que usuários enviem arquivos em partes, o que é muito conveniente quando o dispositivo que executa o servidor tem pouca memória RAM. Podemos compilar e executar o exemplo simplesmente clonando o repositório e rodando o comando make na pasta do exemplo. O servidor começa imediatamente a escutar na porta 8000 e define /tmp como a pasta de destino dos arquivos enviados (conforme especificado na linha 13 do arquivo main.c.)

Agora, podemos usar curl para enviar arquivos. O comando curl -X POST --data-binary "test" http://localhost:8000/upload?offset=0&name=test.txt criará no servidor o arquivo /tmp/test.txt com o texto “test”.

Para explorar a vulnerabilidade, podemos inserir segmentos de caminho “../” antes do parâmetro de consulta name: curl -X POST --data-binary "pwned" http://localhost:8000/upload?offset=0&name=../malicious-file. O exploit criará então um arquivo malicious-file na raiz do sistema de arquivos.

Impactos para a segurança

Explorar vulnerabilidades de gravação arbitrária de arquivos não é tão simples quanto explorar vulnerabilidades de leitura arbitrária, mas, em muitos casos, ainda pode levar à execução remota de código (RCE). Muitas vezes, isso é possível porque um agente mal-intencionado consegue sobrescrever arquivos importantes do sistema, como /etc/init.d/, ou até mesmo os próprios arquivos executáveis do servidor.

Como corrigir

Assim como no exemplo anterior, o Mongoose corrigiu a vulnerabilidade removendo “..” do nome de arquivo fornecido pelo usuário. A correção foi publicada na versão 7.6.

Como recomendação geral, evite usar nomes de arquivo fornecidos pelo usuário. Em vez dos nomes originais, use strings aleatórias, registros de data e hora ou hashes MD5. Isso costuma ser aplicável a aplicativos com funcionalidade de upload e ajuda a evitar muitas outras vulnerabilidades, como XSS.

Zip Slip

O terceiro tipo de problema de directory traversal que vamos abordar é o Zip Slip. Como o nome sugere, ele envolve vulnerabilidades na lógica de extração de arquivos de arquivos compactados. Esse tipo é muito parecido com a gravação arbitrária de arquivos, mas tem uma superfície de ataque menor, pois a lógica de extração de arquivos compactados não é tão comum.

Para demonstrar a vulnerabilidade Zip Slip, vamos usar a versão vulnerável 6.1.4 do JUCE, um framework de código aberto para aplicativos C++ multiplataforma.

#include <juce_core/juce_core.h>

int main()
{
    juce::File file("archive.zip");
    juce::ZipFile zipFile(file);
    zipFile.uncompressTo(juce::File("data"));
    return 0;
}

Este é um aplicativo muito simples que usa a API do JUCE para extrair archive.zip para a pasta data.

Para ver como funciona, podemos criar um arquivo compactado de exemplo com um arquivo test.txt:

echo test > test.txt && zip archive.zip test.zip
  adding: test.zip (stored 0%)

Se compilarmos e executarmos o aplicativo de exemplo, a pasta data será criada e o arquivo test.txt ficará dentro dela. Mas e se criarmos um arquivo compactado com o arquivo um nível acima na hierarquia de arquivos?

echo bad > ../bad.txt && zip archive.zip ../bad.txt

Nesse caso, se executarmos o programa, o arquivo bad.txt ficará fora da pasta de destino data. Como você pode ver, os riscos de segurança do Zip Slip são exatamente os mesmos da gravação arbitrária de arquivos.

Há outras variantes do Zip Slip, como a extração de links simbólicos. Recomendamos a leitura do nosso artigo de pesquisa para entender melhor esse tema.

Como corrigir

A vulnerabilidade demonstrada foi corrigida na versão 6.1.5 do framework JUCE, junto com outra variante: o Zip Slip por meio de links simbólicos. O JUCE corrigiu a vulnerabilidade verificando se o caminho completo resolvido permanece dentro do diretório de destino.

Recomendações gerais de correção

Ao pesquisar essa classe de vulnerabilidade, a equipe de Pesquisa de Segurança da Snyk analisou muitos exemplos de código com diferentes lógicas relacionadas a caminhos no sistema de arquivos. Acreditamos ter encontrado uma regra geral que pode ajudar você a se proteger contra vulnerabilidades de directory traversal.

Se for possível evitar caminhos de arquivo fornecidos pelo usuário, evite!

Essa regra vale para muitos casos de lógica de upload e extração de arquivos compactados.

Mas, se você precisar usar caminhos de arquivo fornecidos pelo usuário, recomendamos seguir a lógica abaixo (implementada com std):

// #include <iostream>
// #include <string>
// #include <filesystem>
// #include <algorithm>

    // This is the path to your static files folder.
    std::string base_path("/tmp/path_to_the_content_root_directory/");
    // User input – possibly contains "../" path segments.
    std::string user_input("user_provided_path");

    // The "canonical" method resolves ".", ".." and symlinks.
    std::filesystem::path base_resolved_path(
        std::filesystem::canonical(base_path));
    // The "weakly_canonical" method does the same as "canonical" but not requires
    // the file to exist.
    std::filesystem::path requested_file_path(
        std::filesystem::weakly_canonical(base_resolved_path / user_input));

    // Using "equal" we can check if "requested_file_path" starts
    // with base_resolved_path. Because we previously canonicalized both paths
    // they can't contain any ".." segments, so this check is sufficient.
    if (std::equal(
            base_resolved_path.begin(),
            base_resolved_path.end(),
            requested_file_path.begin())) {
        std::cout << "It is safe to work with the file: " << requested_file_path << "\n";
    } else {
        std::cout << "Not safe! We have to throw an exception here.\n";
    }

O código acima requer pelo menos C++17, mas a mesma lógica pode ser implementada com boost. Se std e boost não estiverem disponíveis no seu ambiente, também é suficiente substituir “..” por strings vazias e, em seguida, substituir várias barras “/” consecutivas por uma única.

Além disso, se o seu aplicativo for multiplataforma e também for executado no Windows, considere normalizar os caracteres separadores de diretório. Na maioria dos casos, o Windows aceita tanto a barra (“/”) quanto a barra invertida (“\”) como separador de diretório. Isso significa que o payload “..\” equivale a “../” e também pode ser usado para realizar um ataque de directory traversal. A maneira mais simples de garantir que seu código seja seguro no Windows é substituir “/” por “\” antes de processar os caminhos.

Conclusão e principais descobertas

Quando profissionais de segurança da informação pensam em vulnerabilidades de C e C++, costumam se concentrar em problemas de baixo nível, como diferentes tipos de corrupção de memória. Embora esses problemas tenham grande impacto no ecossistema, também precisamos considerar o contexto dos aplicativos e prevenir vulnerabilidades de nível mais alto, além das de baixo nível. Quando iniciamos a pesquisa, nossa hipótese era que desenvolvedores web de C/C++ não davam atenção suficiente a problemas comuns da web. De fato, encontramos muitas vulnerabilidades de directory traversal:

  • CVE-2022-25299: gravação arbitrária de arquivos no Mongoose, servidor web embarcado.

  • CVE-2022-25297: gravação arbitrária de arquivos no Drogon, framework de aplicativos HTTP baseado em C++14/17.

  • CVE-2021-23520, CVE-2021-23521: Zip Slip no JUCE, framework de código aberto para aplicativos C++ multiplataforma.

  • CVE-2021-23514: path traversal no Crow, microframework C++.

  • CVE-2022-25298: path traversal no Webcc, cliente e servidor HTTP leve em C++.

Esta lista continua crescendo, e vamos atualizá-la à medida que os colaboradores publicarem suas correções.

Mantenha-se seguro!

Comece a jogar Capture the Flag

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