AI Hacking - Como os hackers utilizam a inteligência artificial nos ciberataques

Ler agora
Utilizamos inteligência artificial para as traduções dos sítios e, embora nos esforcemos por garantir a exatidão, estas podem nem sempre ser 100% precisas. Agradecemos a sua compreensão.

Proteção contra ataques de desserialização no Spring para Apache Kafka

por OPSWAT
Partilhar esta publicação
Uma imagem profissional de Nguyen Phu Hung e Tran Anh Huy, representando as suas filiações na HUTECH e na Universidade de Ciências de HCMC.

Os ataques de desserialização - a manipulação não autorizada de dados serializados para executar código malicioso - podem resultar em danos graves para as organizações. Os desenvolvedores e as equipes de segurança devem identificar proativamente as principais vulnerabilidades que revelam como, quando e onde o ataque ocorreu.  

Um exemplo recente dessa ameaça é o CVE-2023-34040, um vetor de ataque de desserialização no Spring para Apache Kafka que pode levar a RCE (execução remota de código). Na verdade, a prevalência de vulnerabilidades de desserialização é destacada pela lista OWASP Top 10, que inclui "desserialização de dados não confiáveis" como um dos 10 principais riscos de segurança de aplicativos da Web mais críticos.  

Ferramentas como o OPSWAT MetaDefender Core™ com seu mecanismo SBOMSoftware Bill of Materials) são essenciais na deteção e prevenção de ataques de desserialização. Estas ferramentas permitem que os programadores verifiquem e analisem eficientemente o seu código, garantindo que nenhuma vulnerabilidade é ignorada.  

Neste blogue, os nossos Graduate Fellows discutirão os pormenores do CVE-2023-34040 e a sua exploração, bem como a forma de proteger componentes de código aberto contra ameaças semelhantes. 

Sobre o CVE-2023-34040

O CVE-2023-34040 revela um vetor de ataque de desserialização no Spring para o Apache Kafka, que pode ser explorado quando é aplicada uma configuração invulgar. Esta vulnerabilidade permite a um atacante construir um objeto serializado malicioso num dos cabeçalhos de registo de exceção de desserialização, o que pode resultar em RCE. A vulnerabilidade afecta as versões 2.8.1 a 2.9.10 e 3.0.0 a 3.0.9 do Spring for Apache Kafka.

Uma captura de ecrã que apresenta as pontuações de gravidade CVSS 3.x para vulnerabilidades, com níveis de risco alto e médio do NIST e da VMware.

Especificamente, uma aplicação/consumidor é vulnerável nas seguintes configurações e condições específicas: 

  • A classe ErrorHandlingDeserializer não está configurada para a chave e/ou o valor do registo. 
  • As propriedades checkDeserExWhenKeyNull e/ou checkDeserExWhenValueNull do consumidor são definidas como true. 
  • As fontes não fidedignas têm permissão para publicar num tópico do Kafka.

Apache Kafka

O Apache Kafka, desenvolvido pela Apache Software Foundation, é uma plataforma distribuída de fluxo de eventos concebida para capturar, processar, responder e encaminhar fluxos de dados em tempo real a partir de várias fontes, incluindo bases de dados, sensores e dispositivos mobile . 

Por exemplo, pode transmitir notificações para serviços que reagem a actividades do cliente, como a conclusão da compra de um produto ou a realização de um pagamento.

Um diagrama que ilustra um pipeline de processamento de dados baseado no Apache Kafka, ligando microsserviços de produtos a microsserviços de SMS, notificações push e correio eletrónico.

No Apache Kafka, um evento - também designado por registo ou mensagem - serve como uma unidade de dados que representa uma ocorrência na aplicação sempre que os dados são lidos ou escritos. Cada evento inclui uma chave, um valor, um carimbo de data/hora e cabeçalhos de metadados opcionais.

Chave-binária
(pode ser nulo)
Valor binário
(pode ser nulo)
Tipo de compressão
[nenhum, gzip, snappy, lz4, zstd]
Cabeçalhos (opcional)
ChaveValor
ChaveValor
Partição + Desvio
Carimbo de data/hora (definido pelo sistema ou pelo utilizador)

Os eventos são armazenados de forma duradoura e organizados em tópicos. As aplicações cliente que enviam (escrevem) eventos para os tópicos do Kafka são designadas por produtores, enquanto as que subscrevem (lêem e processam) eventos são designadas por consumidores. 

Um fluxograma que mostra como a entrada do utilizador é processada através de um produtor, registada, consumida e entregue a um ponto final.

Spring para Apache Kafka

Para ligar o Apache Kafka ao ecossistema Spring, os programadores podem utilizar o Spring for Apache Kafka, que simplifica a integração em aplicações Java.  

O Spring para Apache Kafka oferece ferramentas e APIs robustas que simplificam o processo de envio e recebimento de eventos com o Kafka, permitindo que os desenvolvedores realizem essas tarefas sem codificação extensa e complexa.

Um gráfico que apresenta os logótipos do Spring Boot e do Apache Kafka, significando a integração para aplicações orientadas para eventos.

Serialização e desserialização

A serialização é um mecanismo de conversão do estado de um objeto numa cadeia de caracteres ou num fluxo de bytes. Por outro lado, a desserialização é o processo inverso, em que os dados serializados são convertidos novamente no seu objeto ou estrutura de dados original. A serialização permite que dados complexos sejam transformados para que possam ser salvos em um arquivo, enviados por uma rede ou armazenados em um banco de dados. A serialização e a desserialização são essenciais para a troca de dados em sistemas distribuídos e promovem a comunicação entre os vários componentes de uma aplicação de software. Em Java, writeObject() é utilizado para a serialização e readObject() é utilizado para a desserialização.

Um diagrama técnico que mostra como os objectos de dados são serializados para a memória, ficheiros ou bases de dados e posteriormente desserializados de volta para objectos.

Uma vez que a desserialização permite a conversão de um fluxo de bytes ou de uma cadeia de caracteres num objeto, o tratamento incorreto ou a falta de validação adequada dos dados de entrada pode resultar numa vulnerabilidade de segurança significativa, conduzindo potencialmente a um ataque RCE.

Análise de vulnerabilidade

De acordo com a descrição do CVE, os bolseiros OPSWAT configuraram o checkDeserExWhenKeyNull e o checkDeserExWhenValueNull para verdadeiro, de modo a ativar a vulnerabilidade de segurança. Ao enviar um registo com uma chave/valor vazio e ao efetuar uma análise detalhada através da depuração do consumidor à medida que este recebia um registo Kafka do produtor, os nossos bolseiros graduados descobriram o seguinte fluxo de trabalho durante o processamento do registo: 

Etapa 1: Receção de registos (mensagens)

Ao receber registos, o consumidor invoca o método invokeIfHaveRecords(), que depois chama o método invokeListener() para acionar um ouvinte de registos registado (uma classe anotada com a anotação @KafkaListener ) para o processamento efetivo dos registos. 

Uma captura de ecrã do código Java que define um contentor de ouvinte de mensagens Kafka para tratar registos num sistema orientado por eventos.

O método invokeListener() invoca então o método invokeOnMessage().

Etapa 2: Verificação dos registos

No método invokeOnMessage(), são avaliadas várias condições em relação ao valor do registo e às propriedades de configuração, que determinam subsequentemente o passo seguinte a executar.

Um trecho de código Java destacado que demonstra o tratamento de exceções para a desserialização de mensagens em um consumidor Kafka.

Se um registo tiver uma chave ou um valor nulos e as propriedades checkDeserExWhenKeyNull e/ou checkDeserExWhenValueNull estiverem explicitamente definidas como true, o método checkDeser() será chamado para examinar o registo.

Passo 3: Verificar a exceção a partir dos cabeçalhos

Em checkDesr(), o consumidor invoca continuamente getExceptionFromHeader() para recuperar quaisquer excepções dos metadados do registo, se presentes, e armazena o resultado numa variável chamada exceção.

O método Java checkDeser chama getExceptionFromHeader para tratar excepções de desserialização no processamento de mensagens Kafka.

Passo 4: Extrair a exceção dos cabeçalhos

O método getExceptionFromHeader() foi concebido para extrair e devolver uma exceção do cabeçalho de um registo Kafka. Primeiro, recupera o cabeçalho do registo e, em seguida, obtém o valor do cabeçalho, que é armazenado numa matriz de bytes.

O método Java getExceptionFromHeader extrai excepções de desserialização dos cabeçalhos das mensagens Kafka

Posteriormente, encaminha a matriz de bytes do valor do cabeçalho para o método byteArrayToDeserializationException() para tratamento posterior. 

Passo 5: Desserializar dados

Na byteArrayToDeserializationException(), a função resolveClass() é substituída para restringir a desserialização apenas às classes permitidas. Esta abordagem impede a desserialização de qualquer classe que não seja explicitamente permitida. O valor da matriz de bytes do cabeçalho só pode ser desserializado em byteArrayToDeserializationException() se satisfizer a condição definida em resolveClass(), que permite a desserialização exclusivamente para a classe DeserializationException.

O método Java byteArrayToDeserializationException converte uma matriz de bytes numa exceção de desserialização

No entanto, a classe DeserializationException estende a classe Exception padrão e inclui um construtor com quatro parâmetros. O último parâmetro, cause, representa a exceção original que desencadeou a DeserializationException, como uma IOException ou uma ClassNotFoundException.

Construtor Java para DeserializationException com parâmetros para mensagem, dados, sinalizador de chave e causa descartável

A classe Throwable serve como a superclasse para todos os objectos que podem ser lançados como excepções ou erros em Java. Na linguagem de programação Java, as classes de tratamento de exceções como Throwable, Exception e Error podem ser deserializadas com segurança. Quando uma exceção é desserializada, Java permite que as classes Throwable sejam carregadas e instanciadas com verificações menos exigentes do que as aplicadas a classes normais. 

Com base no fluxo de trabalho e na análise exaustiva, se os dados serializados corresponderem a uma classe maliciosa que herda da classe Throwable, esta pode contornar as verificações de condições. Isto permite a desserialização de um objeto malicioso, que pode executar código nocivo e resultar potencialmente num ataque RCE.

Exploração

Diagrama que mostra o processo de exploração de um ciberataque, desde a injeção de dados maliciosos por um atacante até à execução remota de código

Tal como indicado na análise, a exploração desta vulnerabilidade requer a geração de dados maliciosos enviados ao consumidor através do registo de cabeçalho Kafka. Inicialmente, o atacante deve criar uma classe maliciosa que estende a classe Throwable e, em seguida, utilizar uma cadeia de gadgets para conseguir a execução remota de código. Os gadgets são trechos de código exploráveis dentro da aplicação e, ao encadeá-los, o atacante pode chegar a um "gadget de sumidouro" que desencadeia acções prejudiciais. 

O seguinte é uma classe maliciosa que pode ser utilizada para explorar esta vulnerabilidade no Spring para Apache Kafka: 

A classe Java CustomExceptionClass demonstra uma vulnerabilidade de execução de carga útil utilizando comandos do PowerShell

Em seguida, uma instância da classe maliciosa é criada e passada como um argumento para o parâmetro cause no construtor da classe DeserializationException. A instância de DeserializationException é então serializada em um fluxo de bytes, que é usado posteriormente como o valor no cabeçalho do registro malicioso do Kafka.

Classe Java para construir um objeto serializado malicioso para explorações de desserialização
O método Java sendMessage constrói e envia uma mensagem Kafka, injectando um payload serializado malicioso se tanto a chave como os dados forem nulos

Se o atacante conseguir enganar a vítima para que esta utilize o seu produtor malicioso, pode controlar os registos Kafka enviados para o consumidor, criando uma oportunidade para comprometer o sistema. 

IU para enviar mensagens Kafka, mostrando um formulário de mensagem e mensagens recebidas de dois produtores

Quando o consumidor vulnerável recebe um registo Kafka do produtor malicioso contendo chaves e valores nulos, juntamente com uma instância serializada maliciosa no cabeçalho do registo, o consumidor processa o registo, incluindo o processo de desserialização. Isto conduz, em última análise, à execução remota de código, permitindo ao atacante comprometer o sistema.

Terminal e interface do remetente de mensagens Kafka, demonstrando a injeção de mensagens com a execução de um comando num ambiente Windows

Mitigando o CVE-2023-34040 com SBOM no MetaDefender Core

Para mitigar eficazmente os riscos associados ao CVE-2023-34040, as organizações necessitam de uma solução abrangente que forneça visibilidade e controlo sobre os seus componentes de código aberto.  

O SBOM, uma tecnologia fundamental do MetaDefender Core, fornece uma resposta poderosa. Ao agir como um inventário abrangente de todos os componentes de software, bibliotecas e dependências em uso, o SBOM permite que as organizações rastreiem, protejam e atualizem seus componentes de código aberto de maneira proativa e eficiente.

Captura de ecrã de um dashboard que apresenta um ficheiro XML bloqueado com vulnerabilidades críticas e elevadas

Com o SBOM, as equipas de segurança podem:

  • Localizar rapidamente componentes vulneráveis: Identificar imediatamente os componentes de código aberto afectados por ataques de desserialização. Isto garante uma ação rápida na aplicação de correcções ou na substituição das bibliotecas vulneráveis. 
  • Garantir a aplicação proactiva de patches e actualizações: Monitore continuamente os componentes de código aberto por meio do SBOM para ficar à frente das vulnerabilidades de desserialização. O SBOM pode detetar componentes desatualizados ou inseguros, permitindo atualizações oportunas e reduzindo a exposição a ataques. 
  • Manter a conformidade e os relatórios: O SBOM ajuda as organizações a cumprir os requisitos de conformidade, uma vez que as estruturas regulamentares exigem cada vez mais transparência nas cadeias de fornecimento de software.

Reflexões finais

As vulnerabilidades de desserialização são uma ameaça significativa à segurança que pode ser utilizada para explorar uma vasta gama de aplicações. Estas vulnerabilidades ocorrem quando uma aplicação desserializa dados maliciosos, permitindo que os atacantes executem código arbitrário ou acedam a informação sensível. A vulnerabilidade CVE-2023-34040 no Spring para Apache Kafka serve como um forte lembrete dos perigos dos ataques de desserialização. 

Para evitar ataques de desserialização, é essencial implementar ferramentas avançadas, como o OPSWAT MetaDefender Core e sua tecnologia SBOM. As organizações podem obter uma visibilidade profunda da sua cadeia de fornecimento de software, garantir a correção atempada de vulnerabilidades e proteger-se contra o cenário de ameaças em constante evolução. Proteger proactivamente os componentes de código aberto não é apenas uma prática recomendada - é uma necessidade para proteger os sistemas modernos contra a exploração potencial.


Mantenha-se atualizado com OPSWAT!

Inscreva-se hoje para receber as últimas actualizações da empresa, histórias, informações sobre eventos e muito mais.