|
| 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 |
0 commit comments