Land #17828, add AMQP login scanner module

This commit is contained in:
space-r7
2023-03-29 09:24:48 -05:00
4 changed files with 284 additions and 0 deletions
@@ -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
+8
View File
@@ -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