Active Directory duplicate SPN, WriteSPN abuse, Kerberos downgrade — three problems that most AD teams track on three separate tickets collapsed into a single one on 10 March 2026, when Microsoft shipped the fix for CVE-2026-25177. Semperis researcher Shai Laron, who reported it, named it KerberLoss. The short version: a low-privileged principal holding the right to write one attribute on one object could break Kerberos for any HOST-mapped service in the forest — and, with a second turn of the same trick, silently push any service's clients onto NTLM.
What makes this a detection problem rather than a patching problem is the bypass technique. The duplicate SPN that breaks the service is not a byte-for-byte duplicate. It only collides at the Kerberos layer. Every tool your runbook reaches for — setspn -X, the forest-wide uniqueness index, the Directory Services event that fires when a duplicate is blocked — compares strings for equality, and equality is exactly what the attacker sidesteps.
⚠️ Warning: This article covers a vulnerability Microsoft patched on 10 March 2026. At publication, MSRC's assessment was Publicly Disclosed: No, Exploited: No, Exploitation Less Likely. Nothing here should be read as a claim of in-the-wild exploitation.
What Is a Duplicate SPN in Active Directory
A Service Principal Name is the string a Kerberos client uses to ask the KDC for a ticket to a specific service instance — cifs/fileserver.corp.local, http/intranet, MSSQLSvc/db01:1433. It lives in the multi-valued servicePrincipalName attribute on the user or computer account the service runs as. When a client wants to talk to a service, it builds the SPN, asks the KDC for a service ticket, and the KDC encrypts that ticket with the long-term key of the account holding the SPN — the same key whose compromise enables Silver Ticket forgery.
That design only works if the mapping from SPN to account is a function. Microsoft is explicit about it: "Unless a service uses the computer account and the HOST SPN, SPNs must be unique in the AD DS forest. In a multi-forest environment, the SPN must be unique across all the associated forests" and "One SPN can be associated with only one account" (Kerberos Generates KDC_ERR_S_PRINCIPAL_UNKNOWN or KDC_ERR_PRINCIPAL_NOT_UNIQUE Error).
When two accounts claim the same SPN, the KDC has no basis for picking a key. Microsoft's Kerberos event reference documents a dedicated failure code for that condition, 0x8 KDC_ERR_PRINCIPAL_NOT_UNIQUE, described as "Multiple principal entries in KDC database… This error occurs if duplicate principal names exist. Unique principal names are crucial for ensuring mutual authentication."
There is a second layer that matters here. Computer accounts get a HOST SPN automatically, and a documented list of service classes falls back to it. Microsoft's setspn reference lists them — cifs, http, www, wins, spooler, rpcss, dns, netlogon and about forty more — and states the precedence rule plainly: "These SPNs are recognized for computer accounts if the computer has a host SPN. Unless they're explicitly placed on objects, a host SPN can substitute for any of the mentioned SPNs." The equivalence itself is expressed in the sPNMappings attribute (CN=SPN-Mappings, attribute ID 1.2.840.113556.1.4.1347) on the NTDS Service object, which Microsoft describes as holding "a list of service principal names (SPN) to show the equivalence of SPN types."
Semperis distilled the consequence into one sentence: "The SPN lookup algorithm always looks for an explicit SPN first. The algorithm looks for the SPN's mapped alias only if an explicit SPN is not found."
An explicit SPN, anywhere in the forest, outranks a HOST mapping. That is the lever.
Active Directory Duplicate SPN, WriteSPN Abuse, Kerberos Downgrade: How KerberLoss Works
Since Windows Server 2012 R2, domain controllers refuse to create a duplicate. Microsoft's SPN and UPN uniqueness reference documents the workflow: the DC queries the forest-wide servicePrincipalName index — on a Global Catalog, or locally if the DC is itself a GC — and "If entries returned != 0 -> write fails." The caller gets extended error 8647 / 0x21C7 ERROR_DS_SPN_VALUE_NOT_UNIQUE_IN_FOREST (UPNs get 8648 / 0x21C8), and the DC writes event ID 2974, source ActiveDirectory_DomainService, to the Directory Services log — an event that names the blocked value and lists up to ten objects already holding it.
The same reference notes that this check also runs when an SPN is regenerated rather than written directly. Changing dNSHostName, sAMAccountName, msDS-AdditionalDnsHostName, msDS-AdditionalSamAccountName, serverReferenceBL or userAccountControl causes AD to delete the old SPNs and construct new ones, and "If any of the new SPN value is a duplicate, we fail the modification."
Semperis dates the current three-check set — UPN uniqueness, SPN uniqueness and SPN alias uniqueness — to Microsoft's 2021 patch for CVE-2021-42282. The alias check is the one KerberLoss actually defeats.
It is a solid control. It compares strings.
Semperis' research went at the comparison itself. Testing 385 invisible Unicode characters, Laron found that only 106 were filterable through standard LDAP queries; the rest are either treated as whitespace or, in Semperis' words, "completely ignored by the DC." Drop one of those into an SPN string and the uniqueness index no longer sees a match — "by using an invisible and unfilterable character and inserting it into the string, NotAdmin can successfully add the conflicting SPN."
Microsoft's own advisory FAQ for CVE-2026-25177 describes the same chain and its outcome:
ℹ️ Note: "An attacker could exploit this issue by adding specially crafted Unicode characters to duplicate Service Principal Names (SPNs) or User Principal Names (UPNs). These characters bypass normal Active Directory checks designed to prevent duplicates… When clients request Kerberos authentication for that service, the domain controller may issue a ticket encrypted with the wrong key, causing the service to reject the ticket. This can lead to a denial of service or force the service to fall back to NTLM authentication if it is enabled. No access to the targeted server is required beyond the initial SPN-write permission."
MSRC rated it CVSS 3.1 base 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H, temporal 7.7), severity Important, classified CWE-641 (Improper Restriction of Names for Files and Other Resources), with the title Active Directory Domain Services Elevation of Privilege Vulnerability. Neither MSRC nor Semperis documents what the March code change actually does internally — treat the fix as opaque and verify it by DC build number, not by behaviour.
The permission the whole thing rests on is unremarkable. On computer objects it is the validated write Microsoft calls "Validated write to service principal name" (CN=Validated-SPN, Rights-GUID f3a64788-5306-11d1-a9c5-0000f80367c1), delegable in ADUC and explicitly mentioned in the setspn docs: "You can also delegate the permission by assigning the Validated write to service principal name permission to the desired user or group." On any object, GenericAll, GenericWrite, or WriteProperty on servicePrincipalName does the same job. This is the same class of attribute-write foothold that drives Shadow Credentials via msDS-KeyCredentialLink — and, per Semperis, WriteSPN "on any computer or user account in the forest" was enough.
The Attack Chain
Step 1 — Obtain a writable object
The attacker needs write access to servicePrincipalName on one account. That can be a delegated helpdesk right, a stale service-desk group, an over-broad OU delegation — or a computer object the attacker created themselves, since ms-DS-MachineAccountQuota lets any domain user join ten computers by default and the creator gets write rights over what they created.
Step 2 — Identify a HOST-mapped target
The target is any service that relies on the automatic HOST SPN rather than an explicit one — file shares (cifs), IIS (http/www), print (spooler), and the rest of the documented list. These are precisely the services that have no explicit SPN to defend their claim.
List a target's registered SPNs — if cifs/SERVERB is absent, the service is riding the HOST mapping and can be pre-empted:
setspn -L SERVERB
Step 3 — Plant the colliding SPN
The attacker writes cifs/SERVERB onto their own controlled object, with an invisible code point embedded so the forest-wide index sees a different string. The uniqueness check passes. No 8647 error is returned. No event 2974 is written, because from the DC's perspective nothing was blocked.
Step 4 — The service breaks, loudly
Clients now request cifs/SERVERB, and the KDC finds an explicit SPN that outranks SERVERB's HOST mapping. It issues a ticket — sealed with the attacker's account key. SERVERB cannot decrypt it and rejects it with 0x29 KRB_AP_ERR_MODIFIED, documented by Microsoft as "The authentication data was encrypted with the wrong key for the intended server."
Note what did not happen. From the KDC's point of view the request succeeded: a ticket was asked for, a ticket was issued. There is no Kerberos failure to fall back from, and therefore no NTLM fallback — just a service that has stopped working. Semperis reports users seeing "The specified network name is no longer available.", "The target account name is incorrect." or "Cannot find path 'path' because it does not exist.", and access being restored the moment the malicious SPN is removed.
Step 5 — The same write, a quieter outcome: NTLM
The downgrade is a different use of the same permission. Instead of pre-empting a HOST mapping, the attacker duplicates an SPN that already exists explicitly. Now the KDC finds two accounts holding it, cannot choose a key, and — in Semperis' testing — returns KDC_ERR_S_PRINCIPAL_UNKNOWN. That is a genuine Kerberos failure, which is precisely what a Windows client needs in order to fall back.
This is the variant to worry about. Per Semperis it works against any service in the forest, HOST-mapped or not, and "From the user's perspective, normal access is seemingly maintained." Nobody opens a ticket. Meanwhile everything built on the assumption that Kerberos is the authentication path — every relay defence skipped because "we're Kerberos-only" — is now load-bearing on NTLM relay controls instead. Where NTLM has been disabled, the same duplicate produces an outage rather than a downgrade.
Step 6 — Or: steer someone else's delegation
Semperis demonstrates a third use, SPN-jacking, adapted from Elad Shamir's original write-up. An attacker who has compromised an account configured for constrained delegation, and holds WriteSPN over an account they control, plants the intermediate service's SPN on that account and runs the full S4U flow — collecting a ticket sealed with their own key. Shamir's version also needed WriteSPN on the account holding the target SPN, to strip it first; KerberLoss removes that requirement. If your environment still runs the delegation patterns covered in Kerberos delegation attacks, that is the path to review first.
Detection
Start from an uncomfortable fact: the events that normally announce a duplicate SPN are the ones this technique is built to avoid.
| Indicator | Event ID | Log / source | What it means here |
|---|---|---|---|
servicePrincipalName written on any object | 5136 | Security — Microsoft-Windows-Security-Auditing | The write itself. This is the primary signal. |
| Duplicate SPN/UPN write blocked | 2974 | Directory Services — ActiveDirectory_DomainService | Fires only when the check catches it. Silence is expected during a KerberLoss-style write. |
TGS failure 0x7 KDC_ERR_S_PRINCIPAL_UNKNOWN | 4769 | Security (DC) | What Semperis observed for a duplicated explicit SPN — the NTLM-downgrade variant. Not audited unless KdcExtraLogLevel bit 0x1 is set. |
TGS failure 0x8 KDC_ERR_PRINCIPAL_NOT_UNIQUE | 4769 | Security (DC) | Microsoft's documented code for duplicate principals. Alert on it, but do not wait for it — Semperis' lab returned 0x7. |
0x29 KRB_AP_ERR_MODIFIED | 4769 / app logs | Member server, service logs | Ticket sealed with the wrong key — signature of the HOST-mapping variant, where the KDC itself reports success. |
| Kerberos→NTLM shift for a named service | 4624 (Logon Process / Auth Package) | Member server Security log | The downgrade landing. |
Event 5136 is where this is caught. Microsoft documents it as generating "every time an Active Directory object is modified", and it carries exactly the fields you need: ObjectDN, ObjectClass, AttributeLDAPDisplayName, AttributeValue, OperationType (%%14674 Value Added, %%14675 Value Deleted) and the SubjectUserSid/SubjectUserName of the writer.
Two prerequisites, both easy to get wrong. The Audit Directory Service Changes subcategory must be enabled, and the object needs a matching SACL: "To generate this event, the modified object must have an appropriate entry in SACL: the 'Write' action auditing for specific attributes." Without the SACL, the subcategory alone produces nothing.
Every SPN write, with the identity of the principal that made it:
$xpath = "*[System[EventID=5136]] and *[EventData[Data[@Name='AttributeLDAPDisplayName']='servicePrincipalName']]"
Get-WinEvent -LogName Security -FilterXPath $xpath -MaxEvents 500 |
ForEach-Object {
$x = [xml]$_.ToXml()
[pscustomobject]@{
Time = $_.TimeCreated
Actor = ($x.Event.EventData.Data | Where-Object Name -eq 'SubjectUserName').'#text'
Target = ($x.Event.EventData.Data | Where-Object Name -eq 'ObjectDN').'#text'
Operation = ($x.Event.EventData.Data | Where-Object Name -eq 'OperationType').'#text'
Value = ($x.Event.EventData.Data | Where-Object Name -eq 'AttributeValue').'#text'
}
}
-- Microsoft Sentinel: SPN writes by non-Tier-0 principals.
-- 5136's attribute fields are NOT mapped to SecurityEvent columns
-- (check the table reference) — parse them out of the raw EventData XML.
SecurityEvent
| where EventID == 5136
| extend Attribute = extract(@'Name="AttributeLDAPDisplayName">([^<]*)<', 1, EventData),
SpnValue = extract(@'Name="AttributeValue">([^<]*)<', 1, EventData),
ObjectDN = extract(@'Name="ObjectDN">([^<]*)<', 1, EventData),
Operation = extract(@'Name="OperationType">%%(\d+)<', 1, EventData)
| where Attribute =~ "servicePrincipalName"
| where Operation == "14674" // Value Added
| where SubjectUserName !in ("svc_scom$", "svc_sccm$")
| project TimeGenerated, Computer, SubjectUserName, ObjectDN, SpnValue
| order by TimeGenerated desc
Hunt for the invisible character directly
This is the check that does not depend on catching the write. Any SPN containing a code point outside printable ASCII is anomalous — legitimate SPNs are hostnames, service classes, ports and instance names.
Get-ADObject -LDAPFilter '(servicePrincipalName=*)' -Properties servicePrincipalName -ResultPageSize 500 |
ForEach-Object {
$dn = $_.DistinguishedName
foreach ($spn in $_.servicePrincipalName) {
if ($spn -notmatch '^[\x20-\x7E]+$') {
[pscustomobject]@{
DistinguishedName = $dn
SPN = $spn
CodePoints = ($spn.ToCharArray() | ForEach-Object { '{0:X4}' -f [int]$_ }) -join ' '
}
}
}
}
💡 Tip: Run this before setspn -X, not after. setspn -X finds values that are equal; the whole point of the bypass is that the two values are not equal. A clean setspn -X proves nothing about this technique.
Run the byte-level duplicate search anyway — it catches the ordinary misconfiguration and the unsophisticated attacker:
setspn -X -F -P
setspn -T * -T contoso -X
Turn on the TGS telemetry you probably do not have
Microsoft's event 4769 reference states that some failures "are only reported when you set KdcExtraLogLevel registry key value", with 0x01: Audit SPN unknown errors. The KDC registry reference documents the default value as 2 — meaning bit 0x1 is off, and KDC_ERR_S_PRINCIPAL_UNKNOWN failures are not written to the Security log on a default DC. The exact failure a duplicate SPN produces is, by default, invisible.
On every DC, keep the default PKINIT logging (0x2) and add SPN-unknown auditing (0x1) — that is 3:
$k = 'HKLM:\SYSTEM\CurrentControlSet\Services\Kdc'
New-Item -Path $k -Force | Out-Null
Set-ItemProperty -Path $k -Name 'KdcExtraLogLevel' -Value 3 -Type DWord
Get-ItemProperty -Path $k -Name 'KdcExtraLogLevel'
Expect volume, and baseline for a week before alerting. Microsoft warns that 0x20 failures (expired tickets) are routine noise; SPN-unknown failures are rarer but not zero in a healthy forest.
Remediation
💡 Quick Win: Patch every domain controller with the 10 March 2026 update, then set KdcExtraLogLevel to 3 on all of them. The first closes the bypass; the second means you would actually see it if something like it comes back.
1. Patch the domain controllers. The fix ships in the March 2026 security update — the monthly rollup on Server 2012 and 2012 R2. MSRC lists these per server SKU:
| Windows Server | KB |
|---|---|
| 2012 | KB5078775 |
| 2012 R2 | KB5078774 |
| 2016 | KB5078938 |
| 2019 | KB5078752 |
| 2022 | KB5078766 / KB5078737 |
| 2022 23H2 | KB5078734 |
| 2025 | KB5078740 / KB5078736 |
Where MSRC lists two packages for a SKU — Server 2022 and Server 2025 — the second is the hotpatch package (KB5078737 and KB5078736), which applies only to hotpatch-enabled installations. Confirm which applies to your exact build before deploying. The vulnerability is fixed on the DC, not on member servers — a fully patched fleet with one unpatched DC still processes writes through the unpatched path.
2. Find out who can write SPNs. This is the permission the whole attack needs, and in most forests nobody has ever enumerated it. The query below reports non-administrative principals holding Validated-SPN, direct write on the servicePrincipalName attribute, or generic write over any computer object.
$validatedSpn = [guid]'f3a64788-5306-11d1-a9c5-0000f80367c1'
$schemaNC = (Get-ADRootDSE).schemaNamingContext
$spnAttrGuid = [guid](Get-ADObject -SearchBase $schemaNC `
-LDAPFilter '(lDAPDisplayName=servicePrincipalName)' `
-Properties schemaIDGUID).schemaIDGUID
Get-ADComputer -Filter * -ResultPageSize 500 | ForEach-Object {
$dn = $_.DistinguishedName
(Get-Acl "AD:$dn").Access |
Where-Object {
$_.AccessControlType -eq 'Allow' -and
($_.ObjectType -eq $validatedSpn -or $_.ObjectType -eq $spnAttrGuid -or
$_.ActiveDirectoryRights -match 'GenericAll|GenericWrite')
} |
Where-Object { $_.IdentityReference -notmatch 'SYSTEM|Domain Admins|Enterprise Admins|Administrators' } |
ForEach-Object {
[pscustomobject]@{ Object = $dn; Principal = $_.IdentityReference; Rights = $_.ActiveDirectoryRights }
}
}
Remove what is not justified. Pay particular attention to delegations granted to broad groups (Authenticated Users, Domain Users, legacy helpdesk groups) and to principals that hold rights over Tier 0 computer objects.
3. Reduce the pool of attacker-controlled objects. Set ms-DS-MachineAccountQuota to 0 and grant machine-join through a delegated group instead. It does not fix CVE-2026-25177, but it removes the easiest way for an ordinary user to obtain an object they fully control.
4. Make the downgrade land nowhere. Kerberos failing is a resilience problem; Kerberos failing into NTLM is a security problem. Require SMB signing, enforce LDAP signing and channel binding, and start restricting NTLM where you can — the audit path is covered in NTLM relay attacks, and the encryption-downgrade sibling of this problem in Kerberos RC4 fallback.
5. Instrument, then verify. Enable Audit Directory Service Changes, add a SACL auditing writes to servicePrincipalName on your computer and service-account OUs, set KdcExtraLogLevel to 3, and schedule the non-ASCII SPN sweep above as a recurring job. Then confirm the pipeline works end to end by writing a benign SPN to a test object and checking that the 5136 reaches your SIEM — an audit policy that produces no events looks identical to a forest with no attacks.
6. Review the neighbouring flaw. The same Semperis research disclosed CVE-2026-27912 (ResetNightmare), a userPrincipalName and Kerberos Change Password issue patched on 14 April 2026. If you are only now applying March, you are also only now applying April. Both are documented in Semperis' write-up.
How EtcSec Detects This
EtcSec's Active Directory audit maps directly onto the preconditions this attack needs, so you can answer "are we exposed" without writing the queries above yourself.
WRITESPN_ABUSE enumerates the principals holding write access to servicePrincipalName — the single permission CVE-2026-25177 turns into a forest-wide denial of service. DUPLICATE_SPN and COMPUTER_DUPLICATE_SPN surface SPN values claimed by more than one object, the byte-identical duplicates that break Kerberos whether an attacker put them there or a migration did. COMPUTER_WITH_SPNS inventories which computer objects carry explicit SPNs, which is how you tell a service that defends its own name from one that depends on the HOST mapping and can therefore be pre-empted. DELEGATION_UNKNOWN_TARGET flags constrained-delegation configurations pointing at SPNs that do not resolve cleanly — the condition SPN-jacking needs.
Together those four checks reconstruct the attack surface: who can write, what is already colliding, which services are HOST-dependent, and where delegation is pointing somewhere it should not. For the wider review this fits into, see how to audit Active Directory security, and for the classic SPN exposure problem, Kerberoasting detection and prevention.
ℹ️ Note: EtcSec automatically checks for this vulnerability during every AD/Azure audit. Run a free audit to verify your environment.
Sources
- Microsoft Security Response Center — CVE-2026-25177, Active Directory Domain Services Elevation of Privilege Vulnerability (CVSS 3.1 8.8, CWE-641, released 10 March 2026)
- Semperis — Identity Crisis: Novel Vulnerabilities Leading to Kerberos Downgrade, DoS, and Full Domain Takeover, Shai Laron, 5 August 2026
- Microsoft Learn — SPN and UPN uniqueness (event 2974, errors 8647/8648)
- Microsoft Learn — Kerberos Generates KDC_ERR_S_PRINCIPAL_UNKNOWN or KDC_ERR_PRINCIPAL_NOT_UNIQUE Error
- Microsoft Learn — setspn (HOST-mapped service classes,
-X,-F, Validated-SPN delegation) - Microsoft Learn — Validated-SPN validated write (Rights-GUID
f3a64788-5306-11d1-a9c5-0000f80367c1) - Microsoft Learn — SPN-Mappings attribute
- Microsoft Learn — 5136(S) A directory service object was modified
- Microsoft Learn — 4769(S, F) A Kerberos service ticket was requested (failure code table)
- Microsoft Learn — Registry entries about Kerberos protocol and KDC (
KdcExtraLogLevel, default 2) - Microsoft Learn — Azure Monitor Logs reference: SecurityEvent (column list behind the Sentinel query)
- Semperis — SPN-jacking: An Edge Case in WriteSPN Abuse, Elad Shamir
Explore the identity security pages that support this topic
