Land #9330, add MQTT scanner
commit
8c2c30c230
|
@ -1,3 +1,4 @@
|
|||
admin
|
||||
123456
|
||||
12345
|
||||
123456789
|
||||
|
|
|
@ -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
|
||||
|
||||
```
|
|
@ -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
|
|
@ -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])
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue