Query Details

HUNT 06 AD ADCS Vulnerable Template Audit 30d

Query

// =========================================================
// HUNT-06 | AD-ADCS-Vulnerable-Template-Audit-30d
// Description : All certificate issuances (4886/4887) over
//               30 days grouped by template, with SAN
//               presence, requester vs. subject mismatch
//               detection, enrollment agent use flags,
//               and bulk enrollment rate indicators.
//               Maps to ESC1 (SAN mismatch), ESC3
//               (enrollment agent), ESC8 (DC cert relay).
// Period      : 30 days
// Use Case    : ADCS attack surface review, PKI audit
// Tables      : SecurityEvent
// =========================================================

let Period = 30d;

// Event 4887 — Certificate Issued with detailed fields
SecurityEvent
| where TimeGenerated > ago(Period)
| where EventID in (4886, 4887)
| extend
    EventDesc         = case(EventID == 4886, "Certificate Requested",
                             EventID == 4887, "Certificate Issued", "Other"),
    RequesterUPN      = SubjectUserName,
    RequesterDomain   = SubjectDomainName,
    // Parse template from EventData
    TemplateName      = extract(@"(?i)<Data Name=.CertificateTemplate.>(.*?)</Data>", 1, EventData),
    RequestAttr       = extract(@"(?i)<Data Name=.RequestAttributes.>(.*?)</Data>", 1, EventData),
    SubjectField      = extract(@"(?i)<Data Name=.Subject.>(.*?)</Data>", 1, EventData)
| extend
    // SAN present in request attributes (locally computed from RequestAttr)
    SANValue          = extract(@"(?i)san=([^;,\n]+)", 1, RequestAttr),
    EnrollmentAgent   = extract(@"(?i)enrollmentagent=([^;,\n]+)", 1, RequestAttr),
    RequesterNorm     = tolower(RequesterUPN),
    IsDCTemplate      = TemplateName has_any ("DomainController", "KerberosAuthentication",
                                               "DirectoryEmailReplication")
| extend
    HasSAN            = isnotempty(SANValue),
    HasEnrollAgent    = isnotempty(EnrollmentAgent),
    SANNorm           = tolower(SANValue),
    SANMismatch       = isnotempty(SANValue) and not(tolower(SANValue) contains tolower(RequesterUPN))
| extend
    SensitiveSAN      = SANNorm has_any ("administrator", "domain admins", "enterprise admins",
                                         "krbtgt", "dc$", "dc01", "dc02")
| summarize
    TotalIssuances      = count(),
    SANMismatches       = countif(SANMismatch),
    SensitiveSANIssuances = countif(SensitiveSAN),
    EnrollAgentUses     = countif(HasEnrollAgent),
    DCTemplateCerts     = countif(IsDCTemplate),
    UniqueRequesters    = dcount(RequesterUPN),
    Requesters          = make_set(RequesterUPN, 20),
    SANValues           = make_set(SANValue, 20),
    IssuanceDays        = dcount(startofday(TimeGenerated)),
    LastIssuance        = max(TimeGenerated),
    FirstIssuance       = min(TimeGenerated)
    by TemplateName
| extend
    DailyRateAvg        = round(todouble(TotalIssuances) / todouble(IssuanceDays), 1),
    RiskScore           = (SANMismatches * 30)
                        + (SensitiveSANIssuances * 50)
                        + (EnrollAgentUses * 20)
                        + (DCTemplateCerts * 15),
    ESCFlags = strcat(
        iff(SANMismatches > 0,         "ESC1_SAN_Mismatch; ", ""),
        iff(EnrollAgentUses > 0,       "ESC3_EnrollAgent; ", ""),
        iff(DCTemplateCerts > 0,       "ESC8_DCTemplate; ", ""),
        iff(SensitiveSANIssuances > 0, "Critical_SensitiveSAN; ", "")
    ),
    RiskLevel = case(
        SensitiveSANIssuances > 0, "Critical",
        SANMismatches > 0,         "High",
        EnrollAgentUses > 0,       "High",
        DCTemplateCerts > 5,       "Medium",
        "Low"
    )
| project
    TemplateName,
    RiskLevel,
    RiskScore,
    ESCFlags,
    TotalIssuances,
    DailyRateAvg,
    SANMismatches,
    SensitiveSANIssuances,
    EnrollAgentUses,
    DCTemplateCerts,
    UniqueRequesters,
    FirstIssuance,
    LastIssuance,
    SANValues,
    Requesters
| order by RiskScore desc

Explanation

This KQL query is designed to audit and analyze certificate issuances in an Active Directory Certificate Services (ADCS) environment over the past 30 days. Here's a simple breakdown of what the query does:

  1. Data Source: It looks at security events related to certificate requests and issuances (Event IDs 4886 and 4887) from the SecurityEvent table.

  2. Time Frame: The analysis covers a period of the last 30 days.

  3. Data Extraction: For each event, it extracts and processes several pieces of information:

    • The type of event (certificate requested or issued).
    • The template used for the certificate.
    • The requester's user principal name (UPN) and domain.
    • Subject Alternative Name (SAN) details.
    • Whether an enrollment agent was used.
    • Whether the certificate template is for a domain controller.
  4. Analysis:

    • It checks for SAN presence and mismatches between the SAN and the requester.
    • It identifies sensitive SAN values that could indicate potential security risks.
    • It counts the use of enrollment agents and domain controller templates.
  5. Summarization: The query summarizes the data by certificate template, calculating:

    • Total number of issuances.
    • Number of SAN mismatches and sensitive SAN issuances.
    • Use of enrollment agents and domain controller templates.
    • Unique requesters and their details.
    • Issuance frequency and time range.
  6. Risk Assessment:

    • It calculates a risk score based on the presence of SAN mismatches, sensitive SANs, enrollment agent usage, and domain controller templates.
    • It assigns a risk level (Critical, High, Medium, Low) based on specific criteria.
  7. Output: The results are projected to show key details like template name, risk level, risk score, and other relevant metrics, sorted by risk score in descending order to prioritize higher-risk templates.

This query is useful for identifying potential vulnerabilities and risks in the certificate issuance process, helping to enhance the security posture of the ADCS environment.

Details

David Alonso profile picture

David Alonso

Released: March 24, 2026

Tables

SecurityEvent

Keywords

SecurityEventCertificateTemplateRequesterDomainSANEnrollmentAgentRiskScoreRiskLevelESCFlags

Operators

letagoinextendcaseextracthas_anyisnotemptytolowercontainssummarizecountcountifdcountmake_setstartofdaymaxminbyroundtodoublestrcatiffprojectorder by

Actions