Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
cve-2026-22732-poc — Prova de conceito demonstrando a CVE-2026-22732, uma falha do Spring Security em que setIntHeader("Content-Length") remove todos os cabeçalhos de segurança, com builds vulneráveis e corrigidos. | Kitploit
Ferramentas/GitHubGitHub/dylan-chainguard/cve-2026-22732-poc
Ferramentas DefensivasAnálise de VulnerabilidadesExploraçãoVirtualização para SegurançaSegurança WebAprendizado e Educação
GitHubdylan-chainguard/cve-2026-22732-poc

cve-2026-22732-poc

Prova de conceito demonstrando a CVE-2026-22732, uma falha do Spring Security em que setIntHeader("Content-Length") remove todos os cabeçalhos de segurança, com builds vulneráveis e corrigidos.

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Ver Repositório
há 2h 52mAinda não revisado
Compartilhar

CVE-2026-22732 — Prova de Conceito

O Spring Security descarta silenciosamente os cabeçalhos de segurança da resposta HTTP. Apenas para fins de demonstração / educacionais; execute-o apenas contra este aplicativo local.

CVECVE-2026-22732 (CWE-425), publicado em 2026-03-19
CVSS 3.19.1 CRÍTICO — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Dependência diretaspring-boot-starter-web + spring-boot-starter-security 2.7.18
Componente vulnerávelspring-security-web / -config / -core 5.7.11 — apenas transitivo, nunca nomeado no pom.xml
Componente corrigidospring-security-web 5.7.14-0.cgr.2, alcançado por uma única alteração de <version> — veja a transição
Verificado emTomcat 9.0.118, JDK 17.0.18, macOS arm64

Intervalos afetados: 5.7.0–5.7.21, 5.8.0–5.8.23, 6.3.0–6.3.14, 6.4.0–6.4.14, 6.5.0–6.5.8, 7.0.0–7.0.3. O Spring Boot 2.7.18 fixa o Spring Security 5.7.11, exatamente dentro do primeiro intervalo:

root@kitploit:~
$ mvn dependency:tree -Dincludes=org.springframework.security
+- org.springframework.security:spring-security-config:jar:5.7.11:compile
|  \- org.springframework.security:spring-security-core:jar:5.7.11:compile
\- org.springframework.security:spring-security-web:jar:5.7.11:compile

Executando

root@kitploit:~
./run.sh        # terminal 1 — compila e inicia na :8080 (fixa o JDK 17)
./exploit.sh    # terminal 2 — aciona todos os endpoints, compara os cabeçalhos

O run.sh fixa o JAVA_HOME porque o Spring Boot 2.7.x não consegue rodar no JDK 25 que o mvn resolve por padrão nesta máquina. Substitua com JAVA_HOME_17=/path/to/jdk17.

Ele também compila com -s settings-chainguard.xml por padrão, porque o parent corrigido não está no Maven Central. Defina MAVEN_SETTINGS=/path/to/your/settings.xml para apontar para outro local, ou MAVEN_SETTINGS= para compilar puramente a partir do Central — o que funciona apenas para o 2.7.18 original.

O exploit.sh lê as versões reais de spring-security-web e spring-boot de dentro de target/*.jar, então seu banner sempre reporta o que está realmente em execução em vez de uma string fixa.

A configuração

O SecurityConfig não aplica nenhuma customização de cabeçalho — os padrões do Spring Security estão em vigor, que é exatamente com o que um aplicativo consciente de segurança conta. Todo endpoint retorna o mesmo corpo sensível:

root@kitploit:~
{"account":"4111-1111-1111-1111","holder":"D. Havelock","balance":"82914.55"}

A única coisa que varia é como o controller escreve a resposta.

Resultados medidos

root@kitploit:~
BASELINE  valor de retorno padrão do Spring MVC
  /safe/account                    OK        todos os 6 cabeçalhos entregues

CONTROL   getOutputStream(), corpo > buffer de 8 KB
  /vuln/stream/account             OK        todos os 6 cabeçalhos entregues

CONTROL   response.flushBuffer() explícito
  /vuln/flush/account              OK        todos os 6 cabeçalhos entregues

EXPLOIT   setIntHeader("Content-Length", n)  <-- CVE-2026-22732
  /vuln/content-length/account     BYPASSED  TODOS os 6 cabeçalhos de segurança descartados

BY DESIGN aplicação define seu próprio cabeçalho Expires (NÃO é este CVE)
  /vuln/cache/account              PARTIAL   Cache-Control + Pragma descartados

Apenas /vuln/content-length/account muda de estado quando o CVE é corrigido, então é o único endpoint do qual o exploit.sh deriva seu veredito. Os demais são controles.

O CVE — setIntHeader("Content-Length", n) → bypass total

Três linhas de código de controller aparentemente comum removem todos os cabeçalhos que o Spring Security prometeu:

root@kitploit:~
response.setContentType("application/json");
response.setIntHeader("Content-Length", body.length);
response.getOutputStream().write(body);
root@kitploit:~
$ curl -sD - -o /dev/null http://localhost:8080/vuln/content-length/account
HTTP/1.1 200
Content-Type: application/json
Content-Length: 77
Date: Tue, 01 Sep 2026 00:53:18 GMT

Sem X-Frame-Options, sem X-Content-Type-Options, sem Cache-Control, sem Pragma, sem Expires, sem X-XSS-Protection. Compare com /safe/account, que carrega todos os seis. A resposta é enquadrável por qualquer origem, sujeita a MIME-sniffing e cacheável — enquanto serve um número de cartão.

O caso by-design — cabeçalho de cache definido pela aplicação → supressão de cache

Corrigido: uma versão anterior deste README chamava isso de "exploit 2" e afirmava ser a condição documentada pelo advisory. Não faz parte do CVE-2026-22732 e nenhuma atualização o corrige. O CacheControlHeadersWriter é byte a byte idêntico em 5.7.11, 5.7.14-0.cgr.2, 6.5.8 (último vulnerável) e 6.5.9 (primeiro corrigido) — verificado comparando os jars de código-fonte. Seu Javadoc declara o comportamento explicitamente: "Inserts headers to prevent caching if no cache control headers have been specified."

Ainda vale a pena demonstrar, porque o vazamento é real e o risco residual sobrevive à correção. O CacheControlHeadersWriter desiste se Cache-Control, Expires ou Pragma já estiver presente, então definir qualquer um dos três suprime todas as diretivas no-store do Spring Security. Uma linha bem-intencionada basta:

root@kitploit:~
response.setHeader("Expires", "Thu, 01 Jan 2099 00:00:00 GMT");
root@kitploit:~
/safe/account                  NOT cacheable  (Cache-Control: no-cache, no-store, max-age=0, must-revalidate)
/vuln/cache/account            CACHEABLE      (Cache-Control: absent / Expires: Thu, 01 Jan 2099 00:00:00 GMT)
/vuln/content-length/account   CACHEABLE      (Cache-Control: absent / Expires absent)

Expires conta como "presente" na tabela acima, mas com um valor amigável ao atacante escolhido pela aplicação — o Expires: 0 do Spring Security foi substituído, não apenas descartado. Dados de portador de cartão agora são armazenáveis por todo navegador e proxy compartilhado no caminho até 2099.

Na build corrigida, /vuln/content-length/account muda para NOT cacheable, enquanto /vuln/cache/account permanece exatamente como acima. Apenas código da aplicação ou um proxy reverso o corrige — o que é a coisa útil a dizer em voz alta numa demonstração: atualizar a biblioteca fecha o CVE e deixa isso intacto.

Dois resultados negativos, mantidos de propósito

Vários write-ups amplamente divulgados — incluindo um repositório público de reprodução — listam response.getOutputStream() e response.flushBuffer() como gatilhos, explicando que "a resposta é commitada antes que o Spring Security possa injetar seus cabeçalhos". No Spring Security 5.7.11 isso está errado. Ambos os endpoints entregam todos os seis cabeçalhos.

/diag/committed mostra por que a explicação não se sustenta. Após uma escrita de 12 KB, a resposta realmente é commitada dentro do controller, mas os cabeçalhos ainda chegam:

root@kitploit:~
>>> DIAG response.isCommitted() after 12048 byte write = true
    | wrapper class = org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterResponse

O OnCommittedResponseWrapper sobrescreve flushBuffer() e as escritas do output-stream, então ele emite seus cabeçalhos antes desses commits. A ordenação de commit por si só não é o bug; o caminho do Content-Length declarado é. O próprio advisory do Spring nunca endossa a história da ordenação de commit.

Manter esses dois endpoints torna a PoC falseável: mostra o que não se reproduz tão claramente quanto o que se reproduz, e ambos permanecem verdes ao longo da correção, o que é o que torna o único endpoint que de fato muda significativo.

A transição vulnerável → corrigida

Uma linha no pom.xml, nada mais. Sem alteração de código-fonte, sem alteração de propriedade, sem salto de versão major do Spring Boot:

root@kitploit:~
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.18</version>            <!-- vulnerable -->
  <version>2.7.18-0.cgr.3</version>    <!-- patched   -->
</parent>

Isso refixa spring-security.version de 5.7.11 para 5.7.14-0.cgr.2 (e spring-framework.version de 5.3.31 para 5.3.39-0.cgr.4) através do spring-boot-dependencies do parent. Medido:

2.7.182.7.18-0.cgr.3
/safe/accountOK 6/6OK 6/6
/vuln/stream/accountOK 6/6OK 6/6
/vuln/flush/accountOK 6/6OK 6/6
/vuln/content-length/accountBYPASSED 0/6OK 6/6
/vuln/cache/accountPARTIAL 4/6PARTIAL 4/6 (by design)
veredito do exploit.shVULNERABLEPATCHED

O backport é a correção upstream

Comparando os jars de código-fonte do spring-security-web, apenas um arquivo importa. 5.7.11 → 5.7.14-0.cgr.2 adiciona sobrescritas de setHeader / setIntHeader / addIntHeader ao OnCommittedResponseWrapper, cada uma roteando Content-Length através de setContentLength():

root@kitploit:~
@Override
public void setIntHeader(String name, int value) {
    checkContentLengthHeader(name, value);   // <-- added
    super.setIntHeader(name, value);
}

Antes da correção, apenas addHeader fazia isso, então setIntHeader("Content-Length", n) deixava o comprimento rastreado do wrapper em 0, onResponseCommitted() nunca disparava, e o HeaderWriterFilter nunca escrevia seus cabeçalhos antes que o Tomcat commitasse a resposta.

O mesmo trecho aparece literalmente ao comparar o upstream 6.5.8 (último vulnerável) com 6.5.9 (primeiro corrigido), então esta é a correção oficial portada, não uma reimplementação. A build da Chainguard adiciona duas verificações de nulo que o upstream 6.5.9 não tem (value != null na sobrecarga de String, e (csq != null) ? csq.length() : 4 em append).

Outras mitigações

O Spring Security 5.7.x está em fim de vida no upstream; correções OSS chegam apenas em 6.4.15 / 6.5.9 / 7.0.4+. Se uma 5.7.x recompilada não for uma opção:

  1. Atualizar para fora da 5.7.x — uma migração para o Spring Boot 3.x.
  2. Workaround — definir HeaderWriterFilter.shouldWriteHeadersEagerly = true via um ObjectPostProcessor. Segundo o advisory, isso altera o comportamento: cabeçalhos escritos pela aplicação então sobrescrevem apenas cabeçalhos específicos em vez de suprimir os do Spring Security. Este também corrige /vuln/cache/account, o que o bump de versão não faz.
  3. Suporte comercial — backports do Tanzu Spring Enterprise para 5.7.x/5.8.x.
  4. Defesa em profundidade — definir os cabeçalhos no proxy reverso / ingress para que um cabeçalho de aplicação descartado não seja o único controle. Esta é a única opção listada que cobre ambos os endpoints.

Nenhuma delas está integrada a este projeto, então o comportamento vulnerável é o padrão e o estado corrigido é alcançável pela única alteração de <version> acima.

Corrigindo o Tomcat embutido sem atualizar o Spring Boot

O Boot 2.7.18 fixa o Tomcat 9.0.83, que o grype . sinaliza com 34 CVEs (4 Críticos). Todos eles são corrigidos na 9.0.118 ou abaixo, e a 9.0.118 é a versão 9.0.x mais recente — então uma propriedade limpa o conjunto:

root@kitploit:~
<properties>
  <tomcat.version>9.0.118</tomcat.version>
</properties>

O spring-boot-dependencies declara cada artefato tomcat-embed-* através dessa única propriedade, então sobrescrevê-la refixa core, el e websocket juntos. Verificado:

root@kitploit:~
$ mvn dependency:tree | grep tomcat-embed
tomcat-embed-core:jar:9.0.118:compile
tomcat-embed-el:jar:9.0.118:compile
tomcat-embed-websocket:jar:9.0.118:compile

Restrição: permaneça na linha 9.0.x. O Tomcat 10+ moveu a Servlet API para jakarta.* enquanto o Spring Framework 5.3 compila contra javax.servlet, então um bump para 10.x/11.x falha em tempo de execução com NoClassDefFoundError nos tipos de servlet.

Corrigindo todo o resto, ainda dentro de cada linha major

O mesmo mecanismo aplicado ao restante das dependências gerenciadas do Boot 2.7.18. Sem bumps de versão major, e sem atualização do Spring Boot:

PropriedadePadrão do Boot 2.7.18Fixado aquiMotivo do teto
tomcat.version9.0.839.0.118última 9.0.x; 10+ é jakarta.*
spring-framework.version5.3.315.3.39última 5.3.x OSS no Central
jackson-bom.version2.13.52.22.2última 2.x
log4j2.version2.17.22.26.1última 2.x
snakeyaml.version1.301.33última 1.x; a correção para o CVE restante é a 2.0
logback.version1.2.121.2.13última 1.2.x — veja abaixo
spring-security.version5.7.11deixada em pazé o objeto da demonstração

Essas sobrescritas interagem com o parent corrigido, então saiba o que elas fazem antes de demonstrar. No 2.7.18-0.cgr.3 o parent já fornece tomcat.version 9.0.118, logback.version 1.2.13 e snakeyaml.version 1.33 — essas três linhas se tornam duplicatas exatas e podem ser removidas sem alterar nada. As linhas jackson-bom.version e log4j2.version ainda fazem trabalho real: o parent corrigido mantém os padrões 2.13.5 / 2.17.2 do Boot, então as sobrescritas vencem e essas duas dependências resolvem para builds upstream puros em vez de builds da Chainguard. spring-framework.version está comentada de propósito, o que é o que deixa passar o 5.3.39-0.cgr.4 do parent.

Progressão do grype .

EstadoAchadosDetalhamento
Boot 2.7.18 original997C / 39H / 38M / 15L
+ bump do Tomcat653C / 23H / 29M / 10L
+ todos os bumps in-major433C / 12H / 18M / 10L

Totalmente limpos: Tomcat (34), Jackson (7), log4j (1). No geral 99 → 43, Altos 39 → 12.

Verificado após cada bump: o aplicativo inicia em Tomcat/9.0.118, todos os seis endpoints retornam 200, e o CVE se reproduz byte a byte. O Spring Boot ainda é 2.7.18 e o Spring Security ainda é 5.7.11, então o CVE-2026-22732 permanece intocado — que é o ponto desta seção, e também seu limite honesto: corrigir tudo ao redor não faz nada pelo CVE do framework de aplicação. Corrigir aquele requer o bump do parent, não uma propriedade.

Por que o Logback para na 1.2.13

A 1.x mais recente é a 1.6.3 — mesmo major, então nominalmente no escopo. Não funciona. O Logback 1.3+ substituiu o StaticLoggerBinder do SLF4J 1.7 pelo provedor ServiceLoader do SLF4J 2.x, e o LogbackLoggingSystem do Boot 2.7 chama StaticLoggerBinder diretamente. Medido com 1.5.38:

root@kitploit:~
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder"
Caused by: java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(...:304)

Escapar disso exige SLF4J 2.x (um bump major) e um sistema de logging do Boot 3.x. A 1.2.13 é o teto real, deixando 6 achados do Logback (2 Médios, 4 Baixos) não corrigíveis nesta linha.

Os 43 que permanecem

ComponentePor que não pode ser corrigido in-major
spring-webmvc / -expression / -core / -context (25)5.3.39 é a última 5.3.x OSS; 14 dos 15 achados do webmvc não têm correção alguma, e a 5.3.42 que o grype cita é apenas comercial
logback-core (6)precisa do SLF4J 2.x, veja acima
spring-security-* (8)linha em EOL; o CVE-2026-22732 é deliberado
spring-boot / -autoconfigure (3)nenhuma correção publicada para 2.7.x
snakeyaml (1)o CVE-2022-1471 é corrigido apenas na 2.0

Dois dos três Críticos restantes merecem ser lidos adequadamente em vez de por pontuação:

  • CVE-2016-1000027 (spring-web, correção 6.0.0) — desserialização via HttpInvokerServiceExporter. Este aplicativo não usa HTTP Invoker, então não é alcançável aqui.
  • CVE-2024-38821 (spring-security-web, correção 5.7.13) — bypass de autenticação de recurso estático no WebFlux. Este é um aplicativo servlet, então também não é alcançável. É corrigível in-major (5.7.13/5.7.14 estão no Central) e foi deixado apenas para manter esta seção fixada na 5.7.11. O parent corrigido o limpa como efeito colateral, já que 5.7.14-0.cgr.2 está além da versão de correção.
  • CVE-2026-22732 — intencional no estado vulnerável; limpo pelo bump do parent.

O resíduo é estrutural: o Spring Framework 5.3.x e o Spring Security 5.7.x estão ambos em EOL. Isso, não o Tomcat ou o Jackson, é o verdadeiro argumento para uma migração para o Boot 3.x.

Estrutura

root@kitploit:~
pom.xml                     parent + 2 starters, nada mais. Alterne o <version> para trocar de estado.
run.sh                      compila + executa no JDK 17, via settings-chainguard.xml
exploit.sh                  diff de cabeçalhos, impacto de cache, verificação de clickjacking, veredito com escopo do CVE
settings-chainguard.xml     repositório Chainguard Libraries -- necessário para o parent corrigido
src/main/java/com/example/poc/
  PocApplication.java       @SpringBootApplication
  SecurityConfig.java       permitAll, zero customização de cabeçalho
  AccountController.java    baseline, 2 exploits, 2 controles, 1 diagnóstico

A autenticação é permitAll e o CSRF está desativado para que o curl funcione sem autenticação — nenhum dos dois faz parte deste CVE.

Fontes

  • spring.io/security/cve-2026-22732 — advisory oficial
  • GHSA-mf92-479x-3373
  • NVD CVE-2026-22732
  • Broadcom / Tanzu write-up
  • Análise da HeroDevs
  • semgrep/cve-2026-22732-demo — a reprodução cujas alegações de stream/flush não se sustentaram aqui
  • Red Hat Bugzilla #2449306
Baixar ferramenta