Skip to content

feat(chase): add CVE-2026-54121 (Certighost) cdc-chase attack - #370

Open
canoztas wants to merge 2 commits into
ly4k:mainfrom
canoztas:feat/cve-2026-54121-certighost
Open

canoztas wants to merge 2 commits into
ly4k:mainfrom
canoztas:feat/cve-2026-54121-certighost

Conversation

@canoztas

Copy link
Copy Markdown

Summary

This PR adds support for CVE-2026-54121 ("Certighost"), an AD CS
elevation-of-privilege vulnerability (CVSS 8.8, patched July 2026) that lets a
low-privileged domain user obtain a certificate impersonating a Domain
Controller and recover the DC's NT hash.

The vulnerable code path is the AD CS enrollment "cdc chase" fallback: the CA
trusts two requester-supplied enrollment attributes — cdc (the host the CA
connects to for directory data) and rmd (the DC principal to look up there) —
without verifying that the cdc host is a legitimate DC. By pointing cdc at an
attacker-controlled oracle that returns a target DC's identity, the CA issues a
certificate bound to the DC.

Credit for the vulnerability research and the original PoC goes to
@h0j3n and
@aniqfakhrul.

What's added

New certipy chase command — orchestrates the full attack, reusing existing
Certipy primitives:

  1. Discovers the CA and the target DC over LDAP.
  2. Creates a machine account (via the machine-account quota), reusing Account.
  3. Starts a rogue DC-identity oracle (rogue LDAP + rogue SMB/LSA with NetLogon
    pass-through) that authenticates the CA callback as the machine account but
    answers directory lookups with the target DC's identity.
  4. Submits a certificate request with cdc/rmd set, reusing Request.
  5. PKINITs with the issued DC certificate to recover a TGT and NT hash, reusing
    Authenticate.
sudo certipy chase -u lowpriv@corp.local -p 'Passw0rd!' -dc-ip 10.0.0.10

Modes: -server-only (just run the oracle and print the matching certipy req
command), -no-pkinit (stop after the certificate), -computer-name/-computer-pass/-computer-hash
(reuse an account), -target-account (choose the DC), -listener, -template
(default Machine), -web/-dcom transports.

certipy req gains generic request attributes — -cdc, -rmd, and a
repeatable -request-attribute key:value, appended verbatim to the enrollment
attribute blob, so the request half of the attack can also be driven manually
against a separately-run oracle.

Files

  • certipy/lib/certificate.py — create_csr_attributes() gains a trailing,
    backward-compatible request_attributes param.
  • certipy/lib/req.py — Request accepts cdc / rmd / request_attribute.
  • certipy/commands/parsers/req.py — new -cdc / -rmd / -request-attribute flags.
  • certipy/lib/rogue.py — new: the rogue DC-identity oracle (RogueServer).
  • certipy/commands/chase.py + certipy/commands/parsers/chase.py — new: the command.
  • certipy/commands/parsers/__init__.py — registers chase.

Testing

Requires a lab AD CS deployment with the chase fallback enabled
(EDITF_ENABLECHASECLIENTDC) on an unpatched CA, a template built from AD /
DNS name flags (e.g. the default Machine template), and a domain account with
machine-account-quota > 0. Run from a Linux host (root, for ports 389/445):

sudo certipy chase -u lowpriv@corp.local -p 'Passw0rd!' -dc-ip <DC_IP>

Lint/type gates (flake8 ./certipy, isort --check, black --check, pyright)
pass.

Notes

  • Patched CAs (July 2026+) reject this via _ValidateChaseTargetIsDC. Mitigation
    on unpatched CAs: certutil -setreg policy\EditFlags -EDITF_ENABLECHASECLIENTDC.
  • lib/rogue.py is a near-verbatim port of the disclosed PoC's protocol logic,
    restructured into a typed, logged library class; no new third-party deps.

References

canoztas and others added 2 commits July 29, 2026 11:11
Exploit the AD CS certificate-enrollment "cdc chase" fallback
(CVE-2026-54121) so a low-privileged domain user can obtain a
certificate impersonating a Domain Controller and recover its NT hash.

The CA trusts two requester-supplied enrollment attributes -- cdc (the
host the CA connects to for directory data) and rmd (the DC principal to
look up there) -- without verifying the cdc host is a real DC.

- lib/certificate.py: create_csr_attributes() gains a backward-compatible
  request_attributes parameter
- lib/req.py: Request accepts cdc/rmd/request_attribute and threads them
  into the enrollment attribute list (RPC/DCOM/Web uniformly)
- commands/parsers/req.py: -cdc / -rmd / -request-attribute flags
- lib/rogue.py: rogue DC-identity oracle (RogueServer) -- rogue LDAP +
  SMB/LSA with NetLogon pass-through
- commands/chase.py + commands/parsers/chase.py: the chase command,
  reusing Account, Request and Authenticate
- commands/parsers/__init__.py: register chase

Credit: @H0j3n and @aniqfakhrul (vulnerability research and PoC).
…bugs

Follow-up to the CVE-2026-54121 (Certighost) chase feature: align it with
Certipy's conventions and fix defects in the rogue DC-identity oracle.

lib/rogue.py:
- conn.send -> conn.sendall to avoid silently truncated sealed responses
- tear down the NetLogon secure channel per bind (NLOracle.disconnect in a
  finally) instead of leaking a DCE connection to the DC on every callback
- split the blocking run_lsa into build_smb_lsa so RogueServer.shutdown can
  actually stop the SMB/LSA listener; add clean bind-failure handling
- raise load-bearing failures (NetLogon rejection, bind errors) from debug to
  logging.warning + handle_error, matching the project error convention

style/conventions:
- module docstring form, parenthesized multi-line help, lowercase metavars,
  the standard add_subparser docstring paragraph and a fuller class docstring
- server-only wait via threading.Event instead of a busy sleep loop

Lint/type gates (flake8 ./certipy, isort, black, pyright) pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant