From 7e9127479640abd7aa9016b27100e2cd766fc4ec Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 12:29:50 -0800 Subject: [PATCH 01/18] Add module for connecting to/discovering MQTT endpoints --- modules/auxiliary/scanner/mqtt/connect.rb | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 modules/auxiliary/scanner/mqtt/connect.rb diff --git a/modules/auxiliary/scanner/mqtt/connect.rb b/modules/auxiliary/scanner/mqtt/connect.rb new file mode 100644 index 0000000000..ff5a5b4ea1 --- /dev/null +++ b/modules/auxiliary/scanner/mqtt/connect.rb @@ -0,0 +1,61 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::MQTT + include Msf::Auxiliary::Report + + HANDLED_EXCEPTIONS = [ + Rex::AddressInUse, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, + ::Errno::ETIMEDOUT, ::Timeout::Error, ::EOFError + ] + + def initialize + super( + 'Name' => 'Connect to and discover MQTT endpoints', + 'Description' => %q( + This module attempts to establish a connection with MQTT endpoints. + ), + 'Author' => [ + 'Jon Hart ' # original metasploit module + ], + 'References' => + [ + ['URL', 'http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_3.1_-'] + ], + 'License' => MSF_LICENSE + ) + end + + def run_host(ip) + begin + connect + client = mqtt_client + if mqtt_connect?(client) + print_good("Connected to MQTT") + # TODO: eventually we should subscribe to $SYS/# in order to get useful metadata: + # $ mosquitto_sub -t '$SYS/#' -v + # $SYS/broker/version mosquitto version 1.4.14 + # $SYS/broker/timestamp Mon, 10 Jul 2017 23:48:43 +0100 + report_service( + host: ip, + port: rport, + proto: 'tcp', + name: 'MQTT' + ) + else + vprint_error("Failed to connect to MQTT") + end + rescue *HANDLED_EXCEPTIONS => e + vprint_error("error while connecting and negotiating: #{e}") + return + ensure + mqtt_disconnect(client) + disconnect + end + end +end From c817df0bbc011d7891ce1969b0e28ff307412d66 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 12:30:21 -0800 Subject: [PATCH 02/18] Add module for bruteforcing authentication on MQTT endpoints --- modules/auxiliary/scanner/mqtt/login.rb | 94 +++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 modules/auxiliary/scanner/mqtt/login.rb diff --git a/modules/auxiliary/scanner/mqtt/login.rb b/modules/auxiliary/scanner/mqtt/login.rb new file mode 100644 index 0000000000..d47ba0d196 --- /dev/null +++ b/modules/auxiliary/scanner/mqtt/login.rb @@ -0,0 +1,94 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/mqtt' + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::MQTT + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'MQTT Authentication Scanner', + 'Description' => %q( + This module attempts to authenticate to MQTT. + ), + 'Author' => + [ + 'Jon Hart ' + ], + 'References' => + [ + ['URL', 'http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_3.1_-'] + ], + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'BLANK_PASSWORDS' => true + } + ) + end + + def run_host(ip) + vprint_status("#{ip}:#{rport} - Starting MQTT login sweep") + + 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: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::MQTT.new( + host: ip, + port: rport, + read_timeout: datastore['READ_TIMEOUT'], + client_id: client_id(), + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['ConnectTimeout'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ssl: datastore['SSL'], + ssl_version: datastore['SSLVersion'], + ssl_verify_mode: datastore['SSLVerifyMode'], + ssl_cipher: datastore['SSLCipher'], + local_port: datastore['CPORT'], + local_host: datastore['CHOST'] + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + password = result.credential.private + username = result.credential.public + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + print_good("#{ip}:#{rport} - MQTT Login Successful: #{username}/#{password}") + else + invalidate_login(credential_data) + vprint_error("#{ip}:#{rport} - MQTT LOGIN FAILED: #{username}/#{password} (#{result.status}: #{result.proof})") + end + end + end +end From b4262662dcbbc3495b3eb13a46e529bbf38edcc6 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 12:33:49 -0800 Subject: [PATCH 03/18] Add missing mqtt login helper --- .../framework/login_scanner/mqtt.rb | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 lib/metasploit/framework/login_scanner/mqtt.rb diff --git a/lib/metasploit/framework/login_scanner/mqtt.rb b/lib/metasploit/framework/login_scanner/mqtt.rb new file mode 100644 index 0000000000..e0734b8396 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mqtt.rb @@ -0,0 +1,87 @@ +require 'metasploit/framework/tcp/client' +require 'rex/proto/mqtt' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + # This is the LoginScanner class for dealing with MQTT. + # It is responsible for taking a single target, and a list of + # credentials and attempting them. It then saves the results. + class MQTT + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + # + # CONSTANTS + # + DEFAULT_PORT = Rex::Proto::MQTT::DEFAULT_PORT + DEFAULT_SSL_PORT = Rex::Proto::MQTT::DEFAULT_SSL_PORT + LIKELY_PORTS = [ DEFAULT_PORT, DEFAULT_SSL_PORT ] + LIKELY_SERVICE_NAMES = [ 'MQTT' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # @!attribute read_timeout + # @return [int] The timeout use while reading responses from MQTT, in seconds + attr_accessor :read_timeout + + # @!attribute client_id + # @return [String] The client identifier to use when connecting to MQTT + attr_accessor :client_id + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attmpt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'MQTT' + } + + begin + # Make our initial socket to the target + disconnect if self.sock + connect + + client_opts = { + username: credential.public, + password: credential.private, + read_timeout: read_timeout, + client_id: client_id + } + client = Rex::Proto::MQTT::Client.new(sock, client_opts) + connect_res = client.connect + + if connect_res.return_code == 0 + status = Metasploit::Model::Login::Status::SUCCESSFUL + proof = "Succesful Connack" + else + status = Metasploit::Model::Login::Status::INCORRECT + proof = "Failed Connack (#{connect_res.return_code})" + end + + result_options.merge!( + proof: proof, + status: status + ) + rescue ::EOFError, Errno::ENOTCONN, Rex::ConnectionError, ::Timeout::Error => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + ensure + disconnect + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + end + end + end +end From 14c779b945d148cbdae64fdfa089ff961488a6d6 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 12:44:27 -0800 Subject: [PATCH 04/18] Fix rubocop warning --- modules/auxiliary/scanner/mqtt/login.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/mqtt/login.rb b/modules/auxiliary/scanner/mqtt/login.rb index d47ba0d196..109676d60b 100644 --- a/modules/auxiliary/scanner/mqtt/login.rb +++ b/modules/auxiliary/scanner/mqtt/login.rb @@ -45,7 +45,7 @@ class MetasploitModule < Msf::Auxiliary user_file: datastore['USER_FILE'], userpass_file: datastore['USERPASS_FILE'], username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'], + user_as_pass: datastore['USER_AS_PASS'] ) cred_collection = prepend_db_passwords(cred_collection) From e66ec85677174f60c68894d757452b7387d04d03 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 14:18:33 -0800 Subject: [PATCH 05/18] Set default u/p --- modules/auxiliary/scanner/mqtt/login.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/auxiliary/scanner/mqtt/login.rb b/modules/auxiliary/scanner/mqtt/login.rb index 109676d60b..9c5da16fab 100644 --- a/modules/auxiliary/scanner/mqtt/login.rb +++ b/modules/auxiliary/scanner/mqtt/login.rb @@ -31,6 +31,8 @@ class MetasploitModule < Msf::Auxiliary 'DefaultOptions' => { 'BLANK_PASSWORDS' => true + 'USERNAME' => '', + 'PASSWORD' => '' } ) end From ed5f177fcd70248c607a4df17f526dec39fe5826 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 14:20:08 -0800 Subject: [PATCH 06/18] syntax --- modules/auxiliary/scanner/mqtt/connect.rb | 103 +++++++++++++++------- modules/auxiliary/scanner/mqtt/login.rb | 96 -------------------- 2 files changed, 69 insertions(+), 130 deletions(-) delete mode 100644 modules/auxiliary/scanner/mqtt/login.rb diff --git a/modules/auxiliary/scanner/mqtt/connect.rb b/modules/auxiliary/scanner/mqtt/connect.rb index ff5a5b4ea1..b65bccb59c 100644 --- a/modules/auxiliary/scanner/mqtt/connect.rb +++ b/modules/auxiliary/scanner/mqtt/connect.rb @@ -3,59 +3,94 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/mqtt' + class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Scanner include Msf::Auxiliary::MQTT include Msf::Auxiliary::Report - - HANDLED_EXCEPTIONS = [ - Rex::AddressInUse, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, - ::Errno::ETIMEDOUT, ::Timeout::Error, ::EOFError - ] + include Msf::Auxiliary::AuthBrute def initialize super( - 'Name' => 'Connect to and discover MQTT endpoints', + 'Name' => 'MQTT Authentication Scanner', 'Description' => %q( - This module attempts to establish a connection with MQTT endpoints. + This module attempts to authenticate to MQTT. ), - 'Author' => [ - 'Jon Hart ' # original metasploit module - ], - 'References' => + 'Author' => + [ + 'Jon Hart ' + ], + 'References' => [ ['URL', 'http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_3.1_-'] ], - 'License' => MSF_LICENSE + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'BLANK_PASSWORDS' => true, + 'USERNAME' => '', + 'PASSWORD' => '' + } ) end def run_host(ip) - begin - connect - client = mqtt_client - if mqtt_connect?(client) - print_good("Connected to MQTT") - # TODO: eventually we should subscribe to $SYS/# in order to get useful metadata: - # $ mosquitto_sub -t '$SYS/#' -v - # $SYS/broker/version mosquitto version 1.4.14 - # $SYS/broker/timestamp Mon, 10 Jul 2017 23:48:43 +0100 - report_service( - host: ip, - port: rport, - proto: 'tcp', - name: 'MQTT' - ) + vprint_status("#{ip}:#{rport} - Starting MQTT login sweep") + + 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: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::MQTT.new( + host: ip, + port: rport, + read_timeout: datastore['READ_TIMEOUT'], + client_id: client_id(), + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['ConnectTimeout'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ssl: datastore['SSL'], + ssl_version: datastore['SSLVersion'], + ssl_verify_mode: datastore['SSLVerifyMode'], + ssl_cipher: datastore['SSLCipher'], + local_port: datastore['CPORT'], + local_host: datastore['CHOST'] + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + password = result.credential.private + username = result.credential.public + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + print_good("#{ip}:#{rport} - MQTT Login Successful: #{username}/#{password}") else - vprint_error("Failed to connect to MQTT") + invalidate_login(credential_data) + vprint_error("#{ip}:#{rport} - MQTT LOGIN FAILED: #{username}/#{password} (#{result.status}: #{result.proof})") end - rescue *HANDLED_EXCEPTIONS => e - vprint_error("error while connecting and negotiating: #{e}") - return - ensure - mqtt_disconnect(client) - disconnect end end end diff --git a/modules/auxiliary/scanner/mqtt/login.rb b/modules/auxiliary/scanner/mqtt/login.rb deleted file mode 100644 index 9c5da16fab..0000000000 --- a/modules/auxiliary/scanner/mqtt/login.rb +++ /dev/null @@ -1,96 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'metasploit/framework/credential_collection' -require 'metasploit/framework/login_scanner/mqtt' - -class MetasploitModule < Msf::Auxiliary - include Msf::Exploit::Remote::Tcp - include Msf::Auxiliary::Scanner - include Msf::Auxiliary::MQTT - include Msf::Auxiliary::Report - include Msf::Auxiliary::AuthBrute - - def initialize - super( - 'Name' => 'MQTT Authentication Scanner', - 'Description' => %q( - This module attempts to authenticate to MQTT. - ), - 'Author' => - [ - 'Jon Hart ' - ], - 'References' => - [ - ['URL', 'http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Table_3.1_-'] - ], - 'License' => MSF_LICENSE, - 'DefaultOptions' => - { - 'BLANK_PASSWORDS' => true - 'USERNAME' => '', - 'PASSWORD' => '' - } - ) - end - - def run_host(ip) - vprint_status("#{ip}:#{rport} - Starting MQTT login sweep") - - 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: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'] - ) - - cred_collection = prepend_db_passwords(cred_collection) - - scanner = Metasploit::Framework::LoginScanner::MQTT.new( - host: ip, - port: rport, - read_timeout: datastore['READ_TIMEOUT'], - client_id: client_id(), - proxies: datastore['PROXIES'], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: datastore['ConnectTimeout'], - max_send_size: datastore['TCP::max_send_size'], - send_delay: datastore['TCP::send_delay'], - framework: framework, - framework_module: self, - ssl: datastore['SSL'], - ssl_version: datastore['SSLVersion'], - ssl_verify_mode: datastore['SSLVerifyMode'], - ssl_cipher: datastore['SSLCipher'], - local_port: datastore['CPORT'], - local_host: datastore['CHOST'] - ) - - scanner.scan! do |result| - credential_data = result.to_h - credential_data.merge!( - module_fullname: fullname, - workspace_id: myworkspace_id - ) - password = result.credential.private - username = result.credential.public - if result.success? - credential_core = create_credential(credential_data) - credential_data[:core] = credential_core - create_credential_login(credential_data) - print_good("#{ip}:#{rport} - MQTT Login Successful: #{username}/#{password}") - else - invalidate_login(credential_data) - vprint_error("#{ip}:#{rport} - MQTT LOGIN FAILED: #{username}/#{password} (#{result.status}: #{result.proof})") - end - end - end -end From 495c649c7d9140ebd25bb79fff29cde1538b07a3 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 14:40:42 -0800 Subject: [PATCH 07/18] Better printing --- modules/auxiliary/scanner/mqtt/connect.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/mqtt/connect.rb b/modules/auxiliary/scanner/mqtt/connect.rb index b65bccb59c..e74e1e6a81 100644 --- a/modules/auxiliary/scanner/mqtt/connect.rb +++ b/modules/auxiliary/scanner/mqtt/connect.rb @@ -30,9 +30,7 @@ class MetasploitModule < Msf::Auxiliary 'License' => MSF_LICENSE, 'DefaultOptions' => { - 'BLANK_PASSWORDS' => true, - 'USERNAME' => '', - 'PASSWORD' => '' + 'BLANK_PASSWORDS' => true } ) end @@ -89,7 +87,7 @@ class MetasploitModule < Msf::Auxiliary print_good("#{ip}:#{rport} - MQTT Login Successful: #{username}/#{password}") else invalidate_login(credential_data) - vprint_error("#{ip}:#{rport} - MQTT LOGIN FAILED: #{username}/#{password} (#{result.status}: #{result.proof})") + vprint_error("#{ip}:#{rport} - MQTT LOGIN FAILED: #{username}/#{password} (#{result.proof})") end end end From d0b3abc14b055e304ae3b58bf7e80677d28c891f Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:02:52 -0800 Subject: [PATCH 08/18] Better handling of MQTT endpoints which don't require authentication Arguably this is working around LoginScanner's inability to provide blank usernames AND passwords --- .../framework/login_scanner/mqtt.rb | 1 + lib/rex/proto/mqtt/client.rb | 11 ++++-- modules/auxiliary/scanner/mqtt/connect.rb | 39 ++++++++++++++++--- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/mqtt.rb b/lib/metasploit/framework/login_scanner/mqtt.rb index e0734b8396..25d1bceb9a 100644 --- a/lib/metasploit/framework/login_scanner/mqtt.rb +++ b/lib/metasploit/framework/login_scanner/mqtt.rb @@ -57,6 +57,7 @@ module Metasploit } client = Rex::Proto::MQTT::Client.new(sock, client_opts) connect_res = client.connect + client.disconnect if connect_res.return_code == 0 status = Metasploit::Model::Login::Status::SUCCESSFUL diff --git a/lib/rex/proto/mqtt/client.rb b/lib/rex/proto/mqtt/client.rb index ed9df19b95..a5f55d544c 100644 --- a/lib/rex/proto/mqtt/client.rb +++ b/lib/rex/proto/mqtt/client.rb @@ -18,10 +18,15 @@ module Rex def connect connect_opts = { - client_id: @opts[:client_id], - username: @opts[:username], - password: @opts[:password] + client_id: @opts[:client_id] } + + unless @opts[:username].blank? + connect_opts[:username] = @opts[:username] + end + unless @opts[:password].blank? + connect_opts[:password] = @opts[:password] + end connect = ::MQTT::Packet::Connect.new(connect_opts).to_s @sock.put(connect) res = @sock.get_once(-1, @opts[:read_timeout]) diff --git a/modules/auxiliary/scanner/mqtt/connect.rb b/modules/auxiliary/scanner/mqtt/connect.rb index e74e1e6a81..43747013da 100644 --- a/modules/auxiliary/scanner/mqtt/connect.rb +++ b/modules/auxiliary/scanner/mqtt/connect.rb @@ -30,13 +30,42 @@ class MetasploitModule < Msf::Auxiliary 'License' => MSF_LICENSE, 'DefaultOptions' => { - 'BLANK_PASSWORDS' => true + 'BLANK_PASSWORDS' => false, + 'USER_AS_PASS' => true } ) end + def test_login(username, password) + client_opts = { + username: username, + password: password, + read_timeout: read_timeout, + client_id: client_id + } + connect + client = Rex::Proto::MQTT::Client.new(sock, client_opts) + connect_res = client.connect + client.disconnect + connect_res.return_code.zero? + end + + def default_login + vprint_status("Testing without credentials") + if test_login('', '') + print_good("Does not require authentication") + end + + end + def run_host(ip) - vprint_status("#{ip}:#{rport} - Starting MQTT login sweep") + unless default_login + brute + end + end + + def brute + vprint_status("Starting MQTT login sweep") cred_collection = Metasploit::Framework::CredentialCollection.new( blank_passwords: datastore['BLANK_PASSWORDS'], @@ -51,7 +80,7 @@ class MetasploitModule < Msf::Auxiliary cred_collection = prepend_db_passwords(cred_collection) scanner = Metasploit::Framework::LoginScanner::MQTT.new( - host: ip, + host: rhost, port: rport, read_timeout: datastore['READ_TIMEOUT'], client_id: client_id(), @@ -84,10 +113,10 @@ class MetasploitModule < Msf::Auxiliary credential_core = create_credential(credential_data) credential_data[:core] = credential_core create_credential_login(credential_data) - print_good("#{ip}:#{rport} - MQTT Login Successful: #{username}/#{password}") + print_good("MQTT Login Successful: #{username}/#{password}") else invalidate_login(credential_data) - vprint_error("#{ip}:#{rport} - MQTT LOGIN FAILED: #{username}/#{password} (#{result.proof})") + vprint_error("MQTT LOGIN FAILED: #{username}/#{password} (#{result.proof})") end end end From b9af835d06fb1e015464084064cb9bd5c6294fe7 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:05:00 -0800 Subject: [PATCH 09/18] Style --- modules/auxiliary/scanner/mqtt/connect.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/mqtt/connect.rb b/modules/auxiliary/scanner/mqtt/connect.rb index 43747013da..91d02033e2 100644 --- a/modules/auxiliary/scanner/mqtt/connect.rb +++ b/modules/auxiliary/scanner/mqtt/connect.rb @@ -58,7 +58,7 @@ class MetasploitModule < Msf::Auxiliary end - def run_host(ip) + def run_host(_ip) unless default_login brute end @@ -83,7 +83,7 @@ class MetasploitModule < Msf::Auxiliary host: rhost, port: rport, read_timeout: datastore['READ_TIMEOUT'], - client_id: client_id(), + client_id: client_id, proxies: datastore['PROXIES'], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], From 37ae5e1303bbba5e01c3603d98484b7cd094f41f Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:44:21 -0800 Subject: [PATCH 10/18] Add admin as a default unix passwd --- data/wordlists/unix_passwords.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/data/wordlists/unix_passwords.txt b/data/wordlists/unix_passwords.txt index 78a1139d6c..9f58a0dcf7 100755 --- a/data/wordlists/unix_passwords.txt +++ b/data/wordlists/unix_passwords.txt @@ -1,3 +1,4 @@ +admin 123456 12345 123456789 From 298cb16b1adc9d5fa1ddb3f2e6cae73f5917b9df Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:44:43 -0800 Subject: [PATCH 11/18] Set default USER/PASS files --- modules/auxiliary/scanner/mqtt/connect.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/mqtt/connect.rb b/modules/auxiliary/scanner/mqtt/connect.rb index 91d02033e2..dbbe9987c2 100644 --- a/modules/auxiliary/scanner/mqtt/connect.rb +++ b/modules/auxiliary/scanner/mqtt/connect.rb @@ -31,7 +31,9 @@ class MetasploitModule < Msf::Auxiliary 'DefaultOptions' => { 'BLANK_PASSWORDS' => false, - 'USER_AS_PASS' => true + 'USER_AS_PASS' => true, + 'USER_FILE' => 'data/wordlists/unix_users.txt', + 'PASS_FILE' => 'data/wordlists/unix_passwords.txt' } ) end From 0f72ce1ee520972f45c2eda58514071605b7f27c Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:45:10 -0800 Subject: [PATCH 12/18] Add WIP documentation for auxiliary/scanner/mqtt/connect --- .../modules/auxiliary/scanner/mqtt/connect.md | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 documentation/modules/auxiliary/scanner/mqtt/connect.md diff --git a/documentation/modules/auxiliary/scanner/mqtt/connect.md b/documentation/modules/auxiliary/scanner/mqtt/connect.md new file mode 100644 index 0000000000..4ca8322d60 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/mqtt/connect.md @@ -0,0 +1,74 @@ +## Vulnerable Application + +Most any MQTT instance will work. Instructions for testing against a Dockerized endpoint are provided below. + +### Docker Install + +A dockerized version of [mosquitto](https://mosquitto.org/) is available +[here](https://github.com/toke/docker-mosquitto). There are two basic +scenarios worth discussing -- mosquitto with anonymous authentication allowed +and disallowed. The method for running both is similar. + +#### Docker MQTT Server With Anonymous Authentication + +By default, mosquitto does not require credentials and allows anonymous authentication. To run in this way: + +``` +$ docker run -i -p 1883:1883 toke/mosquitto +1513822879: mosquitto version 1.4.14 (build date Mon, 10 Jul 2017 23:48:43 +0100) starting +1513822879: Config loaded from /mqtt/config/mosquitto.conf. +1513822879: Opening websockets listen socket on port 9001. +1513822879: Opening ipv4 listen socket on port 1883. +1513822879: Opening ipv6 listen socket on port 1883. +``` + +#### Docker MQTT Server Without Anonymous Authenticaiton + +Msquitto can be configured to require credentials. To run in this way: + + 1. Create a simple configuration file: + ```` + $ mkdir -p config && cat > config/mosquitto.conf + password_file /mqtt/config/passwd + allow_anonymous false + ``` + 2. Create a password file for mosquitto (this example creates a user test_user with password test_pass) + ``` + $ touch config/passwd && mosquitto_passwd -b config/passwd test_user test_pass + ``` + 1. Now run the dockerized mosquitto instance, mounting the configuration files from above for use at runtime: + ``` + $ docker run -ti -p 1883:1883 -v `pwd`/config/:/mqtt/config:ro toke/mosquitto + 1513823564: mosquitto version 1.4.14 (build date Mon, 10 Jul 2017 23:48:43 +0100) starting + 1513823564: Config loaded from /mqtt/config/mosquitto.conf. + 1513823564: Opening ipv4 listen socket on port 1883. + 1513823564: Opening ipv6 listen socket on port 1883. + ``` + +## Verification Steps + + + 1. Install the application without credentials + 2. Start msfconsole + 3. Do: ```use auxiliary/scanner/mqtt/connect``` + 4. Do: ```set rhosts [IPs]``` + 5. Do: ```run``` + 6. Confirm that the default or non-default credentials are discovered as configured + +## Options + + **CLIENT_ID** + + When specified, this will set the ID of the client when connecting to the MQTT endpoint. While + not all MQTT implementation support this, some, like mosquitto, support filtering by client ID and + this option can be used in those scenarios. By default, a random ID is selected. + +## Scenarios + +### Docker MQTT Server Without Credentials +``` +``` + +### Docker MQTT Server With Credentials +``` +``` From 508253eadc816f14619e255fb2bfb98713467dd3 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:51:44 -0800 Subject: [PATCH 13/18] More docs --- .../modules/auxiliary/scanner/mqtt/connect.md | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/documentation/modules/auxiliary/scanner/mqtt/connect.md b/documentation/modules/auxiliary/scanner/mqtt/connect.md index 4ca8322d60..f0fcf092dc 100644 --- a/documentation/modules/auxiliary/scanner/mqtt/connect.md +++ b/documentation/modules/auxiliary/scanner/mqtt/connect.md @@ -32,9 +32,9 @@ Msquitto can be configured to require credentials. To run in this way: password_file /mqtt/config/passwd allow_anonymous false ``` - 2. Create a password file for mosquitto (this example creates a user test_user with password test_pass) + 2. Create a password file for mosquitto (this example creates a user admin wtth password admin) ``` - $ touch config/passwd && mosquitto_passwd -b config/passwd test_user test_pass + $ touch config/passwd && mosquitto_passwd -b config/passwd admin admin ``` 1. Now run the dockerized mosquitto instance, mounting the configuration files from above for use at runtime: ``` @@ -66,9 +66,32 @@ Msquitto can be configured to require credentials. To run in this way: ## Scenarios ### Docker MQTT Server Without Credentials + +Configure MQTT in a Docker container without credentials as described above. + ``` +> use auxiliary/scanner/mqtt/connect +> set VERBOSE false +VERBOSE => false +> set RHOSTS localhost +RHOSTS => localhost +> run +[+] 127.0.0.1:1883 - Does not require authentication +[*] Scanned 1 of 1 hosts (100% complete) ``` ### Docker MQTT Server With Credentials + +Configure MQTT in a Docker container with credentials as described above. + ``` +> use auxiliary/scanner/mqtt/connect +> set VERBOSE false +FALSE => false +resource (mqtt.rc)> set RHOSTS localhost +RHOSTS => localhost +resource (mqtt.rc)> run +... +[+] 127.0.0.1:1883 - MQTT Login Successful: admin/admin + ``` From fa1536209aa6a0eccb7c13b1eb0e2bf33a6101e0 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:52:34 -0800 Subject: [PATCH 14/18] syntax --- documentation/modules/auxiliary/scanner/mqtt/connect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/auxiliary/scanner/mqtt/connect.md b/documentation/modules/auxiliary/scanner/mqtt/connect.md index f0fcf092dc..3be8ef856e 100644 --- a/documentation/modules/auxiliary/scanner/mqtt/connect.md +++ b/documentation/modules/auxiliary/scanner/mqtt/connect.md @@ -27,7 +27,7 @@ $ docker run -i -p 1883:1883 toke/mosquitto Msquitto can be configured to require credentials. To run in this way: 1. Create a simple configuration file: - ```` + ``` $ mkdir -p config && cat > config/mosquitto.conf password_file /mqtt/config/passwd allow_anonymous false From 9c0df54f36e35c6bf0e2fb5592657da0713e2028 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 18:54:09 -0800 Subject: [PATCH 15/18] syntax --- documentation/modules/auxiliary/scanner/mqtt/connect.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/modules/auxiliary/scanner/mqtt/connect.md b/documentation/modules/auxiliary/scanner/mqtt/connect.md index 3be8ef856e..c1ab1c144c 100644 --- a/documentation/modules/auxiliary/scanner/mqtt/connect.md +++ b/documentation/modules/auxiliary/scanner/mqtt/connect.md @@ -36,7 +36,7 @@ Msquitto can be configured to require credentials. To run in this way: ``` $ touch config/passwd && mosquitto_passwd -b config/passwd admin admin ``` - 1. Now run the dockerized mosquitto instance, mounting the configuration files from above for use at runtime: + 3. Now run the dockerized mosquitto instance, mounting the configuration files from above for use at runtime: ``` $ docker run -ti -p 1883:1883 -v `pwd`/config/:/mqtt/config:ro toke/mosquitto 1513823564: mosquitto version 1.4.14 (build date Mon, 10 Jul 2017 23:48:43 +0100) starting @@ -65,7 +65,7 @@ Msquitto can be configured to require credentials. To run in this way: ## Scenarios -### Docker MQTT Server Without Credentials +### Docker MQTT Server With Anonymous Authentication Configure MQTT in a Docker container without credentials as described above. @@ -80,7 +80,7 @@ RHOSTS => localhost [*] Scanned 1 of 1 hosts (100% complete) ``` -### Docker MQTT Server With Credentials +### Docker MQTT Server Without Anonymous Authentication Configure MQTT in a Docker container with credentials as described above. From 917e9aa328e354a4269edc1446131446187a8b6b Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 19:10:49 -0800 Subject: [PATCH 16/18] Doc READ_TIMEOUT --- documentation/modules/auxiliary/scanner/mqtt/connect.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/modules/auxiliary/scanner/mqtt/connect.md b/documentation/modules/auxiliary/scanner/mqtt/connect.md index c1ab1c144c..459f3b0eac 100644 --- a/documentation/modules/auxiliary/scanner/mqtt/connect.md +++ b/documentation/modules/auxiliary/scanner/mqtt/connect.md @@ -63,6 +63,10 @@ Msquitto can be configured to require credentials. To run in this way: not all MQTT implementation support this, some, like mosquitto, support filtering by client ID and this option can be used in those scenarios. By default, a random ID is selected. + **READ_TIMEOUT** + + The amount of time, in seconds, to wait for responses from the MQTT endpoint. + ## Scenarios ### Docker MQTT Server With Anonymous Authentication From 6f1196d30c34ff669d61489da0da7e9a77f44100 Mon Sep 17 00:00:00 2001 From: Brent Cook Date: Wed, 27 Dec 2017 22:32:08 -0600 Subject: [PATCH 17/18] clarify what's happening when there is a connection failure --- lib/metasploit/framework/login_scanner/mqtt.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/mqtt.rb b/lib/metasploit/framework/login_scanner/mqtt.rb index 25d1bceb9a..fce6134239 100644 --- a/lib/metasploit/framework/login_scanner/mqtt.rb +++ b/lib/metasploit/framework/login_scanner/mqtt.rb @@ -61,10 +61,10 @@ module Metasploit if connect_res.return_code == 0 status = Metasploit::Model::Login::Status::SUCCESSFUL - proof = "Succesful Connack" + proof = "Successful Connection (Received CONNACK packet)" else status = Metasploit::Model::Login::Status::INCORRECT - proof = "Failed Connack (#{connect_res.return_code})" + proof = "Failed Connection (#{connect_res.return_code})" end result_options.merge!( From ae17943d4c5ccb606162eb503dc7bd8d7987c404 Mon Sep 17 00:00:00 2001 From: Brent Cook Date: Wed, 27 Dec 2017 22:32:26 -0600 Subject: [PATCH 18/18] fix documentation preformat blocks --- .../modules/auxiliary/scanner/mqtt/connect.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/documentation/modules/auxiliary/scanner/mqtt/connect.md b/documentation/modules/auxiliary/scanner/mqtt/connect.md index 459f3b0eac..dd2684bef2 100644 --- a/documentation/modules/auxiliary/scanner/mqtt/connect.md +++ b/documentation/modules/auxiliary/scanner/mqtt/connect.md @@ -27,16 +27,21 @@ $ docker run -i -p 1883:1883 toke/mosquitto Msquitto can be configured to require credentials. To run in this way: 1. Create a simple configuration file: + ``` $ mkdir -p config && cat > config/mosquitto.conf password_file /mqtt/config/passwd allow_anonymous false ``` + 2. Create a password file for mosquitto (this example creates a user admin wtth password admin) + ``` $ touch config/passwd && mosquitto_passwd -b config/passwd admin admin ``` + 3. Now run the dockerized mosquitto instance, mounting the configuration files from above for use at runtime: + ``` $ docker run -ti -p 1883:1883 -v `pwd`/config/:/mqtt/config:ro toke/mosquitto 1513823564: mosquitto version 1.4.14 (build date Mon, 10 Jul 2017 23:48:43 +0100) starting @@ -50,9 +55,9 @@ Msquitto can be configured to require credentials. To run in this way: 1. Install the application without credentials 2. Start msfconsole - 3. Do: ```use auxiliary/scanner/mqtt/connect``` - 4. Do: ```set rhosts [IPs]``` - 5. Do: ```run``` + 3. Do: `use auxiliary/scanner/mqtt/connect` + 4. Do: `set rhosts [IPs]` + 5. Do: `run` 6. Confirm that the default or non-default credentials are discovered as configured ## Options