ADscan version
ADscan launcher: v11.2.0, adscan/adscan-lite:latest
Host OS
Arch linux-lts
Docker version
Docker version 29.7.2, build a7dcaa6fdb
Command and sanitized output
Expected behavior
Summary
adscan reports attack paths of the form Authenticated Users@WELLKNOWN → ADCSESC1 → Domain Admins → ... → DCSync (Direct, T0/Domain Breaker) that are not exploitable. Tracing the edge back through the inventory data and the detector source shows that the ESC1 (and ESC2) edges are emitted against certificate templates that are not enabled and - in the worst case - not published on any CA. Enrollment against such templates is rejected server-side, so the entire attack path is a false positive.
Environment
- adscan 11.2.0 (adscan-lite container)
- Single-forest AD lab, one enterprise CA (
IssuingCA), 51 templates, 15 enabled
- Cross-checked with
certipy find -enabled -json (v5.1.0)
Evidence
1. The reported edge targets a disabled, unpublished template
The flagged template in inventory/adcs_attack_steps.json (sanitized):
{
"relation": "ADCSESC1",
"source_object_id": "S-1-5-11",
"source_name": "Authenticated Users@WELLKNOWN",
"target_kind": "CertTemplate",
"target_name": "LEGACYAPP@CORP.LOCAL"
}
The same template in inventory/adcs_templates.json:
"enabled": null,
"properties": {
"mspki_certificate_name_flag": 1,
"mspki_enrollment_flag": 8,
"mspki_ra_signature": 0,
"pki_extended_key_usage": ["1.3.6.1.5.5.7.3.2"]
}
Note "enabled": null - the collector does not record publish/enabled state at all.
2. Ground truth from Certipy
Template Name : LegacyApp
Enabled : False
Certificate Authorities : [] <-- published on NO CA
Enrollee Supplies Subject : True
Client Authentication : True
Requires Manager Approval : False
Enrollment Rights : ... Domain Admins, Authenticated Users
Enrollment attempts against it fail (template not published on the enterprise CA), so no certificate can ever be minted → no PKINIT → no DA.
3. Code tracing
Collector (adscan_internal/services/collector/adcs_collector.py / adcs_templates.json output): template objects are created without evaluating the template↔CA publication relationship; enabled is left null.
Detector (adscan_internal/services/collector/adcs_detectors/esc1.py, detect_esc1()):
def detect_esc1(*, template_node, template_acl_edges, domain) -> list[CollectorEdge]:
if template_node.kind != "CertTemplate":
return []
name_flag = template_int_property(template_node, "mspki_certificate_name_flag", ...)
if not (name_flag & ENROLLEE_SUPPLIES_SUBJECT):
return []
enrollment_flag = template_int_property(template_node, "mspki_enrollment_flag", ...)
if enrollment_flag & PEND_ALL_REQUESTS:
return []
...
return [make_adcs_edge(principal_sid=sid, template_node=template_node, esc="1")
for sid in get_enroll_principal_sids(template_acl_edges)]
There is no check for:
enabled / CA publication state of the template, or
- whether the CA referenced by the template exists/is reachable.
The same applies to esc2.py (identical shape, also flagged the disabled template for Authenticated Users).
Consequence in the attack graph: Authenticated Users@WELLKNOWN (SID S-1-5-11) gains an ADCSESC1 transition against a T0 template, the path engine then expands it to → Domain Admins → Administrators → DCSync and surfaces it as a ready-to-execute T0/Domain-Breaker path with "Execution Readiness: 1 ready".
Expected behavior
ESC detectors (esc1.py … at minimum the enrollment-based family: ESC1/ESC2/ESC4/ESC5) should be gated on template publication state:
- Skip templates with no entry in the CA↔template publish relationship (
Certificate Authorities: None), or
- emit the edge but mark it as
disabled / not_published so the attack-path engine (and the "Execute this attack path now?" prompt) filters it out.
At minimum the collector should persist the enabled/published flag instead of null.
Impact
False-positive T0 paths directly erode trust in the "Ready" attack-path ranking and the interactive execution prompt - the highest-severity finding in this particular engagement was unexploitable on manual verification, while the genuinely real ADCS issue (ESC8 web enrollment over HTTP) ranked lower.
Workaround (pentester-side)
Cross-check every ADCSESC* finding against certipy find -enabled -json before executing, and discard edges whose target template shows Enabled: false or an empty Certificate Authorities list.
Lab or environment context
No response
ADscan version
ADscan launcher: v11.2.0, adscan/adscan-lite:latest
Host OS
Arch linux-lts
Docker version
Docker version 29.7.2, build a7dcaa6fdb
Command and sanitized output
Expected behavior
Summary
adscanreports attack paths of the formAuthenticated Users@WELLKNOWN → ADCSESC1 → Domain Admins → ... → DCSync(Direct, T0/Domain Breaker) that are not exploitable. Tracing the edge back through the inventory data and the detector source shows that the ESC1 (and ESC2) edges are emitted against certificate templates that are not enabled and - in the worst case - not published on any CA. Enrollment against such templates is rejected server-side, so the entire attack path is a false positive.Environment
IssuingCA), 51 templates, 15 enabledcertipy find -enabled -json(v5.1.0)Evidence
1. The reported edge targets a disabled, unpublished template
The flagged template in
inventory/adcs_attack_steps.json(sanitized):{ "relation": "ADCSESC1", "source_object_id": "S-1-5-11", "source_name": "Authenticated Users@WELLKNOWN", "target_kind": "CertTemplate", "target_name": "LEGACYAPP@CORP.LOCAL" }The same template in
inventory/adcs_templates.json:Note
"enabled": null- the collector does not record publish/enabled state at all.2. Ground truth from Certipy
Enrollment attempts against it fail (template not published on the enterprise CA), so no certificate can ever be minted → no PKINIT → no DA.
3. Code tracing
Collector (
adscan_internal/services/collector/adcs_collector.py/adcs_templates.jsonoutput): template objects are created without evaluating the template↔CA publication relationship;enabledis leftnull.Detector (
adscan_internal/services/collector/adcs_detectors/esc1.py,detect_esc1()):There is no check for:
enabled/ CA publication state of the template, orThe same applies to
esc2.py(identical shape, also flagged the disabled template forAuthenticated Users).Consequence in the attack graph:
Authenticated Users@WELLKNOWN(SIDS-1-5-11) gains anADCSESC1transition against a T0 template, the path engine then expands it to→ Domain Admins → Administrators → DCSyncand surfaces it as a ready-to-execute T0/Domain-Breaker path with "Execution Readiness: 1 ready".Expected behavior
ESC detectors (
esc1.py… at minimum the enrollment-based family: ESC1/ESC2/ESC4/ESC5) should be gated on template publication state:Certificate Authorities: None), ordisabled/not_publishedso the attack-path engine (and the "Execute this attack path now?" prompt) filters it out.At minimum the collector should persist the enabled/published flag instead of
null.Impact
False-positive T0 paths directly erode trust in the "Ready" attack-path ranking and the interactive execution prompt - the highest-severity finding in this particular engagement was unexploitable on manual verification, while the genuinely real ADCS issue (ESC8 web enrollment over HTTP) ranked lower.
Workaround (pentester-side)
Cross-check every
ADCSESC*finding againstcertipy find -enabled -jsonbefore executing, and discard edges whose target template showsEnabled: falseor an emptyCertificate Authoritieslist.Lab or environment context
No response