Skip to content

feat: gate ADCS ESC6/ESC9/ESC13 validation on KDC binding and harden vuln roles - #400

Merged
mwendigg merged 1 commit into
dreadnode:mainfrom
l50:upstream/adcs-kdc-binding-validation
Aug 3, 2026
Merged

feat: gate ADCS ESC6/ESC9/ESC13 validation on KDC binding and harden vuln roles#400
mwendigg merged 1 commit into
dreadnode:mainfrom
l50:upstream/adcs-kdc-binding-validation

Conversation

@l50

@l50 l50 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Key Changes:

  • Introduced KDC StrongCertificateBindingEnforcement gating so ESC6 and ESC9 validation checks the DC of the certificate's own domain, correctly accounting for the KB5014754 (Feb 2025) default shift to Full Enforcement
  • Added an ESC13 group-link verification step that distinguishes a genuinely exploitable issuance-policy link from a template that merely looks configured
  • Made the ESC10 case 1, ESC13, and ESC7 Ansible roles idempotent and self-verifying to prevent silent partial-provision states
  • Added comprehensive unit tests and documentation explaining the KDC binding hard gate across ADCS attack paths

Added:

  • KDC binding resolution and ESC6 gating - Added kdcBinding, readKDCBinding, and checkESC6KDCBinding in cli/internal/validate/checks.go so an EDITF_ATTRIBUTESUBJECTALTNAME2 flag only passes when the validating DC's binding value is exactly 0, since a present security extension is rejected in both Compatibility (1) and Full Enforcement (2)
  • ESC9 enforcement checks - Added checkESC9Enforcement and checkESC9KDCBinding that first confirm the template carries CT_FLAG_NO_SECURITY_EXTENSION, then require a binding value below 2, correctly treating Compatibility mode as exploitable for ESC9 (unlike ESC6)
  • ESC13 group-link verification - Added checkESC13GroupLink and parseESC13GroupLink to confirm the issuance-policy OID carries an msDS-OIDToGroupLink and that exactly one OID object exists, with field-splitting parsing that avoids COUNT=10 matching COUNT=1
  • KDC restart to ESC10 case 1 role - Added a conditional win_service restart of the KDC so the new binding mode actually takes effect, since the running KDC only reads the value at startup (ansible/roles/vulns_adcs_esc10_case1)
  • Directory link verification to esc13.ps1 - Added a post-commit read-back of msDS-OIDToGroupLink that throws if the link did not land, since a silent no-op otherwise looks identical to success
  • Test coverage - Added esc6_kdc_test.go, esc9_kdc_test.go, and esc13_link_test.go covering binding thresholds, cross-forest DC resolution, absent-value default behavior, transport errors, and probe parsing edge cases
  • Documentation - Added a "KDC binding is a hard gate" section to docs/GOAD-vulnerabilities-comprehensive.md with a value/mode table, cross-referenced from the ESC6 and ESC9 entries, and updated docs/domains-and-users.md to note the weak KDC binding on DC03

Changed:

  • ESC13 provisioning script - Reworked esc13.ps1 to reuse an existing issuance-policy OID and converge duplicate OID objects left by prior lab resets, rather than unconditionally creating a new OID and linking only one of them (ansible/roles/vulns_adcs_esc13/files/esc13.ps1)
  • ESC7 PSPKI install - Guarded the Install-Module PSPKI step to skip and report unchanged when the module is already present, so an egress hiccup on a re-run no longer becomes fatal (ansible/roles/vulns_adcs_esc7/tasks/main.yml)
  • Validator test defaults - Removed a redundant nil-check in TestNewValidator_Defaults, since NewValidator always returns a non-nil value (cli/internal/validate/validator_test.go)

@dreadnode-renovate-bot dreadnode-renovate-bot Bot added area/roles Changes made to Ansible roles area/docs Changes made to documentation labels Aug 3, 2026
**Added:**

- KDC binding verification for ESC6 and ESC9 - Added `checkESC6KDCBinding`, `checkESC9KDCBinding`, `checkESC9Enforcement`, and `readKDCBinding` in `cli/internal/validate/checks.go` to read `StrongCertificateBindingEnforcement` on the DC of the certificate's issuing domain, since the CA-side flag alone does not prove a weakly-mapped certificate can authenticate (ESC6 passes only at 0, ESC9 at 0 or 1, an absent value means Full Enforcement per KB5014754)
- ESC13 group-link verification - Added `checkESC13GroupLink` and `parseESC13GroupLink` to confirm the issuance policy OID carries an `msDS-OIDToGroupLink` and that exactly one OID object exists, distinguishing a truly configured template from one that only looks configured
- KDC restart after binding change - Added a conditional `win_service` restart of the KDC in `vulns_adcs_esc10_case1/tasks/main.yml` (and its README) so the new binding mode actually takes effect, since the KDC only reads the value at service start
- Test coverage - Added `esc6_kdc_test.go`, `esc9_kdc_test.go`, and `esc13_link_test.go` covering enforcement thresholds, cross-forest DC resolution, absent-value handling, and duplicate-OID parsing
- Documentation - Added a "KDC binding is a hard gate" section to `docs/GOAD-vulnerabilities-comprehensive.md` with the enforcement-mode table and event-39 caveat, plus updated ESC6/ESC9 prerequisites and DC03's entry in `docs/domains-and-users.md`

**Changed:**

- ESC13 provisioning idempotency - Reworked `vulns_adcs_esc13/files/esc13.ps1` to reuse an existing issuance policy OID, converge duplicates left by prior lab resets, and verify the group link landed rather than silently no-op
- ESC7 PSPKI install guard - Guarded the `Install-Module PSPKI` step in `vulns_adcs_esc7/tasks/main.yml` to skip the PSGallery call when the module is already present, avoiding a fatal on egress hiccups
- Validator error handling - Switched most checks in `checks.go` from `runPSErr` to `runPS`, folding transport-error WARNs into the normal result paths and inlining `checkSingleACL` back into `checkACLPermissions`
- Renamed the `found` result flag to `any` in several checks and changed the IIS "no upload directory" verdict from FAIL to INFO since IIS is optional in some labs
- Simplified `TestNewValidator_Defaults` by dropping the impossible nil-return guard

**Removed:**

- Dropped the unused `labmap` import from `checks.go` and removed the redundant OID re-query block from `esc13.ps1`
@l50
l50 force-pushed the upstream/adcs-kdc-binding-validation branch from ebc1d97 to fbff7ab Compare August 3, 2026 20:19
@l50 l50 changed the title fix: gate ESC6/ESC9 validation on KDC certificate binding enforcement feat: gate ADCS ESC6/ESC9/ESC13 validation on KDC binding and harden vuln roles Aug 3, 2026
@mwendigg
mwendigg added this pull request to the merge queue Aug 3, 2026
Merged via the queue into dreadnode:main with commit d066243 Aug 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Changes made to documentation area/roles Changes made to Ansible roles

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants