Active Directory Sensor — Reference
File: DiscoveryManager/Sensors/discover_activedirectory/activedirectory_sensor.py Sensor name: Discover_ActiveDirectory Protocol: LDAP / LDAPS (port 389 / 636) · Global Catalog (port 3268 / 3269) Last updated: 2026-05-06
Status legend
| Symbol | Meaning |
|---|---|
X | Completed — implemented, tested on real AD, output verified |
W | Works — implemented and running, limited real-domain testing |
D | Developing — partially coded, not finished |
F | Future — not started, planned for a later phase |
— | Out of scope — not a key/cert, skip by design |
1 — Identity objects
| Scan target | Status | AD attribute / objectClass | What is discovered |
|---|---|---|---|
| User certificates | X | userCertificate on objectClass=user | X.509 DER certs on user accounts — smart card login, email signing, VPN |
| Computer certificates | X | userCertificate on objectClass=computer | Machine certs issued by AD CS for TLS, 802.1X, etc. |
| SSH public keys | X | altSecurityIdentities on user / computer | RSA, ECDSA, Ed25519 public keys stored for LDAP-SSH auth |
| Windows Hello (NGC) keys | X | msDS-KeyCredentialLink on user / computer | WHfB asymmetric key pairs — public key, device ID, TPM-backed flag |
| gMSA (service accounts) | X | via msKds-ProvRootKey (KDS root key) | The master key from which all gMSA passwords are derived |
| Computer machine password | — | NT hash on the DC — not an LDAP attribute | Auto-rotates every 30 days. Stored as NT hash inside NTDS.dit — never exposed over LDAP. |
| Groups / Contacts / inetOrgPerson | — | — | No authentication material |
2 — Directory partitions scanned
| Partition | Status | Base DN | What is found there |
|---|---|---|---|
| Domain partition | X | DC=corp,DC=test,DC=local | User certs, SSH keys, NGC keys, BitLocker, trusts |
| Configuration partition | X | CN=Configuration,DC=... | CA certs, KDS root keys, CRL, cross-cert pairs |
| Schema partition | — | CN=Schema,CN=Configuration,... | Object definitions only — no keys |
| Application partition | — | Domain-specific | DNS zones — not in scope |
3 — Authentication & security
| Scan target | Status | AD attribute | What is discovered |
|---|---|---|---|
| Kerberos TGT signing key | X | krbtgt — pwdLastSet, msDS-KeyVersionNumber | Signs every Kerberos ticket — KVNO, last rotation, RODC vs domain |
| Domain trust inter-realm keys | X | objectClass=trustedDomain in CN=System | RC4 vs AES algorithm, trust direction, trust attributes |
| Smart card / cert auth | X | userCertificate | Covered by phase 1 — user certs |
| Windows Hello for Business | X | msDS-KeyCredentialLink | Covered by phase 4 — NGC keys |
| NTLM password hashes | — | Not exposed via LDAP | Never readable over LDAP — not scannable by design |
| Password policies | — | GPO | Policy rules, not key material |
4 — PKI & cryptography
| Scan target | Status | AD attribute / location | What is discovered |
|---|---|---|---|
| CA certificates (domain NC) | X | cACertificate on CA objects | Root and intermediate CA certs on user/computer objects |
| CA certificates (config NC) | X | cACertificate in CN=Public Key Services | Full PKI hierarchy — root, intermediate, issuing |
| Cross-certificate pairs | X | crossCertificatePair in CN=AIA,... | Inter-CA trust certs between this org's CA and external CAs |
| CRL revocation enrichment | X | certificateRevocationList, deltaRevocationList | Pre-fetched at scan start — isRevoked: true added to any cert whose serial appears in the CRL |
| BitLocker recovery keys | X | msFVE-RecoveryInformation under computer objects | Recovery GUIDs, volume GUIDs, linked computer — existence metadata only |
| DPAPI domain backup keys | X | BCKUPKEY_* in CN=System | Two docs per key pair — private_key + public_key, both keyBytesAccessible: false. Algorithm (RSA-2048) is a spec-constant from MS-BKRP §2.2.5 — algorithmSource: "specification" marks this. Actual RSA blob requires LSAD RPC, not LDAP. |
| KDS root keys | X | msKds-ProvRootKey in config NC | Master keys that derive gMSA passwords — version, create/use-start time, Pending/Active state |
| TPM owner auth | X | msTPM-InformationObject | Owner-auth blob per computer — _scan_tpm_keys phase. Existence + linked computer. |
| Certificate templates | — | CN=Certificate Templates | Configuration blueprints only — no keys. Analysis is CBOM server's job. |
| AD FS token signing certs | — | Moved to discover_wincerts | Removed from this sensor. AD FS writes no key material to LDAP — certs only accessible via Get-AdfsCertificate PowerShell, which requires running on the AD FS server itself. Use discover_wincerts with scan_adfs: true. See Docs/Sensors/WinCerts-Sensor.md. |
| AD CS archived private keys | — | CA JET database | Not in LDAP. Archived keys live in the CA's JET database encrypted under the CA Exchange cert. certutil -getkey is the only extraction path, requires CA admin. Needs a dedicated discover_adcs sensor. Not extractable via wincerts. |
| EFS recovery agent certs | — | SYSVOL GPO XML blob | Real cert material but lives on SYSVOL (SMB) — not LDAP. Needs a separate SMB sensor. |
| Archived private keys (AD CS) | — | CA server JET database | Not in LDAP. Needs a separate discover_adcs sensor on the CA server. |
| OCSP / AIA service certs | — | HTTP endpoints only | Not in LDAP — HTTPS sensor concern. |
5 — Multi-forest / multi-domain topology
| Capability | Status | Notes |
|---|---|---|
| Global Catalog DC discovery | X | Connects to GC (port 3268/3269), queries CN=Partitions,CN=Configuration for all domain NCs in the forest, enumerates DCs per domain NC via LDAP filter serverReference + SERVER_TRUST_ACCOUNT. |
| Child domain scanning | X | Each DC discovered via GC is appended to the scan queue automatically. Full 10-phase scan runs on each DC. |
| Tree domain scanning | X | Tree domains (e.g. mergedpartner.local) have independent LDAP roots — discovered via CN=Partitions, not LDAP SUBTREE from forest root. Requires DNS resolution of tree-domain hostnames (conditional forwarder). |
| Cross-forest trust metadata | W | Trust key objects scanned per domain. Actual cross-forest credential unreachable over LDAP — metadata only. |
| GC Config NC workaround | X | GC port 3268/3269 does NOT serve the Configuration NC. Sensor opens a secondary connection on port 636 to the same server for CN=Partitions queries — transparent to the user. |
| Traversal log document | X | One type: "traversal" document emitted at end of every run. Lists every target — server, resolvedIP, resolvedFQDN, outcome, connect errors, phase errors, per-phase cert counts. |
6 — Additional AD services
| Scan target | Status | Notes |
|---|---|---|
| AD FS token-signing / decrypting certs | — | Moved to discover_wincerts. Sensor must run on the AD FS server or a host with RSAT-ADFS. See Docs/Sensors/WinCerts-Sensor.md. |
| AD RMS | — | Rare in modern orgs — separate concern |
| AD LDS (standalone LDAP) | — | Separate LDAP sensor |
| Azure AD Connect / Entra ID | — | Cloud-side only — on-prem sensor boundary ends here |
What the sensor emits
CBOM type | keyType | Source | Notes |
|---|---|---|---|
certificate | — | Domain certs, CA certs, cross-certs | subject, issuer, serialNumber (colon-hex), validFrom, validTo, keyUsage bitmask, SignAlgo, publicKey (b64), publicKeySha256, certSha256 |
key | public_key | Companion to every certificate | publicKeySha256, algorithm, size, state. Join to cert via publicKeySha256. |
key | public_key | SSH keys | SSH wire-format — RSA / ECDSA / Ed25519 |
key | public_key | NGC / WHfB | TPM-backed WHfB key — device ID, usage, creation time |
key | secret_key | BitLocker, KDS, krbtgt, trust keys | Existence + metadata — keyBytesAccessible: false where applicable |
key | private_key | DPAPI backup keys | RSA-2048 existence — algorithm from spec (MS-BKRP §2.2.5) |
key | public_key | DPAPI backup keys | Sibling of the DPAPI private_key doc |
status | — | End of each target scan | Outcome (Ok / Partial / Failed), phaseErrors map, per-phase cert counts |
traversal | — | End of entire run | All targets — server, resolvedIP, resolvedFQDN, outcome, certCounts, scan summary |
Scan phases — execution order inside discover_all()
discover_all()
│
├── GC expansion _discover_dcs_via_gc() GC → CN=Partitions → all domain NCs → all DCs
│ Discovered DCs appended to scan queue
│
│ ── per target ───────────────────────────────────────────────────────
│
├── CRL pre-fetch _load_revoked_serials() Base CRL + Delta CRL → revoked serial set
│
├── Query 1 _scan_domain_certs() userCertificate (domain NC)
├── Query 2 _scan_config_certs() cACertificate (config NC)
├── Query 3 _scan_ssh_keys() altSecurityIdentities
├── Query 4 _scan_ngc_keys() msDS-KeyCredentialLink (WHfB / NGC)
├── Query 5 _scan_bitlocker() msFVE-RecoveryInformation
├── Query 6 _scan_kds_keys() msKds-ProvRootKey (config NC)
├── Query 7 _scan_trust_keys() trustedDomain (CN=System)
├── Query 8 _scan_dpapi_keys() BCKUPKEY_* Secret (CN=System)
├── Query 9 _scan_krbtgt_keys() krbtgt account(s)
├── Query 10 _scan_cross_certs() crossCertificatePair (config NC)
│
│ Queries 6–10 are skipped on GC targets (DC-only phases).
│ Skipped phase names logged in status doc gcSkippedPhases.
│
├── Status doc _status_doc() one per target — counters for all 10 phases
│
└── Traversal doc (end of discover_all) one per full run — all targets + outcomes
All LDAP queries use paged search (page size 500). Deduplication by SHA-256 of DER bytes (certs) or deterministic UUID5 seed (keys). PEM normalisation: userCertificate bytes starting with -----BEGIN are decoded to DER by _to_der().
sourceProperties — fields on every document
| Field | Type | Description |
|---|---|---|
host | string | IP / hostname of the DC the sensor connected to |
distinguishedName | string | Full LDAP path of the source object |
objectClasses | list | Inheritance chain — most specific class last |
adPartition | string | domain or configuration |
adAttribute | string | LDAP attribute that contained the material |
adEntity | string | PascalCase label identifying what was scanned |
adEntity values
| Phase | adEntity |
|---|---|
_scan_domain_certs | DomainCertificate |
_scan_config_certs | CACertificate |
_scan_cross_certs | CrossCertificate |
_scan_ssh_keys | SSHPublicKey |
_scan_ngc_keys | WindowsHelloNGCKey |
_scan_bitlocker | BitLockerKey |
_scan_kds_keys | KDSRootKey |
_scan_trust_keys | DomainTrustKey |
_scan_dpapi_keys | DPAPIBackupKey |
_scan_krbtgt_keys | KerberosKrbtgtKey |
Traversal log — fields per target entry
| Field | Type | Description |
|---|---|---|
server | string | Server as configured or discovered |
resolvedIP | string \ | null |
resolvedFQDN | string \ | null |
port | int | Port used |
forest | string | Forest root DN |
domain | string | Domain this DC belongs to |
isGC | bool | Whether this target was a Global Catalog |
discoverySource | string | configured / gc_itself / gc_discovered |
outcome | string | Ok / Partial / Failed |
connectError | string \ | null |
phaseErrors | dict | phase_name → error string |
certCounts | dict | Per-phase counts + total |
Configuration reference
The sensor uses a forests: block. Each forest entry defines credentials shared by all domains inside it; any field can be overridden at the domain or gc_servers level. Full annotated example: DiscoveryManager/Sensors/discover_activedirectory/ConfigExample.yaml.
Minimal — one forest, one DC
Name: ActiveDirectoryDiscovery
ExtName: Discover_ActiveDirectory
Config:
scan_name: "corp-scan"
page_size: 500
forests:
- name: "corp.test.local"
auth_type: simple # simple | ntlm | kerberos
username: "CORP\\Administrator"
password: "<secret>"
use_ssl: true
verify_ssl: false
domains:
- server: "dc01.corp.test.local"
base_dn: "DC=corp,DC=test,DC=local"
Multi-forest — GC discovery + explicit DCs
Name: ActiveDirectoryDiscovery
ExtName: Discover_ActiveDirectory
Config:
scan_name: "enterprise-scan"
page_size: 500
connect_timeout_seconds: 10
search_timeout_seconds: 30
retry_attempts: 3
forests:
- name: "corp.test.local"
auth_type: ntlm
domain: "CORP" # required for ntlm
username: "Administrator"
password: "<secret>"
use_ssl: true
verify_ssl: false
domains:
- server: "dc01.corp.test.local"
base_dn: "DC=corp,DC=test,DC=local"
- name: "partner.local"
auth_type: ntlm
domain: "PARTNER"
username: "Administrator"
password: "<secret>"
use_ssl: true
verify_ssl: false
gc_servers:
- server: "gc01.partner.local" # GC auto-discovers all child/tree DCs
domains:
- server: "gc01.partner.local"
base_dn: "DC=partner,DC=local"
- server: "dc01.child.partner.local"
base_dn: "DC=child,DC=partner,DC=local"
- server: "dc01.nestedchild.child.partner.local"
base_dn: "DC=nestedchild,DC=child,DC=partner,DC=local"
- server: "dc01.mergedpartner.local"
base_dn: "DC=mergedpartner,DC=local"
Skip a known-unreachable domain
domains:
- server: "dc01.isolated.corp.example.com"
base_dn: "DC=isolated,DC=corp,DC=example,DC=com"
skip: true
Per-domain credential override
domains:
- server: "dc01.us.corp.example.com"
base_dn: "DC=us,DC=corp,DC=example,DC=com"
username: "US\\Administrator" # overrides forest-level credential
password: "<secret>"
Known limitations
| Limitation | Detail |
|---|---|
| DNS for tree domains | Tree domains (e.g. mergedpartner.local) require a conditional DNS forwarder on the parent-forest DC pointing at the tree DC's IP. Without it, hostname resolution fails with LDAPSocketOpenError. |
verify_ssl: true | DC cert must be issued by a CA trusted by the scanning machine. Self-signed DC certs will fail. Use verify_ssl: false for internal AD. |
| NTLM auth — limited testing | Implemented but not tested end-to-end. simple auth is the verified path. |
| DPAPI key bytes | currentValue (actual RSA blob) requires LSAD RPC — not over LDAP 636. Metadata only; keyBytesAccessible: false. |
krbtgt key bytes | Never exposed via LDAP. Only pwdLastSet and KVNO emitted. |
| GC requires port 636 reachable | Secondary connection to port 636 is opened for CN=Partitions queries. Both 3269 and 636 must be open on the GC server. |
Permanent out-of-scope
| Item | Reason |
|---|---|
| Certificate templates | Blueprints only — no keys. Classification is CBOM server's job. |
| AD CS archived private keys | Stored in the CA's JET database encrypted under the CA Exchange cert. certutil -getkey is the only extraction path — requires CA admin rights. Not accessible over LDAP and not extractable via wincerts (wincerts reads Windows cert store, not CA database). Needs a dedicated discover_adcs sensor. |
| EFS recovery agent cert | SYSVOL / SMB — not LDAP. Needs SMB sensor. |
| OCSP responder certs | HTTP endpoints — HTTPS sensor. |
| NTLM hashes | Never exposed via LDAP. |
| Computer machine password | NT hash in NTDS.dit — never via LDAP. |
| Cert purpose classification | EKU OIDs emitted in sourceProperties — S/MIME / VPN / code-signing classification is CBOM server's job. |
Python dependencies
ldap3 >= 2.9
cryptography >= 41.0
jsonschema >= 4.0
Remaining work
| Priority | Item | Notes |
|---|---|---|
| 🔴 High | Duplicate discoverID bug | Same object scanned twice can produce different UUIDs — downstream deduplication breaks. |
| 🔴 High | NTLM auth end-to-end test | Implemented but untested against a real DC. simple auth is the verified path. |
| 🔴 High | Production run with CBOM server | All local testing passes. Need a real deposit → dedup → CryptoMaterial pipeline run against the CBOM server. |
| 🟡 Medium | Schema validation on all emitted docs | Only _ad_cert_to_doc validates against Schema.json currently. Key docs (secret_key, private_key, SSH, NGC) emit without schema check. |
| 🟡 Medium | _adfs.py unit tests in wincerts | discover_wincerts/_adfs.py has no test coverage — PowerShell invocation + cert extraction path is untested. |
| 🟢 Low | verify_ssl: true CA trust setup | Document how to add the DC's issuing CA cert to the scanning machine's trust store. |
| 🟢 Low | Proxy support | Not implemented. Large enterprises may block direct LDAP 636. |
| 🟢 Low | Configuration NC deeper scan | Additional objects under CN=Services,CN=Configuration not yet inventoried. |
Completed
| Item | Done |
|---|---|
| Split sensor into phase files | ✅ _phase_1_domain_certs.py … _phase_10_cross_certs.py, _helpers.py, _connection.py, _discovery.py |
| Lab seeding scripts | ✅ Seed-1-Minimal.ps1 (12-step, idempotent, multi-domain); Seed-2-Bulk.ps1 (10k scale) |
| Unit tests | ✅ 408 tests across 14 files in tests/discover_ad/ |
| Config format refactor | ✅ New forests: block with per-domain override, GC discovery, skip flag |
| CRL revocation enrichment | ✅ Pre-fetched at scan start; isRevoked: true added to matching certs |
| TPM phase | ✅ _scan_tpm_keys — msTPM-InformationObject linked to computers |