Land #9330, add MQTT scanner

MS-2855/keylogger-mettle-extension
Brent Cook 2017-12-27 22:32:59 -06:00
commit 8c2c30c230
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
5 changed files with 328 additions and 3 deletions

View File

@ -1,3 +1,4 @@
admin
123456
12345
123456789

View File

@ -0,0 +1,106 @@
## 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 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
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.
**READ_TIMEOUT**
The amount of time, in seconds, to wait for responses from the MQTT endpoint.
## Scenarios
### Docker MQTT Server With Anonymous Authentication
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 Without Anonymous Authentication
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
```

View File

@ -0,0 +1,88 @@
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
client.disconnect
if connect_res.return_code == 0
status = Metasploit::Model::Login::Status::SUCCESSFUL
proof = "Successful Connection (Received CONNACK packet)"
else
status = Metasploit::Model::Login::Status::INCORRECT
proof = "Failed Connection (#{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

View File

@ -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])

View File

@ -0,0 +1,125 @@
##
# 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 <jon_hart[at]rapid7.com>'
],
'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' => false,
'USER_AS_PASS' => true,
'USER_FILE' => 'data/wordlists/unix_users.txt',
'PASS_FILE' => 'data/wordlists/unix_passwords.txt'
}
)
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)
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'],
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: rhost,
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("MQTT Login Successful: #{username}/#{password}")
else
invalidate_login(credential_data)
vprint_error("MQTT LOGIN FAILED: #{username}/#{password} (#{result.proof})")
end
end
end
end