From f15309bc48d39671cb1c60fc892332164a0be79a Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 12:28:02 -0800 Subject: [PATCH 1/7] Add basic framework for interacting with MQTT --- Gemfile.lock | 2 + lib/msf/core/auxiliary/mixins.rb | 1 + lib/msf/core/auxiliary/mqtt.rb | 69 ++++++++++++++++++++++++++++++++ lib/rex/proto/mqtt.rb | 14 +++++++ lib/rex/proto/mqtt/client.rb | 42 +++++++++++++++++++ metasploit-framework.gemspec | 1 + 6 files changed, 129 insertions(+) create mode 100644 lib/msf/core/auxiliary/mqtt.rb create mode 100644 lib/rex/proto/mqtt.rb create mode 100644 lib/rex/proto/mqtt/client.rb diff --git a/Gemfile.lock b/Gemfile.lock index 5e74ca08f3..3fdd329350 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,6 +20,7 @@ PATH metasploit-payloads (= 1.3.19) metasploit_data_models metasploit_payloads-mettle (= 0.3.2) + mqtt msgpack nessus_rest net-ssh @@ -192,6 +193,7 @@ GEM method_source (0.9.0) mini_portile2 (2.3.0) minitest (5.10.3) + mqtt (0.5.0) msgpack (1.2.0) multi_json (1.12.2) multipart-post (2.0.0) diff --git a/lib/msf/core/auxiliary/mixins.rb b/lib/msf/core/auxiliary/mixins.rb index 296a48d438..f5744ea480 100644 --- a/lib/msf/core/auxiliary/mixins.rb +++ b/lib/msf/core/auxiliary/mixins.rb @@ -23,6 +23,7 @@ require 'msf/core/auxiliary/cisco' require 'msf/core/auxiliary/kademlia' require 'msf/core/auxiliary/llmnr' require 'msf/core/auxiliary/mdns' +require 'msf/core/auxiliary/mqtt' require 'msf/core/auxiliary/nmap' require 'msf/core/auxiliary/natpmp' require 'msf/core/auxiliary/iax2' diff --git a/lib/msf/core/auxiliary/mqtt.rb b/lib/msf/core/auxiliary/mqtt.rb new file mode 100644 index 0000000000..0094d7879f --- /dev/null +++ b/lib/msf/core/auxiliary/mqtt.rb @@ -0,0 +1,69 @@ +# -*- coding: binary -*- + +require 'msf/core/exploit' +require 'rex/proto/mqtt' + +module Msf + module Auxiliary::MQTT + include Exploit::Remote::Tcp + + def initialize(info = {}) + super + + register_options( + [ + Opt::RPORT(Rex::Proto::MQTT::DEFAULT_PORT), + OptString.new('USERNAME', [false, 'The user to authenticate as']), + OptString.new('PASSWORD', [false, 'The password to authenticate with']) + ] + ) + + register_advanced_options( + [ + OptString.new('CLIENT_ID', [false, 'The client ID to send if necessary for bypassing clientid_prefixes']), + OptInt.new('READ_TIMEOUT', [true, 'Seconds to wait while reading MQTT responses', 5]) + ] + ) + + register_autofilter_ports([Rex::Proto::MQTT::DEFAULT_PORT]) + end + + def setup + fail_with(Failure::BadConfig, 'READ_TIMEOUT must be > 0') if read_timeout <= 0 + client_id_arg = datastore['CLIENT_ID'] + if client_id_arg + fail_with(Failure::BadConfig, 'CLIENT_ID must be a non-empty string') if client_id_arg.blank? + end + end + + def read_timeout + datastore['READ_TIMEOUT'] + end + + def client_id + datastore['CLIENT_ID'] || Rex::Text.rand_text_alpha(1 + rand(10)) + end + + def mqtt_client + client_opts = { + client_id: client_id().to_s, + username: datastore['USERNAME'], + password: datastore['PASSWORD'], + read_timeout: read_timeout + } + Rex::Proto::MQTT::Client.new(sock, client_opts) + end + + def mqtt_connect(client) + client.connect + end + + def mqtt_connect?(client) + mqtt_connect(client).return_code == 0 + end + + def mqtt_disconnect(client) + client.disconnect + end + end +end diff --git a/lib/rex/proto/mqtt.rb b/lib/rex/proto/mqtt.rb new file mode 100644 index 0000000000..c10e4e88cb --- /dev/null +++ b/lib/rex/proto/mqtt.rb @@ -0,0 +1,14 @@ +# -*- coding: binary -*- +# +# Support for MQTT + +require 'rex/proto/mqtt/client' + +module Rex + module Proto + module MQTT + DEFAULT_PORT = 1883 + DEFAULT_SSL_PORT = 8883 + end + end +end diff --git a/lib/rex/proto/mqtt/client.rb b/lib/rex/proto/mqtt/client.rb new file mode 100644 index 0000000000..79ef88c217 --- /dev/null +++ b/lib/rex/proto/mqtt/client.rb @@ -0,0 +1,42 @@ +# -*- coding: binary -*- + +require 'mqtt' + +## +# MQTT protocol support +## + +module Rex + module Proto + module MQTT + class Client + + def initialize(sock, opts = {}) + @sock = sock + @opts = opts + end + + def connect + connect_opts = { + client_id: @opts[:client_id], + username: @opts[:username], + password: @opts[:password] + } + connect = ::MQTT::Packet::Connect.new(connect_opts).to_s + @sock.put(connect) + res = @sock.get_once(-1, @opts[:read_timeout]) + ::MQTT::Packet.parse(res) + end + + def connect? + connect.return_code == 0 + end + + def disconnect + disconnect = ::MQTT::Packet::Disconnect.new().to_s + @sock.put(disconnect) + end + end + end + end +end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 00dfecfd41..d9df75c633 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -123,6 +123,7 @@ Gem::Specification.new do |spec| # Protocol Libraries # spec.add_runtime_dependency 'dnsruby' + spec.add_runtime_dependency 'mqtt' spec.add_runtime_dependency 'net-ssh' spec.add_runtime_dependency 'rbnacl', ['< 5.0.0'] spec.add_runtime_dependency 'bcrypt_pbkdf' From ac1daaf10e03e345758ed6953974e20b9007b81a Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 12:41:44 -0800 Subject: [PATCH 2/7] Fix rubocop warning --- lib/msf/core/auxiliary/mqtt.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/auxiliary/mqtt.rb b/lib/msf/core/auxiliary/mqtt.rb index 0094d7879f..3604d3513d 100644 --- a/lib/msf/core/auxiliary/mqtt.rb +++ b/lib/msf/core/auxiliary/mqtt.rb @@ -65,5 +65,5 @@ module Msf def mqtt_disconnect(client) client.disconnect end - end + end end From 741d08f604c5a3e2221b5008ae41f3961d3755c6 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 13:33:47 -0800 Subject: [PATCH 3/7] Style cleanup --- lib/msf/core/auxiliary/mqtt.rb | 9 ++++----- lib/rex/proto/mqtt/client.rb | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/auxiliary/mqtt.rb b/lib/msf/core/auxiliary/mqtt.rb index 3604d3513d..e4df359b54 100644 --- a/lib/msf/core/auxiliary/mqtt.rb +++ b/lib/msf/core/auxiliary/mqtt.rb @@ -5,8 +5,6 @@ require 'rex/proto/mqtt' module Msf module Auxiliary::MQTT - include Exploit::Remote::Tcp - def initialize(info = {}) super @@ -25,7 +23,7 @@ module Msf ] ) - register_autofilter_ports([Rex::Proto::MQTT::DEFAULT_PORT]) + register_autofilter_ports([Rex::Proto::MQTT::DEFAULT_PORT, Rex::Proto::MQTT::DEFAULT_PORT]) end def setup @@ -44,9 +42,10 @@ module Msf datastore['CLIENT_ID'] || Rex::Text.rand_text_alpha(1 + rand(10)) end + # creates a new mqtt client for use against the connected socket def mqtt_client client_opts = { - client_id: client_id().to_s, + client_id: client_id.to_s, username: datastore['USERNAME'], password: datastore['PASSWORD'], read_timeout: read_timeout @@ -59,7 +58,7 @@ module Msf end def mqtt_connect?(client) - mqtt_connect(client).return_code == 0 + client.connect? end def mqtt_disconnect(client) diff --git a/lib/rex/proto/mqtt/client.rb b/lib/rex/proto/mqtt/client.rb index 79ef88c217..ed9df19b95 100644 --- a/lib/rex/proto/mqtt/client.rb +++ b/lib/rex/proto/mqtt/client.rb @@ -29,7 +29,7 @@ module Rex end def connect? - connect.return_code == 0 + connect.return_code.zero? end def disconnect From ddb2566f3b750633667282d85a309246b69013fb Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 19:09:35 -0800 Subject: [PATCH 4/7] Remove duplicate options, set less suspicious client_id --- lib/msf/core/auxiliary/mqtt.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/auxiliary/mqtt.rb b/lib/msf/core/auxiliary/mqtt.rb index e4df359b54..d318431a37 100644 --- a/lib/msf/core/auxiliary/mqtt.rb +++ b/lib/msf/core/auxiliary/mqtt.rb @@ -10,9 +10,7 @@ module Msf register_options( [ - Opt::RPORT(Rex::Proto::MQTT::DEFAULT_PORT), - OptString.new('USERNAME', [false, 'The user to authenticate as']), - OptString.new('PASSWORD', [false, 'The password to authenticate with']) + Opt::RPORT(Rex::Proto::MQTT::DEFAULT_PORT) ] ) @@ -39,7 +37,7 @@ module Msf end def client_id - datastore['CLIENT_ID'] || Rex::Text.rand_text_alpha(1 + rand(10)) + datastore['CLIENT_ID'] || 'mqtt-' + Rex::Text.rand_text_alpha(1 + rand(10)) end # creates a new mqtt client for use against the connected socket From b78f1105f7f7aa2097fbeb1932cf07ed544616e9 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 19:11:33 -0800 Subject: [PATCH 5/7] Add missing port --- lib/msf/core/auxiliary/mqtt.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/auxiliary/mqtt.rb b/lib/msf/core/auxiliary/mqtt.rb index d318431a37..d2d7f57b17 100644 --- a/lib/msf/core/auxiliary/mqtt.rb +++ b/lib/msf/core/auxiliary/mqtt.rb @@ -21,7 +21,7 @@ module Msf ] ) - register_autofilter_ports([Rex::Proto::MQTT::DEFAULT_PORT, Rex::Proto::MQTT::DEFAULT_PORT]) + register_autofilter_ports([Rex::Proto::MQTT::DEFAULT_PORT, Rex::Proto::MQTT::DEFAULT_SSL_PORT]) end def setup From 82bdce683bda590451b4b386e0d41ba800c89a8d Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 20 Dec 2017 19:13:12 -0800 Subject: [PATCH 6/7] Remove to_s --- lib/msf/core/auxiliary/mqtt.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/auxiliary/mqtt.rb b/lib/msf/core/auxiliary/mqtt.rb index d2d7f57b17..98745904b8 100644 --- a/lib/msf/core/auxiliary/mqtt.rb +++ b/lib/msf/core/auxiliary/mqtt.rb @@ -43,7 +43,7 @@ module Msf # creates a new mqtt client for use against the connected socket def mqtt_client client_opts = { - client_id: client_id.to_s, + client_id: client_id, username: datastore['USERNAME'], password: datastore['PASSWORD'], read_timeout: read_timeout From becc05b4f1c347a458401002de3c74738a0cacdb Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 21 Dec 2017 06:57:33 -0800 Subject: [PATCH 7/7] Cleaner client_id handling --- lib/msf/core/auxiliary/mqtt.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/auxiliary/mqtt.rb b/lib/msf/core/auxiliary/mqtt.rb index 98745904b8..9924ef1529 100644 --- a/lib/msf/core/auxiliary/mqtt.rb +++ b/lib/msf/core/auxiliary/mqtt.rb @@ -26,9 +26,10 @@ module Msf def setup fail_with(Failure::BadConfig, 'READ_TIMEOUT must be > 0') if read_timeout <= 0 + client_id_arg = datastore['CLIENT_ID'] - if client_id_arg - fail_with(Failure::BadConfig, 'CLIENT_ID must be a non-empty string') if client_id_arg.blank? + if client_id_arg && client_id_arg.blank? + fail_with(Failure::BadConfig, 'CLIENT_ID must be a non-empty string') end end