Skip to content

Commit bf17764

Browse files
authored
Land rapid7#19199, Improves UX for scanner/login modules
2 parents 2fa1bc6 + 60e5393 commit bf17764

19 files changed

+172
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# -*- coding: binary -*-
2+
3+
##
4+
# This file is part of the Metasploit Framework and may be subject to
5+
# redistribution and commercial restrictions. Please see the Metasploit
6+
# Framework web site for more information on licensing and terms of use.
7+
# https://metasploit.com/framework/
8+
##
9+
10+
module Msf
11+
class Auxiliary
12+
###
13+
#
14+
# This module provides a means to report module summaries
15+
#
16+
###
17+
module ReportSummary
18+
def initialize(info = {})
19+
super(info)
20+
21+
if framework.features.enabled?(Msf::FeatureManager::SHOW_SUCCESSFUL_LOGINS)
22+
register_options(
23+
[
24+
OptBool.new('ShowSuccessfulLogins', [false, 'Outputs a table of successful logins', true]),
25+
]
26+
)
27+
end
28+
end
29+
30+
def run
31+
return super unless framework.features.enabled?(Msf::FeatureManager::SHOW_SUCCESSFUL_LOGINS) && datastore['ShowSuccessfulLogins']
32+
33+
@report = {}
34+
@report.extend(::Rex::Ref)
35+
rhost_walker = Msf::RhostsWalker.new(datastore['RHOSTS'], datastore).to_enum
36+
conditional_verbose_output(rhost_walker.count)
37+
result = super
38+
print_report_summary
39+
result
40+
end
41+
42+
# Creates a credential and adds to to the DB if one is present
43+
#
44+
# @param [Hash] credential_data
45+
# @return [Metasploit::Credential::Login]
46+
def create_credential_login(credential_data)
47+
return super unless framework.features.enabled?(Msf::FeatureManager::SHOW_SUCCESSFUL_LOGINS) && datastore['ShowSuccessfulLogins']
48+
49+
credential = {
50+
public: credential_data[:username],
51+
private_data: credential_data[:private_data]
52+
}
53+
@report[rhost] = { successful_logins: [] }
54+
@report[rhost][:successful_logins] << credential
55+
super
56+
end
57+
58+
# Framework is notified that we have a new session opened
59+
#
60+
# @param [MetasploitModule] obj
61+
# @param [Object] info
62+
# @param [Hash] ds_merge
63+
# @param [FalseClass] crlf
64+
# @param [Socket] sock
65+
# @param [Msf::Sessions::<SESSION_CLASS>] sess
66+
# @return [Msf::Sessions::<SESSION_CLASS>]
67+
def start_session(obj, info, ds_merge, crlf = false, sock = nil, sess = nil)
68+
return super unless framework.features.enabled?(Msf::FeatureManager::SHOW_SUCCESSFUL_LOGINS) && datastore['ShowSuccessfulLogins']
69+
70+
result = super
71+
@report[rhost].merge!({ successful_sessions: [] })
72+
@report[rhost][:successful_sessions] << result
73+
result
74+
end
75+
76+
private
77+
78+
# Prints a summary of successful logins
79+
# Returns a ::Rex::Text::Table with the following data: host, public and private credentials for each
80+
# successful login per host
81+
#
82+
# @return [Hash] Rhost keys mapped to successful logins and sessions for each host
83+
def print_report_summary
84+
report = @report
85+
86+
logins = report.flat_map { |_k, v| v[:successful_logins] }.compact
87+
sessions = report.flat_map { |_k, v| v[:successful_sessions] }.compact
88+
89+
print_status("Scan completed, #{logins.size} #{logins.size == 1 ? 'credential was' : 'credentials were'} successful.")
90+
print_successful_logins(report)
91+
92+
if datastore['CreateSession']
93+
print_status("#{sessions.size} #{sessions.size == 1 ? 'session was' : 'sessions were'} opened successfully.")
94+
end
95+
96+
report
97+
end
98+
99+
# Logic to detect if the ShowSuccessLogins datastore option has been set
100+
#
101+
# @param [Hash] report Host mapped to successful logins and sessions
102+
# @return [String] Rex::Text::Table containing successful logins
103+
def print_successful_logins(report)
104+
if datastore['ShowSuccessfulLogins'] == true && !report.empty?
105+
table = successful_logins_to_table(report)
106+
print_line("\n" + table.to_s + "\n")
107+
end
108+
end
109+
110+
# The idea here is to add a hybrid approach for scanner modules
111+
# If only one host is scanned a more verbose output is useful to the user
112+
# If scanning multiple hosts we would want more lightweight information
113+
#
114+
# @param [Object] host_count The number of hosts
115+
def conditional_verbose_output(host_count)
116+
if host_count == 1
117+
datastore['Verbose'] = true
118+
end
119+
end
120+
121+
# Takes the login/session results and converts them into a Rex::Text::Table format
122+
#
123+
# @param report [Hash{String => [Metasploit::Framework::LoginScanner::Result, Msf::Sessions]}]
124+
# @return [Rex::Text::WrappedTable] Rex::Text::Table containing successful logins
125+
def successful_logins_to_table(report)
126+
field_headers = %w[Host Public Private]
127+
128+
markdown_fields = report.flat_map do |host, result|
129+
if result[:successful_logins].nil?
130+
next
131+
end
132+
133+
result[:successful_logins].map do |credential|
134+
[host, credential[:public], credential[:private_data]]
135+
end
136+
end
137+
138+
::Rex::Text::Table.new(
139+
'Header' => 'Successful logins',
140+
'Indent' => 4,
141+
'Columns' => field_headers,
142+
'Rows' => markdown_fields.compact
143+
)
144+
end
145+
end
146+
end
147+
end

lib/msf/core/feature_manager.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class FeatureManager
2727
MYSQL_SESSION_TYPE = 'mysql_session_type'
2828
MSSQL_SESSION_TYPE = 'mssql_session_type'
2929
LDAP_SESSION_TYPE = 'ldap_session_type'
30+
SHOW_SUCCESSFUL_LOGINS = 'show_successful_logins'
3031

3132
DEFAULTS = [
3233
{
@@ -103,6 +104,13 @@ class FeatureManager
103104
default_value: false,
104105
developer_notes: 'To be enabled by default after appropriate testing'
105106
}.freeze,
107+
{
108+
name: SHOW_SUCCESSFUL_LOGINS,
109+
description: 'When enabled scanners/login modules will return a table off successful logins once the module completes',
110+
requires_restart: false,
111+
default_value: false,
112+
developer_notes: 'To be enabled after appropriate testing'
113+
}.freeze,
106114
{
107115
name: DNS,
108116
description: 'When enabled allows configuration of DNS resolution behaviour in Metasploit',

modules/auxiliary/cloud/aws/enum_ssm.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class MetasploitModule < Msf::Auxiliary
1111
include Msf::Auxiliary::Report
1212
include Msf::Auxiliary::CommandShell
1313
include Msf::Sessions::CreateSessionOptions
14+
include Msf::Auxiliary::ReportSummary
1415

1516
def initialize(info = {})
1617
super(

modules/auxiliary/scanner/ldap/ldap_login.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class MetasploitModule < Msf::Auxiliary
1313
include Msf::Exploit::Remote::LDAP
1414
include Msf::Sessions::CreateSessionOptions
1515
include Msf::Auxiliary::CommandShell
16+
include Msf::Auxiliary::ReportSummary
1617

1718
def initialize(info = {})
1819
super(

modules/auxiliary/scanner/mssql/mssql_login.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class MetasploitModule < Msf::Auxiliary
1515
include Msf::Auxiliary::CommandShell
1616
include Msf::Auxiliary::Scanner
1717
include Msf::Sessions::CreateSessionOptions
18+
include Msf::Auxiliary::ReportSummary
1819

1920
def initialize
2021
super(

modules/auxiliary/scanner/mysql/mysql_login.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class MetasploitModule < Msf::Auxiliary
1313
include Msf::Auxiliary::Scanner
1414
include Msf::Sessions::CreateSessionOptions
1515
include Msf::Auxiliary::CommandShell
16+
include Msf::Auxiliary::ReportSummary
1617

1718
def initialize(info = {})
1819
super(update_info(info,

modules/auxiliary/scanner/postgres/postgres_login.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class MetasploitModule < Msf::Auxiliary
1414
include Msf::Auxiliary::Report
1515
include Msf::Auxiliary::CommandShell
1616
include Msf::Sessions::CreateSessionOptions
17+
include Msf::Auxiliary::ReportSummary
1718

1819
# Creates an instance of this module.
1920
def initialize(info = {})

modules/auxiliary/scanner/rservices/rexec_login.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class MetasploitModule < Msf::Auxiliary
1010
include Msf::Auxiliary::Scanner
1111
include Msf::Auxiliary::CommandShell
1212
include Msf::Sessions::CreateSessionOptions
13+
include Msf::Auxiliary::ReportSummary
1314

1415
def initialize
1516
super(

modules/auxiliary/scanner/rservices/rlogin_login.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class MetasploitModule < Msf::Auxiliary
1212
include Msf::Auxiliary::Login
1313
include Msf::Auxiliary::CommandShell
1414
include Msf::Sessions::CreateSessionOptions
15+
include Msf::Auxiliary::ReportSummary
1516

1617
def initialize
1718
super(

modules/auxiliary/scanner/rservices/rsh_login.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class MetasploitModule < Msf::Auxiliary
1111
include Msf::Auxiliary::Scanner
1212
include Msf::Auxiliary::CommandShell
1313
include Msf::Sessions::CreateSessionOptions
14+
include Msf::Auxiliary::ReportSummary
1415

1516
def initialize
1617
super(

0 commit comments

Comments
 (0)