From 70b13819d96b32a5c07fdb1f3913e5ddefae84a4 Mon Sep 17 00:00:00 2001 From: nstarke Date: Sun, 12 Oct 2014 23:13:49 +0000 Subject: [PATCH 1/6] Adding Login Scanner for MyBook Live This is a LoginScanner auxiliary module for Western Digital MyBook Live NAS devices as well as the spec for testing. --- .../framework/login_scanner/mybook_live.rb | 59 +++++++++++++++ .../scanner/http/mybook_live_login.rb | 75 +++++++++++++++++++ .../login_scanner/mybook_live_spec.rb | 10 +++ 3 files changed, 144 insertions(+) create mode 100644 lib/metasploit/framework/login_scanner/mybook_live.rb create mode 100644 modules/auxiliary/scanner/http/mybook_live_login.rb create mode 100644 spec/lib/metasploit/framework/login_scanner/mybook_live_spec.rb diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb new file mode 100644 index 0000000000..ca9398bf0f --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -0,0 +1,59 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Western Digital MyBook Live login scanner + class MyBookLive < HTTP + + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = true + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/UI/login" if self.uri.nil? + self.method = "POST" if self.method.nil? + + super + end + + def attempt_login(credential) + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + begin + body = "data%5BLogin%5D%5Bowner_name%5D=admin&data%5BLogin%5D%5Bowner_passwd%5D=#{Rex::Text.uri_encode(credential.private)}" + cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli.connect + req = cli.request_cgi({ + 'method' => 'POST', + 'uri' => '/UI/login', + 'data' => body + }) + res = cli.send_recv(req) + print res + if res && res.code == 302 && res.headers['location'] && res.headers['location'].include?('UI') + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res) + end + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + Result.new(result_opts) + end + end + end + end +end diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb new file mode 100644 index 0000000000..e993462b32 --- /dev/null +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -0,0 +1,75 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/mybook_live' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'Western Digital MyBook Live Login Utility', + 'Description' => 'This module simply attempts to login to a Western Digital MyBook Live instance using a specific user/pass.', + 'Author' => [ 'Nicholas Starke ' ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80) + ], self.class) + + register_autofilter_ports([ 80 ]) + + #username is hardcoded into application + deregister_options('RHOST', 'USERNAME', 'USER_FILE', 'USER_AS_PASS', 'DB_ALL_USERS') + end + + def run_host(ip) + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: 'admin', + user_as_pass: datastore['USER_AS_PASS'] + ) + + scanner = Metasploit::Framework::LoginScanner::MyBookLive.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 10, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'] + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" + end + end + end +end diff --git a/spec/lib/metasploit/framework/login_scanner/mybook_live_spec.rb b/spec/lib/metasploit/framework/login_scanner/mybook_live_spec.rb new file mode 100644 index 0000000000..9ad1c2ffdd --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/mybook_live_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/mybook_live' + +describe Metasploit::Framework::LoginScanner::MyBookLive do + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP' + +end From 82b74d5f3c497b97bab7e48ec7b41e808f308c54 Mon Sep 17 00:00:00 2001 From: nstarke Date: Mon, 20 Oct 2014 01:35:06 +0000 Subject: [PATCH 2/6] Fixes to MyBook Live Module This commit contains three fixes as requested on PR #4003. Those include: + Removing extraneous puts statement + Checking for valid response + SSL support. --- lib/metasploit/framework/login_scanner/mybook_live.rb | 5 +++-- modules/auxiliary/scanner/http/mybook_live_login.rb | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index ca9398bf0f..6caf6a6ea0 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -42,11 +42,12 @@ module Metasploit 'data' => body }) res = cli.send_recv(req) - print res if res && res.code == 302 && res.headers['location'] && res.headers['location'].include?('UI') result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers) + elsif res.nil? + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT) else - result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res) + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res.headers) end rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb index e993462b32..33425bfbec 100644 --- a/modules/auxiliary/scanner/http/mybook_live_login.rb +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -54,6 +54,11 @@ class Metasploit3 < Msf::Auxiliary vhost: datastore['VHOST'] ) + if ssl + scanner.ssl = datastore['SSL'] + scanner.ssl_version = datastore['SSLVERSION'] + end + scanner.scan! do |result| credential_data = result.to_h credential_data.merge!( From ee3dd3a2ac13fd8782f1dacdb74df4046d429c08 Mon Sep 17 00:00:00 2001 From: nstarke Date: Wed, 22 Oct 2014 03:06:21 +0000 Subject: [PATCH 3/6] More Fixes for WD MyBook Live Scanner Fixes include removing deregistered options from credentials collection object and adding proof when there is no response --- lib/metasploit/framework/login_scanner/mybook_live.rb | 2 +- modules/auxiliary/scanner/http/mybook_live_login.rb | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index 6caf6a6ea0..0c34cf6bc9 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -45,7 +45,7 @@ module Metasploit if res && res.code == 302 && res.headers['location'] && res.headers['location'].include?('UI') result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers) elsif res.nil? - result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT) + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: 'No response') else result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res.headers) end diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb index 33425bfbec..975b3a2014 100644 --- a/modules/auxiliary/scanner/http/mybook_live_login.rb +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -37,10 +37,7 @@ class Metasploit3 < Msf::Auxiliary blank_passwords: datastore['BLANK_PASSWORDS'], pass_file: datastore['PASS_FILE'], password: datastore['PASSWORD'], - user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: 'admin', - user_as_pass: datastore['USER_AS_PASS'] + username: 'admin' ) scanner = Metasploit::Framework::LoginScanner::MyBookLive.new( From ce8a9941ea301d9e69a4ee1d9a9ad6a422f466b5 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 22 Oct 2014 10:36:24 -0700 Subject: [PATCH 4/6] Cleanup. Sanity check in setup. vprint --- .../framework/login_scanner/mybook_live.rb | 16 ++++++------ .../scanner/http/mybook_live_login.rb | 26 ++++++++++++------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index 0c34cf6bc9..65a44d255f 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -22,10 +22,10 @@ module Metasploit def attempt_login(credential) result_opts = { - credential: credential, - host: host, - port: port, - protocol: 'tcp' + credential: credential, + host: host, + port: port, + protocol: 'tcp' } if ssl result_opts[:service_name] = 'https' @@ -33,14 +33,14 @@ module Metasploit result_opts[:service_name] = 'http' end begin - body = "data%5BLogin%5D%5Bowner_name%5D=admin&data%5BLogin%5D%5Bowner_passwd%5D=#{Rex::Text.uri_encode(credential.private)}" + body = "data[Login][owner_name]=admin&data[Login][owner_passwd]=#{credential.private}" cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) cli.connect - req = cli.request_cgi({ + req = cli.request_cgi( 'method' => 'POST', 'uri' => '/UI/login', - 'data' => body - }) + 'data' => Rex::Text.uri_encode(body) + ) res = cli.send_recv(req) if res && res.code == 302 && res.headers['location'] && res.headers['location'].include?('UI') result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers) diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb index 975b3a2014..1ba0c9ee6f 100644 --- a/modules/auxiliary/scanner/http/mybook_live_login.rb +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -27,17 +27,25 @@ class Metasploit3 < Msf::Auxiliary ], self.class) register_autofilter_ports([ 80 ]) - - #username is hardcoded into application + + # username is hardcoded into application deregister_options('RHOST', 'USERNAME', 'USER_FILE', 'USER_AS_PASS', 'DB_ALL_USERS') end + def setup + # They must select at least blank passwords, provide a pass file or a password + one_required = %w(BLANK_PASSWORDS PASS_FILE PASSWORD) + unless one_required.any? { |o| datastore[o] } + fail_with(Failure::BadConfig, "Invalid options: One of #{one_required.join(', ')} must be set") + end + end + def run_host(ip) cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], - username: 'admin' + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + username: 'admin' ) scanner = Metasploit::Framework::LoginScanner::MyBookLive.new( @@ -59,8 +67,8 @@ class Metasploit3 < Msf::Auxiliary scanner.scan! do |result| credential_data = result.to_h credential_data.merge!( - module_fullname: fullname, - workspace_id: myworkspace_id + module_fullname: fullname, + workspace_id: myworkspace_id ) if result.success? credential_core = create_credential(credential_data) @@ -70,7 +78,7 @@ class Metasploit3 < Msf::Auxiliary print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" else invalidate_login(credential_data) - print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" + vprint_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" end end end From 83df08aaa7cfc18cf3bbd59aba009a0a177cc040 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 22 Oct 2014 22:43:06 -0700 Subject: [PATCH 5/6] Properly encode body and catch invalid configs --- lib/metasploit/framework/login_scanner/mybook_live.rb | 5 +++-- modules/auxiliary/scanner/http/mybook_live_login.rb | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index 65a44d255f..ad8fbb12bb 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -33,13 +33,14 @@ module Metasploit result_opts[:service_name] = 'http' end begin - body = "data[Login][owner_name]=admin&data[Login][owner_passwd]=#{credential.private}" + cred = Rex::Text.uri_encode(credential.private) + body = "data%5BLogin%5D%5Bowner_name%5D=admin&data%5BLogin%5D%5Bowner_passwd%5D=#{cred}" cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) cli.connect req = cli.request_cgi( 'method' => 'POST', 'uri' => '/UI/login', - 'data' => Rex::Text.uri_encode(body) + 'data' => body ) res = cli.send_recv(req) if res && res.code == 302 && res.headers['location'] && res.headers['location'].include?('UI') diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb index 1ba0c9ee6f..458f51dffb 100644 --- a/modules/auxiliary/scanner/http/mybook_live_login.rb +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -33,11 +33,17 @@ class Metasploit3 < Msf::Auxiliary end def setup + super # They must select at least blank passwords, provide a pass file or a password one_required = %w(BLANK_PASSWORDS PASS_FILE PASSWORD) - unless one_required.any? { |o| datastore[o] } + unless one_required.any? { |o| datastore.has_key?(o) && datastore[o] } fail_with(Failure::BadConfig, "Invalid options: One of #{one_required.join(', ')} must be set") end + if !datastore['PASS_FILE'] + if !datastore['BLANK_PASSWORDS'] && datastore['PASSWORD'].blank? + fail_with(Failure::BadConfig, "PASSWORD or PASS_FILE must be set to a non-empty string if not BLANK_PASSWORDS") + end + end end def run_host(ip) From 765b5e686c19f39005c7cd76cf3eb3e8fa0374d0 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Mon, 27 Oct 2014 09:56:39 -0700 Subject: [PATCH 6/6] Use configured method and URI rather than duplicated values --- lib/metasploit/framework/login_scanner/mybook_live.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index ad8fbb12bb..2f32ebe304 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -14,8 +14,8 @@ module Metasploit # (see Base#set_sane_defaults) def set_sane_defaults - self.uri = "/UI/login" if self.uri.nil? - self.method = "POST" if self.method.nil? + self.uri = '/UI/login' if self.uri.nil? + self.method = 'POST' if self.method.nil? super end @@ -38,8 +38,8 @@ module Metasploit cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) cli.connect req = cli.request_cgi( - 'method' => 'POST', - 'uri' => '/UI/login', + 'method' => method, + 'uri' => uri, 'data' => body ) res = cli.send_recv(req)