Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-33701-Unsafe-Deserialization-in-OpenTelemetry-Java-Agent-RMI-Instrumentation — Technical analysis of CVE-2026-33701, an unsafe deserialization vulnerability in OpenTelemetry Java Agent RMI instrumentation, including exploit conditions, patch details, and mitigation strategies. | Kitploit
Tools/GitHubGitHub/pl4tyz/cve-2026-33701-unsafe-deserialization-in-opentelemetry-java-agent-rmi-instrumentation
Vulnerability AnalysisExploitationWeb SecurityLearning & Education
GitHubpl4tyz/cve-2026-33701-unsafe-deserialization-in-opentelemetry-java-agent-rmi-instrumentation

CVE-2026-33701-Unsafe-Deserialization-in-OpenTelemetry-Java-Agent-RMI-Instrumentation

Technical analysis of CVE-2026-33701, an unsafe deserialization vulnerability in OpenTelemetry Java Agent RMI instrumentation, including exploit conditions, patch details, and mitigation strategies.

View Repository
186 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-33701 — Unsafe Deserialization in OpenTelemetry Java Agent RMI Instrumentation

Severity: Critical (CVSS v4.0: 9.3)
Affected versions: opentelemetry-javaagent < 2.26.1
Patched in: 2.26.1 (released March 23, 2026)


Overview

OpenTelemetry is probably the most widely adopted observability framework in the Java ecosystem right now. The Java agent specifically is used by thousands of production services to automatically instrument applications for tracing, metrics and logging without requiring any code changes from the developer. You just attach it as a javaagent flag and it does everything behind the scenes.

What makes CVE-2026-33701 interesting isnt just the vulnerability itself, its the nature of how it got introduced. The agent quietly registers a custom RMI endpoint as a side effect of its RMI instrumentation. Developers dont know its there. Its not in any application code. Its not something they configured. The agent put it there automatically, and for versions below 2.26.1 that endpoint was deserializing incoming data with absolutely no filters applied.

If the application already had an RMI or JMX port exposed, an attacker with network access to that port could send a crafted serialized payload to the agents custom endpoint and potentially achieve remote code execution with the privileges of the JVM process. The catch is that RCE requires a compatible gadget chain to be present on the application classpath. More on that later.


How the Agent Instruments RMI

When the OTel Java agent attaches to a JVM it instruments RMI calls for context propagation. The idea is that when an RMI client calls a remote method, the agent needs to propagate the current trace context to the server side so spans connect correctly across service boundaries.

To do this the agent registers its own custom RMI object using a hardcoded ObjID. In ContextPropagator.java:

public static final ObjID CONTEXT_CALL_ID =
    new ObjID("io.opentelemetry.javaagent.context-call".hashCode());

This ObjID is how the agent identifies its own endpoint within the RMI runtime. When an instrumented RMI client connects to a server, it first checks if the server has this ObjID registered. If it does, it sends a context propagation payload to it. If not, it skips context propagation and just does the regular call.

The problem is that this endpoint is registered inside the JVM RMI runtime, meaning it shares the same transport as whatever RMI or JMX port the application has open. If the application has -Dcom.sun.management.jmxremote.port set, or if it exports its own RMI services, the agents endpoint is reachable on that same port by anyone who can connect to it.


The Vulnerable Code

The deserialization happens in ContextPayload.java. In versions below 2.26.1 the read() method looked like this:

@SuppressWarnings("BanSerializableRead") // fine
public static ContextPayload read(ObjectInput oi) throws IOException {
    try {
        Object object = oi.readObject();
        if (object instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, String> map = (Map<String, String>) 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:

@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<String, String> map = new HashMap<>();
    for (int i = 0; i < size; i++) {
        String key = oi.readUTF();
        String value = oi.readUTF();
        map.put(key, value);
    }
    return new ContextPayload(map);
}

And the write side was updated to match:

public void write(ObjectOutput out) throws IOException {
    int size = context.size();
    if (size > MAX_CONTEXT_ENTRIES) {
        out.writeInt(0);
        return;
    }
    out.writeInt(size);
    for (Map.Entry<String, String> 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:

// 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

Download Tool