Skip to content
KitploitKITPLOIT
도구블로그
Log in
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
cve-2026-22732-poc — CVE-2026-22732를 시연하는 개념 증명으로, `setIntHeader("Content-Length")`가 모든 보안 헤더를 제거하는 Spring Security 취약점이며, 취약 빌드와 패치된 빌드를 포함합니다. | Kitploit
도구/GitHubGitHub/dylan-chainguard/cve-2026-22732-poc
Defensive ToolsVulnerability AnalysisExploitationSecurity VirtualizationWeb SecurityLearning & Education
GitHubdylan-chainguard/cve-2026-22732-poc

cve-2026-22732-poc

CVE-2026-22732를 시연하는 개념 증명으로, `setIntHeader("Content-Length")`가 모든 보안 헤더를 제거하는 Spring Security 취약점이며, 취약 빌드와 패치된 빌드를 포함합니다.

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
11720일 전아직 검토되지 않음

CVE-2026-22732 — 개념 증명

Spring Security가 HTTP 응답 보안 헤더를 조용히 누락시킨다. 데모 / 교육용으로만 사용하며, 이 로컬 앱 외에는 어떤 대상에도 실행하지 말 것.

CVECVE-2026-22732 (CWE-425), 2026-03-19 공개
CVSS 3.19.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
직접 의존성spring-boot-starter-web + spring-boot-starter-security 2.7.18
취약 컴포넌트spring-security-web / -config / -core 5.7.11 — 전이 의존성일 뿐, pom.xml에 명시된 적 없음
수정 컴포넌트spring-security-web 5.7.14-0.cgr.2, <version> 한 번 변경으로 도달 — 전환 과정 참조
검증 환경Tomcat 9.0.118, JDK 17.0.18, macOS arm64

영향 범위: 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. Spring Boot 2.7.18은 Spring Security 5.7.11을 고정하며, 이는 첫 번째 범위에 정확히 들어간다:

$ 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

실행하기

./run.sh        # 터미널 1 — 빌드 후 :8080에서 시작 (JDK 17 고정)
./exploit.sh    # 터미널 2 — 모든 엔드포인트를 구동하고 헤더를 비교

run.sh는 JAVA_HOME을 고정하는데, Spring Boot 2.7.x는 이 머신에서 mvn이 기본으로 선택하는 JDK 25에서 실행될 수 없기 때문이다. JAVA_HOME_17=/path/to/jdk17로 재정의할 수 있다.

또한 기본적으로 -s settings-chainguard.xml로 빌드하는데, 패치된 parent가 Maven Central에 없기 때문이다. 다른 위치를 가리키려면 MAVEN_SETTINGS=/path/to/your/settings.xml을 설정하거나, MAVEN_SETTINGS=로 순수하게 Central에서만 빌드할 수 있다 — 이는 순정 2.7.18에만 통한다.

exploit.sh는 target/*.jar에서 실제 spring-security-web과 spring-boot 버전을 읽어들이므로, 배너는 항상 하드코딩된 문자열이 아니라 실제로 실행 중인 것을 보고한다.

구성

SecurityConfig는 헤더 커스터마이징을 전혀 적용하지 않는다 — Spring Security의 기본값이 그대로 적용되며, 이는 보안을 고려한 앱이 바로 의존하는 것이다. 모든 엔드포인트는 동일한 민감한 본문을 반환한다:

{"account":"4111-1111-1111-1111","holder":"D. Havelock","balance":"82914.55"}

유일하게 달라지는 것은 컨트롤러가 응답을 작성하는 방식이다.

측정 결과

BASELINE  standard Spring MVC return value
  /safe/account                    OK        all 6 headers delivered

CONTROL   getOutputStream(), body > 8 KB buffer
  /vuln/stream/account             OK        all 6 headers delivered

CONTROL   explicit response.flushBuffer()
  /vuln/flush/account              OK        all 6 headers delivered

EXPLOIT   setIntHeader("Content-Length", n)  <-- CVE-2026-22732
  /vuln/content-length/account     BYPASSED  ALL 6 security headers dropped

BY DESIGN application sets its own Expires header (NOT this CVE)
  /vuln/cache/account              PARTIAL   Cache-Control + Pragma dropped

CVE가 패치될 때 상태가 바뀌는 것은 /vuln/content-length/account뿐이므로, exploit.sh가 판정을 도출하는 엔드포인트도 이것뿐이다. 나머지는 대조군이다.

CVE — setIntHeader("Content-Length", n) → 완전 우회

평범해 보이는 컨트롤러 코드 세 줄이 Spring Security가 약속한 모든 헤더를 제거한다:

response.setContentType("application/json");
response.setIntHeader("Content-Length", body.length);
response.getOutputStream().write(body);
$ 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

X-Frame-Options도, X-Content-Type-Options도, Cache-Control도, Pragma도, Expires도, X-XSS-Protection도 없다. 여섯 개를 모두 갖춘 /safe/account와 비교해 보라. 응답은 어떤 오리진에서든 프레이밍 가능하고, MIME 스니핑 가능하며, 캐시 가능하다 — 그러면서 카드 번호를 제공한다.

설계상의 사례 — 애플리케이션이 설정한 캐시 헤더 → 캐시 억제

정정: 이 README의 이전 버전은 이를 "exploit 2"라고 부르고 어드바이저리가 문서화한 조건이라고 주장했다. 그것은 CVE-2026-22732의 일부가 아니며 어떤 업그레이드로도 고쳐지지 않는다. CacheControlHeadersWriter는 5.7.11, 5.7.14-0.cgr.2, 6.5.8(마지막 취약 버전), 6.5.9(첫 수정 버전)에서 바이트 단위로 동일하다 — 소스 jar를 diff하여 검증했다. 그 Javadoc은 이 동작을 명시적으로 서술한다: "캐시 제어 헤더가 지정되지 않은 경우 캐싱을 방지하기 위해 헤더를 삽입한다."

그래도 시연할 가치가 있는데, 유출이 실제이며 잔여 위험이 패치 후에도 살아남기 때문이다. CacheControlHeadersWriter는 Cache-Control, Expires 또는 Pragma가 이미 존재하면 중단하므로, 세 가지 중 하나만 설정해도 Spring Security의 모든 no-store 지시문이 억제된다. 선의의 한 줄이면 충분하다:

response.setHeader("Expires", "Thu, 01 Jan 2099 00:00:00 GMT");
/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는 "존재함"으로 간주되지만, 앱이 선택한 공격자 친화적 값으로 인해 Spring Security의 Expires: 0은 단순히 누락된 것이 아니라 대체되었다. 카드 소지자 데이터는 이제 2099년까지 경로상의 모든 브라우저와 공유 프록시에 저장될 수 있다.

패치된 빌드에서는 /vuln/content-length/account가 NOT cacheable로 바뀌는 반면, /vuln/cache/account는 위와 정확히 동일하게 유지된다. 애플리케이션 코드나 리버스 프록시만이 이를 고칠 수 있다 — 이것이 데모에서 소리 내어 말할 유용한 점이다: 라이브러리를 업그레이드하면 CVE는 닫히고 이것은 그대로 남는다.

의도적으로 남겨둔 두 가지 부정적 결과

널리 퍼진 여러 글 — 공개 재현 저장소를 포함하여 — 은 response.getOutputStream()과 response.flushBuffer()를 트리거로 나열하며, "Spring Security가 헤더를 주입하기 전에 응답이 커밋된다"고 설명한다. Spring Security 5.7.11에서는 그것이 틀렸다. 두 엔드포인트 모두 여섯 개 헤더를 전부 전달한다.

/diag/committed는 그 설명이 성립하지 않는 이유를 보여준다. 12 KB 쓰기 후 응답은 실제로 컨트롤러 내부에서 커밋되지만, 헤더는 여전히 도착한다:

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

OnCommittedResponseWrapper는 flushBuffer()와 출력 스트림 쓰기를 재정의하므로, 그 커밋보다 먼저 헤더를 내보낸다. 커밋 순서만으로는 버그가 아니며, 선언된 Content-Length 경로가 버그다. Spring 어드바이저리 자체도 커밋 순서 이야기를 지지하지 않는다.

이 두 엔드포인트를 남겨두면 PoC가 반증 가능해진다: 재현되지 않는 것을 재현되는 것만큼 명확히 보여주며, 둘 다 패치 전반에 걸쳐 초록으로 유지되는데, 이것이 바로 바뀌는 하나의 엔드포인트를 의미 있게 만든다.

취약 → 패치 전환

pom.xml의 한 줄, 그 외에는 아무것도 없다. 소스 변경도, 프로퍼티 변경도, Spring Boot 메이저 버전 업도 없다:

<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>

이는 parent의 spring-boot-dependencies를 통해 spring-security.version을 5.7.11에서 5.7.14-0.cgr.2로 (그리고 spring-framework.version을 5.3.31에서 5.3.39-0.cgr.4로) 다시 고정한다. 측정 결과:

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 (설계상)
exploit.sh 판정VULNERABLEPATCHED

백포트가 곧 업스트림 수정

spring-security-web 소스 jar를 diff하면 파일 하나만 중요하다. 5.7.11 → 5.7.14-0.cgr.2는 OnCommittedResponseWrapper에 setHeader / setIntHeader / addIntHeader 재정의를 추가하며, 각각 Content-Length를 setContentLength()를 통해 라우팅한다:

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

수정 전에는 addHeader만 이렇게 했으므로, setIntHeader("Content-Length", n)은 래퍼의 추적 길이를 0으로 남겨두었고, onResponseCommitted()는 결코 발동하지 않았으며, HeaderWriterFilter는 Tomcat이 응답을 커밋하기 전에 헤더를 결코 쓰지 않았다.

업스트림 6.5.8(마지막 취약)과 6.5.9(첫 수정)를 diff할 때도 동일한 헝크가 그대로 나타나므로, 이는 재구현이 아니라 공식 수정을 백포트한 것이다. Chainguard 빌드는 업스트림 6.5.9에 없는 null 가드 두 개를 추가한다(String 오버로드의 value != null, 그리고 append의 (csq != null) ? csq.length() : 4).

기타 완화책

Spring Security 5.7.x는 업스트림에서 EOL이다; OSS 수정은 6.4.15 / 6.5.9 / 7.0.4+에만 적용된다. 재빌드된 5.7.x가 선택지가 아니라면:

  1. 5.7.x에서 업그레이드 — Spring Boot 3.x 마이그레이션.
  2. 워크어라운드 — ObjectPostProcessor를 통해 HeaderWriterFilter.shouldWriteHeadersEagerly = true를 설정. 어드바이저리에 따르면 이는 동작을 바꾼다: 애플리케이션이 작성한 헤더가 Spring Security의 헤더를 억제하는 대신 특정 헤더만 재정의하게 된다. 이것은 버전 범프가 고치지 않는 /vuln/cache/account도 고친다.
  3. 상용 지원 — Tanzu Spring Enterprise의 5.7.x/5.8.x 백포트.
  4. 심층 방어 — 리버스 프록시 / 인그레스에서 헤더를 설정하여 누락된 애플리케이션 헤더가 유일한 통제가 되지 않도록 한다. 나열된 옵션 중 두 엔드포인트를 모두 커버하는 유일한 방법이다.

이 중 어느 것도 이 프로젝트에 연결되어 있지 않으므로, 취약한 동작이 기본값이며 패치된 상태는 위의 단일 <version> 변경으로 도달할 수 있다.

Spring Boot를 업그레이드하지 않고 내장 Tomcat 패치하기

도구 다운로드