Skip to content

Commit

Permalink
Improves UX for scanner/login modules
Browse files Browse the repository at this point in the history
  • Loading branch information
cgranleese-r7 committed May 23, 2024
1 parent 6301d84 commit 6dd306e
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 62 deletions.
3 changes: 2 additions & 1 deletion lib/msf/base/sessions/create_session_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def initialize(info = {})

register_options(
[
OptBool.new('CreateSession', [false, 'Create a new session for every successful login', true])
OptBool.new('CreateSession', [false, 'Create a new session for every successful login', true]),
OptBool.new('ShowSuccessLogins', [false, 'Outputs a table of successful logins', true]),
]
)
end
Expand Down
86 changes: 86 additions & 0 deletions lib/msf/core/auxiliary/report_logins.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# -*- coding: binary -*-

module Msf
class Auxiliary
###
#
# This module provides a means to report successful logins
#
###
module ReportLogins
# Takes the login/session results and outputs statuses based upon successful credentials and sessions
# as well as conditionally outputting the results in a Rex::Text::Table format
#
# @param results [Hash{String => [Metasploit::Framework::LoginScanner::Result, Msf::Sessions]}]
# @return [Hash] Host mapped to successful logins and sessions
def successful_logins(results)
logins = results.flat_map { |_k, v| v[:successful_logins] }.compact
sessions = results.flat_map { |_k, v| v[:successful_sessions] }.compact

# return results unless framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE)

print_status("Bruteforce completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
if datastore['CreateSession']
print_status("#{sessions.size} MySQL #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
else
print_status('You can open an MySQL session with these credentials and %grnCreateSession%clr set to true')
end

show_successful_logins(results)

results
end

private

# Logic to detect if the ShowSuccessLogins datastore option has been set
#
# @param [Hash] results Host mapped to successful logins and sessions
# @return [String] Rex::Text::Table containing successful logins
def show_successful_logins(results)
if datastore['ShowSuccessLogins'] == true
successful_logins_to_table(results)
end
end

# The idea here is to add a hybrid approach for scanner modules
# If only one host is scanned a more verbose output is useful to the user
# If scanning multiple hosts we would want more lightweight information
#
# @param [Object] mod The framework module object
def conditional_verbose_output(host_count, mod)
if host_count == 1
mod.datastore['Verbose'] = true
end
end

# Takes the login/session results and converts them into a Rex::Text::Table format
#
# @param results [Hash{String => [Metasploit::Framework::LoginScanner::Result, Msf::Sessions]}]
# @return [String] Rex::Text::Table containing successful logins
def successful_logins_to_table(results)
field_headers = %w[Host Public Private]

markdown_fields = results.flat_map do |host, result|

if result[:successful_logins].nil?
next
end

result[:successful_logins].map do |res|
[host, res.credential.public, res.credential.private]
end
end

table = ::Rex::Text::Table.new(
'Header' => 'Successful logins',
'Indent' => 4,
'Columns' => field_headers,
'Rows' => markdown_fields.compact
)

print_line("\n" + table.to_s + "\n")
end
end
end
end
10 changes: 9 additions & 1 deletion lib/msf/core/auxiliary/scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module Auxiliary::Scanner
class AttemptFailed < Msf::Auxiliary::Failed
end

include Msf::Auxiliary::ReportLogins

#
# Initializes an instance of a recon auxiliary module
#
Expand Down Expand Up @@ -72,6 +74,7 @@ def run

res = Queue.new
results = Hash.new
host_response_map = Hash.new

#
# Sanity check threading given different conditions
Expand Down Expand Up @@ -125,7 +128,10 @@ def run
nmod.datastore = thr_datastore

begin
res << { targ => nmod.run_host(targ) }
conditional_verbose_output(rhosts_walker.count, nmod)
host_result = { targ => nmod.run_host(targ) }
host_response_map.merge!(host_result)
res << host_result
rescue ::Rex::BindFailed
if datastore['CHOST']
@scan_errors << "The source IP (CHOST) value of #{datastore['CHOST']} was not usable"
Expand Down Expand Up @@ -170,6 +176,8 @@ def run
end

scanner_handle_fatal_errors

successful_logins(host_response_map)
return results
end

Expand Down
15 changes: 0 additions & 15 deletions modules/auxiliary/scanner/mssql/mssql_login.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,6 @@ def create_session?
end
end

def run
results = super
logins = results.flat_map { |_k, v| v[:successful_logins] }
sessions = results.flat_map { |_k, v| v[:successful_sessions] }
print_status("Bruteforce completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
return results unless framework.features.enabled?(Msf::FeatureManager::MSSQL_SESSION_TYPE)

if create_session?
print_status("#{sessions.size} MSSQL #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
else
print_status('You can open an MSSQL session with these credentials and %grnCreateSession%clr set to true')
end
results
end

def run_host(ip)
print_status("#{rhost}:#{rport} - MSSQL - Starting authentication scanner.")

Expand Down
15 changes: 0 additions & 15 deletions modules/auxiliary/scanner/mysql/mysql_login.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,6 @@ def target
[rhost,rport].join(":")
end

def run
results = super
logins = results.flat_map { |_k, v| v[:successful_logins] }
sessions = results.flat_map { |_k, v| v[:successful_sessions] }
print_status("Bruteforce completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
return results unless framework.features.enabled?(Msf::FeatureManager::MYSQL_SESSION_TYPE)

if create_session?
print_status("#{sessions.size} MySQL #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
else
print_status('You can open an MySQL session with these credentials and %grnCreateSession%clr set to true')
end
results
end

def run_host(ip)
begin
if mysql_version_check("4.1.1") # Pushing down to 4.1.1.
Expand Down
15 changes: 0 additions & 15 deletions modules/auxiliary/scanner/postgres/postgres_login.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,6 @@ def create_session?
end
end

def run
results = super
logins = results.flat_map { |_k, v| v[:successful_logins] }
sessions = results.flat_map { |_k, v| v[:successful_sessions] }
print_status("Bruteforce completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
return results unless framework.features.enabled?(Msf::FeatureManager::POSTGRESQL_SESSION_TYPE)

if create_session?
print_status("#{sessions.size} Postgres #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
else
print_status('You can open a Postgres session with these credentials and %grnCreateSession%clr set to true')
end
results
end

# Loops through each host in turn. Note the current IP address is both
# ip and datastore['RHOST']
def run_host(ip)
Expand Down
15 changes: 0 additions & 15 deletions modules/auxiliary/scanner/smb/smb_login.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,6 @@ def create_session?
end
end

def run
results = super
logins = results.flat_map { |_k, v| v[:successful_logins] }
sessions = results.flat_map { |_k, v| v[:successful_sessions] }
print_status("Bruteforce completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
return results unless framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE)

if create_session?
print_status("#{sessions.size} SMB #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
else
print_status('You can open an SMB session with these credentials and %grnCreateSession%clr set to true')
end
results
end

def run_host(ip)
print_brute(level: :vstatus, ip: ip, msg: 'Starting SMB login bruteforce')

Expand Down

0 comments on commit 6dd306e

Please sign in to comment.