diff --git a/docs/metasploit-framework.wiki/ad-certificates/Attacking-AD-CS-ESC-Vulnerabilities.md b/docs/metasploit-framework.wiki/ad-certificates/Attacking-AD-CS-ESC-Vulnerabilities.md index e6917b8aa95a..2e0f22c0f6d8 100644 --- a/docs/metasploit-framework.wiki/ad-certificates/Attacking-AD-CS-ESC-Vulnerabilities.md +++ b/docs/metasploit-framework.wiki/ad-certificates/Attacking-AD-CS-ESC-Vulnerabilities.md @@ -892,7 +892,7 @@ In the following example the AUTO mode is used to issue a certificate for the MS authenticated. ```msf -msf6 auxiliary(server/relay/esc8) > set RELAY_TARGETS 172.30.239.85 +msf6 auxiliary(server/relay/esc8) > set RHOSTS 172.30.239.85 msf6 auxiliary(server/relay/esc8) > run [*] Auxiliary module running as background job 1. msf6 auxiliary(server/relay/esc8) > diff --git a/lib/msf/core/auxiliary/multiple_target_hosts.rb b/lib/msf/core/auxiliary/multiple_target_hosts.rb new file mode 100644 index 000000000000..5e83d5e9d9e4 --- /dev/null +++ b/lib/msf/core/auxiliary/multiple_target_hosts.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +module Msf + +### +# +# This module provides methods for modules which intend to handle multiple hosts +# themselves through some means, e.g. scanners. This circumvents the typical +# RHOSTS -> RHOST logic offered by the framework. +# +### + +module Auxiliary::MultipleTargetHosts + + def has_check? + respond_to?(:check_host) + end + + def check + nmod = replicant + begin + nmod.check_host(datastore['RHOST']) + rescue NoMethodError + Exploit::CheckCode::Unsupported + end + end + +end +end diff --git a/lib/msf/core/auxiliary/scanner.rb b/lib/msf/core/auxiliary/scanner.rb index acceae5b5c5b..23a5037b64c9 100644 --- a/lib/msf/core/auxiliary/scanner.rb +++ b/lib/msf/core/auxiliary/scanner.rb @@ -10,6 +10,8 @@ module Msf module Auxiliary::Scanner +include Msf::Auxiliary::MultipleTargetHosts + class AttemptFailed < Msf::Auxiliary::Failed end @@ -31,20 +33,6 @@ def initialize(info = {}) end -def has_check? - respond_to?(:check_host) -end - -def check - nmod = replicant - begin - nmod.check_host(datastore['RHOST']) - rescue NoMethodError - Exploit::CheckCode::Unsupported - end -end - - def peer # IPv4 addr can be 16 chars + 1 for : and + 5 for port super.ljust(21) diff --git a/lib/msf/core/exploit/remote/smb/relay_server.rb b/lib/msf/core/exploit/remote/smb/relay_server.rb index e596fb399f58..594b999050bb 100644 --- a/lib/msf/core/exploit/remote/smb/relay_server.rb +++ b/lib/msf/core/exploit/remote/smb/relay_server.rb @@ -4,6 +4,7 @@ module Msf module Exploit::Remote::SMB # This mixin provides a minimal SMB server module RelayServer + include ::Msf::Auxiliary::MultipleTargetHosts include ::Msf::Exploit::Remote::SocketServer include ::Msf::Exploit::Remote::SMB::Server::HashCapture @@ -15,7 +16,7 @@ def initialize(info = {}) OptPort.new('SRVPORT', [true, 'The local port to listen on.', 445]), OptString.new('SMBDomain', [true, 'The domain name used during SMB exchange.', 'WORKGROUP'], aliases: ['DOMAIN_NAME']), OptInt.new('SRV_TIMEOUT', [true, 'Seconds that the server socket will wait for a response after the client has initiated communication.', 25]), - OptAddressRange.new('RELAY_TARGETS', [true, 'Target address range or CIDR identifier to relay to'], aliases: ['SMBHOST']), + OptAddressRange.new('RHOSTS', [true, 'Target address range or CIDR identifier to relay to'], aliases: ['SMBHOST', 'RELAY_TARGETS']), OptInt.new('RELAY_TIMEOUT', [true, 'Seconds that the relay socket will wait for a response after the client has initiated communication.', 25]) ], self.class) end diff --git a/lib/msf/core/exploit/remote/tcp.rb b/lib/msf/core/exploit/remote/tcp.rb index e0992907e2d1..153687db05b9 100644 --- a/lib/msf/core/exploit/remote/tcp.rb +++ b/lib/msf/core/exploit/remote/tcp.rb @@ -209,11 +209,22 @@ def print_prefix # Otherwise we are logging in the global context where rhost can be any # size (being an alias for rhosts), which is not very useful to insert into # a single log line. - if rhost && rhost.split(' ').length == 1 - super + peer + ' - ' - else - super + unless instance_variable_defined?(:@print_prefix) + if rhost.present? && Rex::Socket::RangeWalker.new(rhost).length == 1 + @print_prefix = peer + ' - ' + else + @print_prefix = '' + end end + + super + @print_prefix + end + + def replicant + obj = super + # invalidate the cached print_prefix in case the target changes + obj.remove_instance_variable(:@print_prefix) if instance_variable_defined?(:@print_prefix) + obj end ## @@ -259,7 +270,7 @@ def lport # Returns the rhost:rport def peer - "#{rhost}:#{rport}" + Rex::Socket.to_authority(rhost, rport) end # diff --git a/lib/msf/core/session.rb b/lib/msf/core/session.rb index d28eb65c5f8c..9add2cb91b41 100644 --- a/lib/msf/core/session.rb +++ b/lib/msf/core/session.rb @@ -187,9 +187,17 @@ def set_via(opts) # exploit instance. Store references from and to the exploit module. # def set_from_exploit(m) + target_host = nil + unless m.target_host.blank? + # only propagate the target_host value if it's exactly 1 host + if (rw = Rex::Socket::RangeWalker.new(m.target_host)).length == 1 + target_host = rw.next_ip + end + end + self.via = { 'Exploit' => m.fullname } self.via['Payload'] = ('payload/' + m.datastore['PAYLOAD'].to_s) if m.datastore['PAYLOAD'] - self.target_host = Rex::Socket.getaddress(m.target_host) if (m.target_host.to_s.strip.length > 0) + self.target_host = target_host self.target_port = m.target_port if (m.target_port.to_i != 0) self.workspace = m.workspace self.username = m.owner diff --git a/lib/msf/ui/console/command_dispatcher/auxiliary.rb b/lib/msf/ui/console/command_dispatcher/auxiliary.rb index f4c66077f283..7eddbf77640c 100644 --- a/lib/msf/ui/console/command_dispatcher/auxiliary.rb +++ b/lib/msf/ui/console/command_dispatcher/auxiliary.rb @@ -69,7 +69,7 @@ def cmd_run(*args, action: nil, opts: {}) begin # Check if this is a scanner module or doesn't target remote hosts - if rhosts.blank? || mod.class.included_modules.include?(Msf::Auxiliary::Scanner) + if rhosts.blank? || mod.class.included_modules.include?(Msf::Auxiliary::MultipleTargetHosts) mod_with_opts.run_simple( 'Action' => args[:action], 'LocalInput' => driver.input, diff --git a/lib/msf/ui/console/command_dispatcher/exploit.rb b/lib/msf/ui/console/command_dispatcher/exploit.rb index 392fccbf86d2..5d41fe7e830a 100644 --- a/lib/msf/ui/console/command_dispatcher/exploit.rb +++ b/lib/msf/ui/console/command_dispatcher/exploit.rb @@ -145,7 +145,7 @@ def cmd_exploit(*args, opts: {}) driver.run_single('reload_lib -a') if args[:reload_libs] - if rhosts && has_rhosts_option + if rhosts && has_rhosts_option && !mod.class.included_modules.include?(Msf::Auxiliary::MultipleTargetHosts) rhosts_walker = Msf::RhostsWalker.new(rhosts, mod_with_opts.datastore) rhosts_walker_count = rhosts_walker.count rhosts_walker = rhosts_walker.to_enum diff --git a/lib/rex/proto/smb/simple_client.rb b/lib/rex/proto/smb/simple_client.rb index 0347fec4fa52..51a3e3dd18f5 100644 --- a/lib/rex/proto/smb/simple_client.rb +++ b/lib/rex/proto/smb/simple_client.rb @@ -267,7 +267,15 @@ def peerport end def peerinfo - "#{peerhost}:#{peerport}" + Rex::Socket.to_authority(peerhost, peerport) + end + + def signing_required + if client.is_a?(Rex::Proto::SMB::Client) + client.peer_require_signing + else + client.signing_required + end end private diff --git a/modules/auxiliary/server/relay/esc8.rb b/modules/auxiliary/server/relay/esc8.rb index efc7dc96a8d5..0971e3574fb1 100644 --- a/modules/auxiliary/server/relay/esc8.rb +++ b/modules/auxiliary/server/relay/esc8.rb @@ -7,7 +7,7 @@ class MetasploitModule < Msf::Auxiliary include ::Msf::Exploit::Remote::SMB::RelayServer include ::Msf::Exploit::Remote::HttpClient - def initialize + def initialize(_info = {}) super({ 'Name' => 'ESC8 Relay: SMB to HTTP(S)', 'Description' => %q{ @@ -40,8 +40,6 @@ def initialize OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]), ] ) - - deregister_options('RHOSTS') end def relay_targets @@ -54,7 +52,7 @@ def relay_targets ) end - def initial_handshake?(target_ip) + def check_host(target_ip) res = send_request_raw( { 'rhost' => target_ip, @@ -67,18 +65,30 @@ def initial_handshake?(target_ip) ) disconnect - res&.code == 401 - end + return Exploit::CheckCode::Unknown if res.nil? + unless res.code == 401 + return Exploit::CheckCode::Safe('The target does not require authentication.') + end + + unless res.headers['WWW-Authenticate'].include?('NTLM') && res.body.present? + return Exploit::CheckCode::Safe('The target does not support NTLM.') + end - def check_options - if datastore['RHOSTS'].present? - print_warning('Warning: RHOSTS datastore value has been set which is not supported by this module. Please verify RELAY_TARGETS is set correctly.') + if datastore['SSL'] + # if the target is over SSL, downgrade to "Detected" because Extended Protection for Authentication may or may not be enabled + Exploit::CheckCode::Detected('Server replied that authentication is required and NTLM is supported.') + else + Exploit::CheckCode::Appears('Server replied that authentication is required and NTLM is supported.') end + end + + def validate + super case datastore['MODE'] when 'SPECIFIC_TEMPLATE' - if datastore['CERT_TEMPLATE'].nil? || datastore['CERT_TEMPLATE'].blank? - fail_with(Failure::BadConfig, 'CERT_TEMPLATE must be set in AUTO and SPECIFIC_TEMPLATE mode') + if datastore['CERT_TEMPLATE'].blank? + raise Msf::OptionValidateError.new({ 'CERT_TEMPLATE' => 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE' }) end when 'ALL', 'AUTO', 'QUERY_ONLY' unless datastore['CERT_TEMPLATE'].nil? || datastore['CERT_TEMPLATE'].blank? @@ -88,11 +98,11 @@ def check_options end def run - check_options @issued_certs = {} relay_targets.each do |target| vprint_status("Checking endpoint on #{target}") - unless initial_handshake?(target.ip) + check_code = check_host(target.ip) + if [Exploit::CheckCode::Unknown, Exploit::CheckCode::Safe].include?(check_code) fail_with(Failure::UnexpectedReply, "Web Enrollment does not appear to be enabled on #{target}") end end diff --git a/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index 87d4744eebeb..cbb333fb0284 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -131,7 +131,7 @@ module is not able to clean up after itself. The service and payload ) deregister_options( - 'RPORT', 'RHOSTS', 'SMBPass', 'SMBUser', 'CommandShellCleanupCommand', 'AutoVerifySession' + 'RPORT', 'SMBPass', 'SMBUser', 'CommandShellCleanupCommand', 'AutoVerifySession' ) if framework.features.enabled?(Msf::FeatureManager::SMB_SESSION_TYPE) add_info('New in Metasploit 6.4 - The %grnCREATE_SMB_SESSION%clr action within this module can open an interactive session') @@ -163,11 +163,7 @@ def validate_service_stub_encoder! end end - def exploit - if datastore['RHOSTS'].present? - print_warning('Warning: RHOSTS datastore value has been set which is not supported by this module. Please verify RELAY_TARGETS is set correctly.') - end - + def validate case action.name when 'PSEXEC' validate_service_stub_encoder! @@ -205,7 +201,7 @@ def run_psexec(relay_connection) framework.threads.spawn(thread_name, false, new_mod_instance) do |mod_instance| mod_instance.exploit_smb_target rescue StandardError => e - print_error("Failed running psexec against target #{datastore['RHOST']} - #{e.class} #{e.message}") + print_error("Failed running psexec against target #{relay_connection.target.ip} - #{e.class} #{e.message}") elog(e) # ensure # # Note: Don't cleanup explicitly, as the shared replicant state leads to payload handlers etc getting closed. @@ -217,12 +213,31 @@ def run_psexec(relay_connection) def relay_targets Msf::Exploit::Remote::SMB::Relay::TargetList.new( :smb, - 445, - datastore['RELAY_TARGETS'], + rport, + datastore['RHOSTS'], randomize_targets: datastore['RANDOMIZE_TARGETS'] ) end + def check_host(target_ip) + generic_message = 'Failed to connect and negotiate an SMB connection.' + begin + simple = connect(false, direct: true) + protocol = simple.client.negotiate + rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError, Errno::ECONNRESET + return Exploit::CheckCode::Unknown(generic_message) + rescue ::Exception => e # rubocop:disable Lint/RescueException + elog(generic_message, error: e) + return Exploit::CheckCode::Unknown(generic_message) + end + + if simple.signing_required + return Exploit::CheckCode::Safe('Signing is required by the target server.') + end + + Exploit::CheckCode::Vulnerable('Signing is not required by the target server.') + end + # Called after a successful connection to a relayed host is opened def exploit_smb_target # automatically select an SMB share unless one is explicitly specified @@ -287,4 +302,7 @@ def session_setup(client) s end + def rport + 445 + end end