## https://sploitus.com/exploit?id=FA5008C0-9788-5878-80A4-8AC429DFB043
# CVE-2026-33701 โ Unsafe Deserialization in OpenTelemetry Java Agent RMI Instrumentation
**Severity:** Critical (CVSS v4.0: 9.3)
**Affected versions:** opentelemetry-javaagent map = (Map) object;
return new ContextPayload(map);
}
} catch (ClassCastException | ClassNotFoundException ex) {
logger.log(FINE, "Error reading object", ex);
}
return null;
}
```
The `@SuppressWarnings("BanSerializableRead")` annotation with the comment `// fine` is actually kind of telling. Someone on the team flagged this as a concern at some point, the suppression was added, and the comment was meant to justify it. Clearly it wasnt fine.
`oi.readObject()` with no serialization filters is the classic unsafe deserialization pattern. Java serialization will happily instantiate any class on the classpath during deserialization, and if the attacker can control the serialized stream they can choose which classes get instantiated and in what order. Thats the basis of gadget chain attacks.
The code does check `instanceof Map` after deserialization, but by that point the damage is already done. The gadget chain executes during the `readObject()` call itself, before any type checking happens. The instanceof check is completely irrelevant from a security standpoint.
---
## The Patch
The fix in 2.26.1 completely replaces the deserialization approach. Instead of serializing a `Map` object and calling `readObject()`, the new implementation manually reads the context entries as primitive types:
```java
@Nullable
public static ContextPayload read(ObjectInput oi) throws IOException {
int size = oi.readInt();
if (size > MAX_CONTEXT_ENTRIES) {
logger.log(
FINE,
"RMI context propagation payload size {0} exceeds maximum allowed of {1}, skipping context propagation.",
new Object[] {size, MAX_CONTEXT_ENTRIES});
return null;
}
Map map = new HashMap<>();
for (int i = 0; i MAX_CONTEXT_ENTRIES) {
out.writeInt(0);
return;
}
out.writeInt(size);
for (Map.Entry entry : context.entrySet()) {
out.writeUTF(entry.getKey());
out.writeUTF(entry.getValue());
}
}
```
This approach only reads primitive types (ints and UTF strings) from the stream. There is no object instantiation happening. No gadget chain can execute through `readInt()` or `readUTF()`. The fix is correct and complete.
There is also a second change worth noting. The ObjID used to identify the agents custom endpoint was versioned:
```java
// Before (2.26.0)
new ObjID("io.opentelemetry.javaagent.context-call".hashCode());
// After (2.26.1)
new ObjID("io.opentelemetry.javaagent.context-call-v2".hashCode());
```
This means a patched agent will not respond to the old ObjID at all. An attacker targeting the old endpoint would get no response from a patched server. This is a nice additional hardening measure on top of the deserialization fix, it effectively breaks any exploit that was built against the v1 endpoint even if somehow the deserialization was still reachable.
---
## Exploit Conditions
All three of the following must be true simultaneously for this to be exploitable:
**1. OpenTelemetry Java agent attached on JDK 16 or lower**
This is the most important constraint. JDK 17 introduced significant changes to the RMI internals and enforces much stricter module encapsulation via the Java Platform Module System. Most gadget chains that work against Java deserialization rely on reflection to access internal JDK classes or to invoke methods that wouldnt normally be accessible. JDK 17 breaks those reflection paths by default because of strong encapsulation, which means the majority of known gadget chains simply dont work on JDK 17 and above.
On JDK 8, 11, and 16 however, reflection access is much more permissive and gadget chains work as expected. These JDK versions still represent a substantial portion of production Java deployments, especially in older enterprise environments.
**2. An RMI or JMX port is network-reachable by the attacker**
The application needs to have something like `-Dcom.sun.management.jmxremote.port=9010` configured, or it needs to export its own RMI services. The OTel agents endpoint piggybacks on whatever RMI transport is already in use. If theres no RMI port open, the endpoint isnt reachable.
**3. A gadget-chain-compatible library is present on the application classpath**
The agent jar itself does not contain any gadget-chain-compatible classes. We confirmed this by inspecting the class list extracted from the 2.26.0 agent jar. The agent contains OTel instrumentation code, shaded API classes, semconv definitions and bootstrap infrastructure. None of the commonly exploited gadget sources like Commons Collections, Commons BeanUtils, or Spring Framework classes are bundled inside the agent.
This means the gadget chain has to come from the application. The developer has to have included a library that contains exploitable serialization gadgets, which means the exploitability varies depending on what the application depends on.
---
## Why This Still Matters in Practice
The three conditions above might sound restrictive but they are actually quite common together in real enterprise Java environments. Consider a typical production scenario: a backend service running on JDK 11 or JDK 17 (with --add-opens flags, which are common in containerized deployments and can partially re-enable gadget chains), instrumented with the OTel agent for observability, with JMX enabled for operational monitoring, and a dependency tree rich enough to contain gadget-compatible classes.
The key insight is that developers trust observability agents as passive infrastructure. They expect the agent to observe, not to open new network-reachable endpoints. The attack surface created here is invisible from the application developers perspective. They didnt write any RMI code. They didnt configure any RMI endpoint. The agent did it silently as a consequence of instrumentation.
---
## Gadget Chain Dependency
For further research on the gadget chain requirement, the ysoserial project (publicly available, widely referenced in academic and professional security research) documents several Java deserialization gadget chains that are relevant to this class of vulnerability. Chains like `CommonsCollections`, `Spring1`, and `Spring2` are well documented examples of how existing library classes can be chained to achieve code execution through unsafe deserialization. Which specific chains are applicable in a given environment depends entirely on what libraries that application has on its classpath.
---
## Remediation
Upgrade to `opentelemetry-javaagent` version 2.26.1 or later. This is the only complete fix.
If immediate upgrade isnt possible, the RMI instrumentation can be disabled entirely by adding the following system property to the JVM startup flags:
```
-Dotel.instrumentation.rmi.enabled=false
```
This disables the instrumentation that registers the vulnerable endpoint, removing the attack surface entirely. Context propagation across RMI calls will not work while this flag is set, but that is acceptable as a temporary mitigation.
Independently of this CVE, if JMX is exposed on a network-reachable port without authentication it should be treated as a critical misconfiguration regardless of the OTel agent being present or not.
---
## References
- GitHub Security Advisory: GHSA-xw7x-h9fj-p2c7
- Patch commit: open-telemetry/opentelemetry-java-instrumentation@9cf4fba
- NVD entry: CVE-2026-33701
- ysoserial (public gadget chain reference): https://github.com/frohoff/ysoserial