From 179c9928641cd0adea266c0f0e96794fc0352c43 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 8 Nov 2024 13:46:38 -0500 Subject: [PATCH 1/8] Use the existing #validate method for options --- modules/auxiliary/server/relay/esc8.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/server/relay/esc8.rb b/modules/auxiliary/server/relay/esc8.rb index efc7dc96a8d5..f4d8a9ebcdcb 100644 --- a/modules/auxiliary/server/relay/esc8.rb +++ b/modules/auxiliary/server/relay/esc8.rb @@ -70,15 +70,13 @@ def initial_handshake?(target_ip) res&.code == 401 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.') - 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,7 +86,6 @@ def check_options end def run - check_options @issued_certs = {} relay_targets.each do |target| vprint_status("Checking endpoint on #{target}") From ec01052732cd27a8ca74416649466210bfc626eb Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 8 Nov 2024 13:48:09 -0500 Subject: [PATCH 2/8] Add a new mixin for handling multiple targets --- .../core/auxiliary/multiple_target_hosts.rb | 29 +++++++++++++++++++ lib/msf/core/auxiliary/scanner.rb | 16 ++-------- .../console/command_dispatcher/auxiliary.rb | 2 +- 3 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 lib/msf/core/auxiliary/multiple_target_hosts.rb 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/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, From 01ff9d08b3bd0915c0c4be3779d95c9fd77e9f65 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 8 Nov 2024 17:18:22 -0500 Subject: [PATCH 3/8] Switch relay modules, add ESC8 check method --- lib/msf/core/exploit/remote/smb/relay_server.rb | 3 ++- lib/msf/ui/console/command_dispatcher/exploit.rb | 2 +- modules/auxiliary/server/relay/esc8.rb | 15 +++++++++------ modules/exploits/windows/smb/smb_relay.rb | 10 +++------- 4 files changed, 15 insertions(+), 15 deletions(-) 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/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/modules/auxiliary/server/relay/esc8.rb b/modules/auxiliary/server/relay/esc8.rb index f4d8a9ebcdcb..68489450502f 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,7 +65,11 @@ def initial_handshake?(target_ip) ) disconnect - res&.code == 401 + return Exploit::CheckCode::Unknown if res.nil? + return Exploit::CheckCode::Safe unless res.code == 401 + return Exploit::CheckCode::Safe unless res.headers['WWW-Authenticate'].include?('NTLM') && res.body.present? + + Exploit::CheckCode::Detected('Server replied that authentication is required and NTLM is supported.') end def validate @@ -89,7 +91,8 @@ def run @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..3cc1733b4d73 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! @@ -218,7 +214,7 @@ def relay_targets Msf::Exploit::Remote::SMB::Relay::TargetList.new( :smb, 445, - datastore['RELAY_TARGETS'], + datastore['RHOSTS'], randomize_targets: datastore['RANDOMIZE_TARGETS'] ) end From 47380320919692d0f320edecb5ce9f2e55e2366b Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 11 Nov 2024 11:14:09 -0500 Subject: [PATCH 4/8] Actually count the hosts RangeWalker handles many more formats for specifying multiple hosts, so simply checking for a space is insufficient. --- lib/msf/core/exploit/remote/tcp.rb | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) 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 # From 0d2a9e116ec3ff6fd904246bcf7d6dd865633568 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 11 Nov 2024 13:23:44 -0500 Subject: [PATCH 5/8] Add #signing_required to SMB::SimpleClient --- lib/rex/proto/smb/simple_client.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 From 85f62a8769cc71ca6d6ad44ef1770abad794c312 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 12 Nov 2024 08:52:26 -0500 Subject: [PATCH 6/8] Update target_host information --- lib/msf/core/session.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 From 2e4bc2c6d5b1a9207345f4217b9101687271fa35 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 12 Nov 2024 09:14:51 -0500 Subject: [PATCH 7/8] Add a check method to the smb_relay module --- .../Attacking-AD-CS-ESC-Vulnerabilities.md | 2 +- modules/exploits/windows/smb/smb_relay.rb | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) 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/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index 3cc1733b4d73..cbb333fb0284 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -201,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. @@ -213,12 +213,31 @@ def run_psexec(relay_connection) def relay_targets Msf::Exploit::Remote::SMB::Relay::TargetList.new( :smb, - 445, + 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 @@ -283,4 +302,7 @@ def session_setup(client) s end + def rport + 445 + end end From 6097a6839a6e5799b3aad12494112e8066220efa Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 12 Nov 2024 11:58:57 -0500 Subject: [PATCH 8/8] Finish up the ESC8 check after more research --- modules/auxiliary/server/relay/esc8.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/server/relay/esc8.rb b/modules/auxiliary/server/relay/esc8.rb index 68489450502f..0971e3574fb1 100644 --- a/modules/auxiliary/server/relay/esc8.rb +++ b/modules/auxiliary/server/relay/esc8.rb @@ -66,10 +66,20 @@ def check_host(target_ip) disconnect return Exploit::CheckCode::Unknown if res.nil? - return Exploit::CheckCode::Safe unless res.code == 401 - return Exploit::CheckCode::Safe unless res.headers['WWW-Authenticate'].include?('NTLM') && res.body.present? + 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 - Exploit::CheckCode::Detected('Server replied that authentication is required and NTLM is supported.') + 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