Land #17828, add AMQP login scanner module
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module will test AMQP logins on a range of machines and report successful logins. If you have loaded a database
|
||||
plugin and connected to a database this module will record successful logins and hosts so you can track your access.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install RabbitMQ and start it
|
||||
1. To use Docker, run: `docker run --rm -it --hostname "$(hostname)" -p 15672:15672 -p 5672:5672 rabbitmq:3-management`
|
||||
2. Start msfconsole
|
||||
3. Do: `use auxiliary/scanner/amqp/amqp_login`
|
||||
4. Do: `set rhosts`
|
||||
5. Do: set usernames and passwords via any of the available options
|
||||
6. Do: `run`
|
||||
|
||||
## Options
|
||||
|
||||
### BLANK_PASSWORD
|
||||
|
||||
Boolean value on if an additional login attempt should be attempted with an empty password for every user.
|
||||
|
||||
### PASSWORD
|
||||
|
||||
Password to try for each user.
|
||||
|
||||
### PASS_FILE
|
||||
|
||||
A file containing a password on every line. Kali linux example: `/usr/share/wordlists/metasploit/password.lst`
|
||||
|
||||
### STOP_ON_SUCCESS
|
||||
|
||||
If a valid login is found on a host, immediately stop attempting additional logins on that host.
|
||||
|
||||
### USERNAME
|
||||
|
||||
Username to try for each password.
|
||||
|
||||
### USERPASS_FILE
|
||||
|
||||
A file containing a username and password, separated by a space, on every line. An example line would be `username
|
||||
password`.
|
||||
|
||||
### USER_AS_PASS
|
||||
|
||||
Boolean value on if an additional login attempt should be attempted with the password as the username.
|
||||
|
||||
### USER_FILE
|
||||
|
||||
A file containing a username on every line.
|
||||
|
||||
### VERBOSE
|
||||
|
||||
Show a failed login attempt. This can get rather verbose when large `USER_FILE`s or `PASS_FILE`s are used. A failed
|
||||
attempt will look similar to the following:
|
||||
|
||||
```
|
||||
[-] 192.168.159.128:5672 - LOGIN FAILED: admin:Password1! (Incorrect: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.)
|
||||
```
|
||||
|
||||
## Option Combinations
|
||||
|
||||
It is important to note that usernames and passwords can be entered in multiple combinations. For instance, a password
|
||||
could be set in `PASSWORD`, be part of either `PASS_FILE` or `USERPASS_FILE`, be guessed via `USER_AS_PASS` or
|
||||
`BLANK_PASSWORDS`. This module makes a combination of all of the above when attempting logins. So if a password is set
|
||||
in `PASSWORD`, and a `PASS_FILE` is listed, passwords will be generated from BOTH of these.
|
||||
|
||||
## Scenarios
|
||||
### RabbitMQ 3.11.10 on Docker
|
||||
|
||||
The Docker container listens on 5672/tcp without SSL. There's also an administrative site running on 15672/tcp where
|
||||
users can be added. The default credentials to login are `guest` / `guest`. A new `admin` account was added for this
|
||||
example.
|
||||
|
||||
```
|
||||
msf6 > use auxiliary/scanner/amqp/amqp_login
|
||||
msf6 auxiliary(scanner/amqp/amqp_login) > set RHOSTS 192.168.159.128
|
||||
RHOSTS => 192.168.159.128
|
||||
msf6 auxiliary(scanner/amqp/amqp_login) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf6 auxiliary(scanner/amqp/amqp_login) > set PASS_FILE data/wordlists/unix_passwords.txt
|
||||
PASS_FILE => data/wordlists/unix_passwords.txt
|
||||
msf6 auxiliary(scanner/amqp/amqp_login) > set RPORT 5672
|
||||
RPORT => 5672
|
||||
msf6 auxiliary(scanner/amqp/amqp_login) > set SSL false
|
||||
[!] Changing the SSL option's value may require changing RPORT!
|
||||
SSL => false
|
||||
msf6 auxiliary(scanner/amqp/amqp_login) > run
|
||||
|
||||
[-] 192.168.159.128:5672 - LOGIN FAILED: admin:Password1! (Incorrect: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.)
|
||||
[-] 192.168.159.128:5672 - LOGIN FAILED: admin:admin (Incorrect: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.)
|
||||
[-] 192.168.159.128:5672 - LOGIN FAILED: admin:123456 (Incorrect: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.)
|
||||
[-] 192.168.159.128:5672 - LOGIN FAILED: admin:12345 (Incorrect: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.)
|
||||
[-] 192.168.159.128:5672 - LOGIN FAILED: admin:123456789 (Incorrect: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.)
|
||||
[+] 192.168.159.128:5672 - Login Successful: admin:password
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(scanner/amqp/amqp_login) >
|
||||
```
|
||||
@@ -0,0 +1,89 @@
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
require 'rex/proto/amqp'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
class AMQP
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
|
||||
DEFAULT_PORT = 5671
|
||||
LIKELY_PORTS = [ DEFAULT_PORT, 5672 ]
|
||||
LIKELY_SERVICE_NAMES = [ 'amqp', 'amqps' ]
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
REALM_KEY = nil
|
||||
|
||||
# (see Base#attempt_login)
|
||||
def attempt_login(credential)
|
||||
result_options = {
|
||||
credential: credential
|
||||
}
|
||||
|
||||
begin
|
||||
result_options.merge!(connect_login(credential.public, credential.private))
|
||||
rescue Rex::Proto::Amqp::Error::NegotiationError => e
|
||||
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
result_options[:proof] = e.message
|
||||
rescue Rex::Proto::Amqp::Error::AmqpError
|
||||
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
rescue ::EOFError, Errno::ECONNRESET, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error
|
||||
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
end
|
||||
|
||||
result = ::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
result.host = host
|
||||
result.port = port
|
||||
result.protocol = 'tcp'
|
||||
result.service_name = "amqp#{ssl ? 's' : ''}"
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connect_login(username, password)
|
||||
result = {}
|
||||
amqp_client = Rex::Proto::Amqp::Version091::Client.new(
|
||||
host,
|
||||
port: port,
|
||||
context: { 'Msf' => framework, 'MsfExploit' => framework_module },
|
||||
ssl: ssl,
|
||||
ssl_version: ssl_version
|
||||
)
|
||||
amqp_client.connect(connection_timeout)
|
||||
amqp_client.send_protocol_header
|
||||
amqp_client.connection_start(username, password)
|
||||
resp = amqp_client.recv_frame
|
||||
|
||||
unless resp.is_a?(Rex::Proto::Amqp::Version091::Frames::AmqpVersion091MethodFrame)
|
||||
raise Rex::Proto::Amqp::Error::UnexpectedReplyError.new(resp)
|
||||
end
|
||||
|
||||
if resp.class_id == Rex::Proto::Amqp::Version091::Frames::MethodArguments::AmqpVersion091ConnectionClose::CLASS_ID && \
|
||||
resp.method_id == Rex::Proto::Amqp::Version091::Frames::MethodArguments::AmqpVersion091ConnectionClose::METHOD_ID
|
||||
result[:status] = Metasploit::Model::Login::Status::INCORRECT
|
||||
result[:proof] = resp.arguments.reply_text
|
||||
return result
|
||||
end
|
||||
|
||||
unless resp.class_id == Rex::Proto::Amqp::Version091::Frames::MethodArguments::AmqpVersion091ConnectionTune::CLASS_ID && \
|
||||
resp.method_id == Rex::Proto::Amqp::Version091::Frames::MethodArguments::AmqpVersion091ConnectionTune::METHOD_ID
|
||||
raise Rex::Proto::Amqp::Error::UnexpectedReplyError.new(resp)
|
||||
end
|
||||
|
||||
result[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
result
|
||||
ensure
|
||||
amqp_client.close
|
||||
end
|
||||
|
||||
def set_sane_defaults
|
||||
self.connection_timeout ||= 30
|
||||
self.port ||= DEFAULT_PORT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex::Proto::Amqp
|
||||
|
||||
require 'rex/proto/amqp/error'
|
||||
require 'rex/proto/amqp/version_0_9_1'
|
||||
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
##
|
||||
# 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/amqp'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
# Creates an instance of this module.
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'AMQP 0-9-1 Login Check Scanner',
|
||||
'Description' => %q{
|
||||
This module will test AMQP logins on a range of machines and
|
||||
report successful logins. If you have loaded a database plugin
|
||||
and connected to a database this module will record successful
|
||||
logins and hosts so you can track your access.
|
||||
},
|
||||
'Author' => [ 'Spencer McIntyre' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
[ 'URL', 'https://www.rabbitmq.com/amqp-0-9-1-reference.html' ]
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => []
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(5671)
|
||||
]
|
||||
)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('SSL', [ true, 'Negotiate SSL/TLS for outgoing connections', true ]),
|
||||
Opt::SSLVersion
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
cred_collection = build_credential_collection(
|
||||
username: datastore['USERNAME'],
|
||||
password: datastore['PASSWORD']
|
||||
)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::AMQP.new(
|
||||
host: ip,
|
||||
port: datastore['RPORT'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
ssl: datastore['SSL'],
|
||||
ssl_version: datastore['SSLVersion']
|
||||
)
|
||||
|
||||
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}:#{datastore['RPORT']} - Login Successful: #{result.credential}"
|
||||
else
|
||||
invalidate_login(credential_data)
|
||||
vprint_error "#{ip}:#{datastore['RPORT']} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user