ResetNightmare: When a Writable UPN Becomes Domain Admin
A technical Red Team analysis of ResetNightmare, where writable UPN attributes, Kerberos name confusion, and kpasswd can create a domain takeover path.

A User Principal Name looks like a login label. In Active Directory, it participates in a larger identity-resolution system that also includes sAMAccountName, Kerberos name types, ticket canonicalization, the Privileged Attribute Certificate, and a separate service for changing passwords. If those components disagree about which identity a name represents, write access to one attribute can become control over another account.
ResetNightmare, tracked as CVE-2026-27912, demonstrates that failure. Semperis researcher Shai Laron found that an attacker who can write the UPN of a suitable user or computer object can request a specially resolved Ticket Granting Ticket, change the controlled object’s UPN, and present the ticket to the Kerberos password-change service. On an unpatched domain controller, the service can apply the password operation to the privileged account named in the ticket instead of the account represented by its requestor SID.
The result can be a complete domain takeover, but only when the documented prerequisites exist. This is not an unauthenticated Internet exploit, it is not a way for every ordinary domain user to become an administrator, and default MachineAccountQuota alone is explicitly excluded from the researcher’s stated object-creation prerequisite. Microsoft patched ResetNightmare on April 14, 2026. The Red Team value now lies in finding domains where patches are incomplete, exposing the delegated rights that make the path reachable, and verifying whether defenders can reconstruct the identity transition.
The Attack Starts With an Object the Operator Can Change#
An Active Directory object carries many names. Two matter most to this chain. sAMAccountName is the legacy logon name used throughout Windows and is normally unique within a domain. userPrincipalName, or UPN, commonly resembles an email address such as analyst@lab.example, but its value can also be written without a suffix.
The two attributes occupy different namespaces. A domain can contain a privileged user whose sAMAccountName is Tier0Admin and a different controlled object whose UPN is set to Tier0Admin. The strings collide from a human perspective, yet they do not violate the ordinary comparison between two UPN values or between two SAM account names.
That collision alone does not grant privilege. The operator still needs a useful object and a way to alter its UPN. The original ResetNightmare disclosure describes the relevant starting conditions as generic write access over a user or computer object, or the ability to create an applicable user or computer object, excluding the default machine-account quota route. Real environments acquire these paths through delegated help-desk rights, service-account ownership, stale access-control entries, application provisioning, migration tooling, or automation identities with broader write permissions than their job requires.
This is why the first Red Team deliverable should be an attack-path finding rather than an exploit attempt. An ACL that lets a low-trust principal write userPrincipalName is already a security boundary worth reporting. The vulnerability turns that edge into a route toward Tier Zero on vulnerable domain controllers, but removing unnecessary delegation remains useful even after patching.
| Component | Normal purpose | Security significance |
|---|---|---|
sAMAccountName | Legacy domain logon name | Identifies the privileged target in the final authentication request |
userPrincipalName | Alternate user logon name | Can be written on an attacker-controlled object to create a cross-attribute name collision |
NT-ENTERPRISE | Kerberos enterprise principal name type | Directs account lookup through the UPN namespace |
PAC_REQUESTOR_SID | Binds a TGT to the requesting account’s SID | Stops several ordinary ticket-confusion paths during TGS processing |
kadmin/changepw | Kerberos password-change service | Accepts the ticket in a path that historically missed the decisive SID validation |
| TCP/UDP 464 | kpasswd transport | Separates password-change activity from ordinary Kerberos exchanges on port 88 |
Kerberos Can Resolve the Same Visible Name Two Ways#
A Kerberos principal contains both a name string and a name type. Active Directory usually handles a client as NT-PRINCIPAL, resolving the name through sAMAccountName. Kerberos also supports NT-ENTERPRISE, which lets the Key Distribution Center locate the principal through its UPN.
Suppose a low-privilege object has the UPN Tier0Admin, while the real privileged user has the sAMAccountName Tier0Admin. A request using NT-ENTERPRISE and the low-privilege object’s password can resolve through the UPN and produce a TGT whose displayed client name is Tier0Admin. The PAC still carries the SID of the actual low-privilege requestor.
That mismatch is important. A ticket’s readable name is not sufficient proof of identity. Modern domain controllers use the PAC, including PAC_REQUESTOR_SID, to preserve the account that actually requested the TGT. When the ticket enters a normal Ticket Granting Service exchange, the KDC validates the SID and blocks the operator from simply turning the confusing name into a privileged service ticket.
This protection came from Microsoft’s response to the noPac family of naming-confusion attacks. It works for the common TGT-to-TGS path. ResetNightmare exists because password changes can follow a different protocol path.
The Password-Change Path Missed the Boundary#
Microsoft’s Kerberos Change Password and Set Password Protocol listens on port 464. A client proves possession of a suitable ticket, sends an authenticated password request, and receives a result from the password-change service. For a user changing their own password, the service relies on the identity represented by the submitted Kerberos material.
The service ticket name used by this protocol is kadmin/changepw, which belongs to the krbtgt account. The disclosed flow can move from the confusing TGT into an AP-REQ for this service without passing through the normal TGS exchange where PAC_REQUESTOR_SID would prevent the identity substitution.
The operator first creates the cross-attribute collision on the controlled object and obtains the TGT through NT-ENTERPRISE resolution. The operator then changes or clears the controlled object’s UPN. This removes the UPN mapping that originally resolved the request, while the ticket still presents the privileged account’s visible name. On vulnerable domain controllers, the password-change path can resolve that name to the real privileged account and reset its password.
The operator can then authenticate normally as the target through the sAMAccountName namespace. The final ticket belongs to the real target rather than the controlled object. At that point, a Domain Admin target means domain compromise.
sequenceDiagram
participant R as Red Team principal
participant AD as Active Directory
participant KDC as Kerberos KDC
participant KP as kpasswd service
R->>AD: Set controlled object UPN to target SAM name
R->>KDC: Request NT-ENTERPRISE TGT
KDC-->>R: TGT name matches target, PAC SID matches controlled object
R->>AD: Clear or change the controlled UPN
R->>KP: Present ticket for password change
KP->>AD: Resolve the ticket's account name
AD-->>KP: Privileged target account
KP-->>R: Password operation succeeds on vulnerable DC
The diagram is conceptual. It deliberately omits packet fields and tool-specific commands. The essential defect is the boundary between a name chosen through UPN resolution and the account selected by the password-change service after the directory mapping changes.
KerberLoss Was the Road to ResetNightmare#
ResetNightmare emerged from related research into Unicode handling in Active Directory. Laron tested hundreds of invisible Unicode characters and found that LDAP treated them in three broad ways: some were filterable, some behaved like whitespace, and some were ignored during server-side comparison.
Those comparison differences led to KerberLoss, CVE-2026-25177. An operator with WriteSPN over a suitable object could use ignored characters to bypass Service Principal Name uniqueness checks. Depending on the construction, the conflict could redirect explicit SPN resolution, deny access to HOST-mapped services, assist an SPN-jacking path, or create duplicate explicit SPNs that force Kerberos failure and NTLM fallback.
KerberLoss and ResetNightmare should not be treated as the same vulnerability. KerberLoss centers on SPN uniqueness and service authentication. ResetNightmare centers on a UPN-to-SAM collision and the password-change protocol. The shared lesson is that directory uniqueness is not a single operation. Different attributes, LDAP comparisons, name types, and protocol services can apply different rules to identity strings.
Microsoft fixed KerberLoss in March 2026 and ResetNightmare in April 2026. A complete assessment must confirm that every writable domain controller has an applicable cumulative update. One unpatched DC is not equivalent to a fully patched forest simply because the operator’s preferred DC is current.
Build the Attack-Path Inventory Without Exploitation#
The following PowerShell utility performs two read-only checks. It finds existing UPN local parts that collide with another object’s sAMAccountName, then inventories access-control entries that can write the UPN property or grant broader object control. It does not change an object, request a ticket, read a password, or execute the ResetNightmare chain.
Run it from an authorized administrative workstation with the ActiveDirectory module. The identity allowlist is intentionally small and should be replaced with the organization’s approved provisioning principals. Results are evidence candidates, not automatic proof that every trustee can exploit the CVE.
param(
[string]$OutputPath = ".\resetnightmare-audit.json",
[string[]]$ApprovedTrustees = @(
"NT AUTHORITY\SYSTEM",
"BUILTIN\Administrators",
"CREATOR OWNER"
)
)
$ErrorActionPreference = "Stop"
Import-Module ActiveDirectory
$root = Get-ADRootDSE
$domain = Get-ADDomain
$schemaObject = Get-ADObject `
-SearchBase $root.schemaNamingContext `
-LDAPFilter "(lDAPDisplayName=userPrincipalName)" `
-Properties schemaIDGUID
$upnGuid = [Guid]::new([byte[]]$schemaObject.schemaIDGUID)
$emptyGuid = [Guid]::Empty
$users = Get-ADUser `
-LDAPFilter "(objectClass=user)" `
-Properties userPrincipalName, sAMAccountName, objectSid, objectGUID
$computers = Get-ADComputer `
-LDAPFilter "(objectClass=computer)" `
-Properties userPrincipalName, sAMAccountName, objectSid, objectGUID
$objects = @($users) + @($computers)
$samIndex = @{}
foreach ($object in $objects) {
if ([string]::IsNullOrWhiteSpace($object.sAMAccountName)) {
continue
}
$key = $object.sAMAccountName.ToLowerInvariant()
if (-not $samIndex.ContainsKey($key)) {
$samIndex[$key] = [System.Collections.Generic.List[object]]::new()
}
$samIndex[$key].Add($object)
}
$collisions = [System.Collections.Generic.List[object]]::new()
foreach ($object in $objects) {
$upn = [string]$object.userPrincipalName
if ([string]::IsNullOrWhiteSpace($upn)) {
continue
}
$localPart = $upn.Split("@", 2)[0].ToLowerInvariant()
if (-not $samIndex.ContainsKey($localPart)) {
continue
}
foreach ($target in $samIndex[$localPart]) {
if ($target.objectSid.Value -eq $object.objectSid.Value) {
continue
}
$collisions.Add([ordered]@{
sourceDistinguishedName = $object.DistinguishedName
sourceObjectGuid = $object.objectGUID.Guid
sourceSid = $object.objectSid.Value
sourceUpn = $upn
targetDistinguishedName = $target.DistinguishedName
targetObjectGuid = $target.objectGUID.Guid
targetSid = $target.objectSid.Value
targetSamAccountName = $target.sAMAccountName
})
}
}
$dangerousRights = @(
[System.DirectoryServices.ActiveDirectoryRights]::GenericAll,
[System.DirectoryServices.ActiveDirectoryRights]::GenericWrite,
[System.DirectoryServices.ActiveDirectoryRights]::WriteProperty,
[System.DirectoryServices.ActiveDirectoryRights]::WriteDacl,
[System.DirectoryServices.ActiveDirectoryRights]::WriteOwner
)
$delegations = [System.Collections.Generic.List[object]]::new()
foreach ($object in $objects) {
$acl = Get-Acl -Path ("AD:\" + $object.DistinguishedName)
foreach ($ace in $acl.Access) {
if ($ace.AccessControlType -ne "Allow") {
continue
}
$identity = $ace.IdentityReference.Value
if ($ApprovedTrustees -contains $identity) {
continue
}
$matchedRights = @(
$dangerousRights | Where-Object {
($ace.ActiveDirectoryRights -band $_) -eq $_
}
)
if ($matchedRights.Count -eq 0) {
continue
}
$objectScope = $ace.ObjectType
$upnScoped = $objectScope -eq $upnGuid
$allProperties = $objectScope -eq $emptyGuid
$broadControl = $matchedRights -contains [System.DirectoryServices.ActiveDirectoryRights]::GenericAll -or
$matchedRights -contains [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite -or
$matchedRights -contains [System.DirectoryServices.ActiveDirectoryRights]::WriteDacl -or
$matchedRights -contains [System.DirectoryServices.ActiveDirectoryRights]::WriteOwner
if (-not ($upnScoped -or $allProperties -or $broadControl)) {
continue
}
$delegations.Add([ordered]@{
objectDistinguishedName = $object.DistinguishedName
objectGuid = $object.objectGUID.Guid
objectSid = $object.objectSid.Value
trustee = $identity
rights = @($matchedRights | ForEach-Object { $_.ToString() })
inherited = [bool]$ace.IsInherited
objectType = $objectScope.Guid
upnPropertyScoped = $upnScoped
allPropertiesScoped = $allProperties
})
}
}
$result = [ordered]@{
generatedAtUtc = [DateTime]::UtcNow.ToString("o")
domain = $domain.DNSRoot
domainSid = $domain.DomainSID.Value
upnSchemaGuid = $upnGuid.Guid
collisionCount = $collisions.Count
delegationCount = $delegations.Count
collisions = $collisions
delegations = $delegations
}
$result | ConvertTo-Json -Depth 8 | Set-Content -Path $OutputPath -Encoding utf8
$result | ConvertTo-Json -Depth 8
if ($collisions.Count -gt 0) {
exit 2
}
exit 0
Use an explicit output path and preserve its hash when the results support a finding:
.\Test-ResetNightmareExposure.ps1 -OutputPath C:\Evidence\resetnightmare-audit.json
Get-FileHash C:\Evidence\resetnightmare-audit.json -Algorithm SHA256
The ACL portion intentionally reports broad control such as WriteDacl and WriteOwner in addition to direct UPN writes. Those permissions do not write the attribute by themselves, but they can let a principal grant the necessary right. A production attack-path engine should recursively resolve group membership, inherited ACEs, deny entries, ownership, protected objects, and effective permissions before assigning severity.
Existing Collisions Are Higher Priority Than Potential Edges#
The script separates two findings. A delegation is a possible path: an identity might be able to create the dangerous state. A collision means the dangerous naming relationship already exists. That state can be legitimate, accidental, staged by an operator, or left behind by an interrupted attack.
Do not compare only display names. Normalize the UPN local part and SAM value case-insensitively, but retain the original strings, object GUIDs, and SIDs as evidence. Hidden Unicode also requires code-point-aware analysis because visual normalization can merge two different values or conceal the character responsible for an SPN conflict.
Privileged targets should receive additional weight. Resolve membership transitively for Domain Admins, Enterprise Admins, Schema Admins, Administrators, protected users, and custom Tier Zero groups. Also include service accounts that control certificate authorities, identity synchronization, virtualization, backups, endpoint management, or domain-controller administration. Domain takeover is not limited to accounts with a familiar built-in group name.
Event 5136 Shows the Attribute Mutation#
Windows Security event ID 5136 records modification of an Active Directory object when the relevant directory-service auditing and SACL are enabled. For ResetNightmare, the useful record identifies userPrincipalName as the changed LDAP attribute, the actor responsible, the target object’s distinguished name and GUID, the old or new value, and the operation type.
One event is not enough. Legitimate identity-management systems modify UPNs every day. High-confidence detection comes from joining the mutation with directory state and subsequent authentication activity.
The following Microsoft Sentinel query assumes event ID 5136 has already been parsed into EventData. Field availability varies between connectors, so validate the extraction against raw events before deploying it.
let Lookback = 1d;
let ApprovedActors = dynamic([
"CONTOSO\\idm-service",
"CONTOSO\\directory-automation"
]);
SecurityEvent
| where TimeGenerated >= ago(Lookback)
| where EventID == 5136
| extend AttributeName = tostring(EventData.AttributeLDAPDisplayName)
| extend ChangedValue = tostring(EventData.AttributeValue)
| extend Operation = tostring(EventData.OperationType)
| extend ObjectGuid = tostring(EventData.ObjectGUID)
| extend ObjectDn = tostring(EventData.ObjectDN)
| extend Actor = strcat(SubjectDomainName, "\\", SubjectUserName)
| where AttributeName =~ "userPrincipalName"
| where Actor !in~ (ApprovedActors)
| extend UpnLocalPart = tolower(tostring(split(ChangedValue, "@")[0]))
| project TimeGenerated, Actor, ObjectDn, ObjectGuid, Operation, ChangedValue, UpnLocalPart, Computer
| order by TimeGenerated desc
Enrichment should compare UpnLocalPart with a current and historical set of sAMAccountName values. A match against another object’s SAM name is the core signal. Raise severity when the matching object is privileged, the actor is not an approved provisioning service, the source object is newly created or unexpectedly writable, or the UPN is cleared shortly after being assigned.
The full sequence can be short. Detection engineering should preserve sub-minute timestamps and correlate port 464 traffic, directory changes, password-reset events, and subsequent logons for the target identity. Exact event availability depends on domain-controller policy and authentication path, so a lab must establish the organization’s real telemetry rather than copying a universal event list.
| Stage | Evidence to retain | Why it matters |
|---|---|---|
| Object access | Effective ACL and group expansion | Proves how the operator could change the UPN |
| UPN assignment | Event 5136 and directory replication metadata | Shows the cross-attribute collision and originating DC |
| TGT request | Kerberos authentication telemetry | Connects the controlled identity to enterprise-name resolution |
| UPN removal | Second event 5136 | Captures the mapping change required by the disclosed flow |
| Password operation | DC password-management and network telemetry | Places activity on the kpasswd path |
| Privileged authentication | Target SID, source host, logon type, ticket data | Confirms impact rather than assuming it from the attribute change |
A Safe Validation Plan#
Testing ResetNightmare requires explicit authorization and an isolated forest. Do not use a production administrator as the target, and do not assume a rollback will restore every ticket, replication, password-history, or monitoring side effect.
Build a disposable lab with at least one vulnerable DC snapshot, one patched DC snapshot, an ordinary workstation, a synthetic privileged user, and a separate low-privilege principal. Delegate only the exact permission under test. Capture directory events, Kerberos traffic, kpasswd traffic, and DC logs before changing any object.
The first test should be non-exploitative. Run the audit utility and confirm that it finds the delegated UPN edge. Create a harmless cross-attribute collision against a non-privileged lab identity, verify that event 5136 contains the expected data, then remove the value. This validates discovery and detection without resetting another account.
If the engagement requires proof of CVE exposure, define a written stop condition before the controlled password operation. Use a synthetic target, record the DC selected for the request, prevent connectivity to production trusts, and restore the entire lab snapshot afterward. Repeat the same validation against a fully patched DC and document the different outcome. The finding should include the ACL path and missing update, not only a screenshot from an offensive tool.
Remediation Is Patch Plus Permission Repair#
Patch every domain controller with the April 2026 security update or a verified superseding cumulative update. Inventory by build number and update history instead of asking whether the monthly patch process ran. Include writable DCs in child domains, recovery sites, labs with production trust, and systems that were offline during deployment.
Next, remove unnecessary UPN write paths. Provisioning services should write only the objects and attributes they own. Help-desk roles that reset passwords do not automatically need generic write over entire user objects. Migration and synchronization accounts should have documented scope, monitored use, credential rotation, and expiration where possible.
Enable directory-service change auditing and configure SACLs for UPN and SPN changes on relevant objects. Send DC Security logs to remote, access-controlled storage. Test the event pipeline with a benign UPN modification so the team knows that event 5136 arrives with the fields required for correlation.
Finally, search for residual dangerous state. Compare UPN local parts against every SAM name, inspect invisible Unicode in UPNs and SPNs, find conflicting service principal names, review recent changes to dSHeuristics, and investigate unusual password activity involving privileged identities. Patching prevents the disclosed vulnerable behavior, but it does not explain changes that occurred before the patch.
What Red Teams Should Learn From ResetNightmare#
ResetNightmare is valuable beyond one CVE because it exposes a repeatable research method. Identity systems rarely use one canonical name in every protocol. A directory may enforce uniqueness within an attribute while a downstream service compares across attributes. A defense can validate a SID in the ordinary ticket path while an older password, enrollment, recovery, or synchronization path makes a decision from a visible name.
Red Teams should map those transitions. For each identity operation, identify the input name, lookup attribute, canonicalization rules, immutable identifier, authorization check, and alternate protocol routes. Then ask whether the same validation executes during password change, password reset, service-ticket issuance, delegation, device enrollment, certificate mapping, and account recovery.
The most dangerous edge may not look privileged in an ACL review. Writing a profile field, registering a device, changing an alternate identifier, or owning a provisioning object can become authentication authority when another component treats that data as identity proof.
Conclusion#
ResetNightmare did not defeat Kerberos cryptography. It exploited disagreement about identity. A controlled object supplied a UPN that looked like another account’s SAM name. The KDC preserved the real requestor SID, but the password-change path did not apply the decisive validation before resolving the visible name. A writable attribute crossed an authentication boundary and became a password reset for a privileged account.
The correct assessment begins with patches and ACLs, not a domain-takeover command. Find who can write UPNs, detect existing cross-attribute collisions, verify every DC, capture event 5136, and correlate directory mutations with password and authentication activity. Only then should an authorized team decide whether isolated proof is necessary.
For defenders, the lesson is equally direct: names are mutable data. SIDs and equivalent immutable identifiers must remain authoritative across every protocol path that changes credentials or grants access.
References#
Identity Crisis: Novel Vulnerabilities Leading to Kerberos Downgrade, DoS, and Full Domain Takeover
RFC 4120: The Kerberos Network Authentication Service
RFC 3244: Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols
MS-KILE: Kerberos Protocol Extensions