13 KiB
AD directory object auditing
ad-object-sacl is a dedicated opt-in command for built-in AD DS SACL auditing. It requires Windows PowerShell 5.1 or PowerShell 7 on Windows, an explicit DC FQDN, and credentials permitted to read the complete security descriptors and update SACLs on the selected objects. Use an account with the necessary directory security privilege; the command does not grant privileges or modify authorization. It does not use the local file/registry configure-sacl targets.
# Audit/plan read directory state without changing it. Export exact DNs and ACEs.
./WELA.ps1 ad-object-sacl -AdServer dc01.example.test -AdSaclAction Plan `
-AdSaclProfile MdiDomain -ResultsPath ad-plan.json
# Configure is explicit. DryRun performs the same reads, without AD writes or backups.
./WELA.ps1 ad-object-sacl -AdServer dc01.example.test -AdSaclAction Configure `
-AdSaclProfile MdiDomain -DryRun -ResultsPath ad-preview.json
./WELA.ps1 ad-object-sacl -AdServer dc01.example.test -AdSaclAction Configure `
-AdSaclProfile MdiDomain -Auto -BackupPath .\new-ad-backup -ResultsPath ad-result.json
# Current/former Exchange configuration is a separate forest-wide choice.
./WELA.ps1 ad-object-sacl -AdServer dc01.example.test -AdSaclAction Plan `
-AdSaclProfile MdiConfiguration -ResultsPath exchange-plan.json
# PKI objects are explicitly selected, not every object in the Configuration partition.
./WELA.ps1 ad-object-sacl -AdServer dc01.example.test -AdSaclAction Plan `
-AdSaclProfile PkiObjects `
-AdObjectDn 'CN=LabTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=test' `
-ResultsPath pki-plan.json
Multiple profiles or PKI DNs can be passed as PowerShell arrays. -Profile and -Baseline select Security audit policy and are rejected by this command. The AD-specific parameters are rejected on other commands. There is no implicit domain discovery: aliases, LDAP URLs, ports, and a RootDSE host different from -AdServer are refused. All requests use one connection to that DC with Negotiate authentication, LDAP signing/sealing, and referral chasing disabled. AD LDS and unknown DC write capability are refused. RODCs can be read; required changes are reported Blocked.
Exact profiles
All ACEs use Everyone (S-1-1-0). An empty object GUID is 00000000-0000-0000-0000-000000000000; it does not restrict the ACE to one property. Plans export the target DN, class, SID, decimal rights mask, named rights, audit outcomes, ACE flags, object GUID, inherited class GUID, and inheritance. Schema GUIDs are checked against the same DC before planning writes.
MdiDomain targets RootDSE defaultNamingContext. Each ACE audits Success, applies to descendants only of the listed class, and has ACE flags 74 (0x4A: Success, ContainerInherit, InheritOnly), empty object GUID and the following inherited class GUID. The precise masks follow Microsoft's readiness script at commit 730dad6, rather than approximating the UI's “Full control minus read” instructions.
| Descendant class | Mask | Inherited class GUID |
|---|---|---|
| user | 852331 (0xD016B) |
bf967aba-0de6-11d0-a285-00aa003049e2 |
| group | 852331 (0xD016B) |
bf967a9c-0de6-11d0-a285-00aa003049e2 |
| computer | 852331 (0xD016B) |
bf967a86-0de6-11d0-a285-00aa003049e2 |
| msDS-ManagedServiceAccount | 852331 (0xD016B) |
ce206244-5827-4a86-ba1c-1c0c386c1b64 |
| msDS-GroupManagedServiceAccount | 852075 (0xD006B) |
7b8b558a-93a5-4af7-adca-c017e67f1057 |
| msDS-DelegatedManagedServiceAccount | 852075 (0xD006B) |
0feb936f-47b3-49f2-9386-1dedc2c23765 |
Both masks include CreateChild, DeleteChild, Self, WriteProperty, DeleteTree, Delete, WriteDacl and WriteOwner. 852331 additionally includes ExtendedRight. dMSA is omitted, with a diagnostic, unless its schema class exists and a domain DC computer reports a version at least 10.0 (26100). This follows MDI's Server 2025 domain condition. Missing or unreadable mandatory schema classes still fail the domain request closed. If only the conditional dMSA schema/DC check is unreadable or cannot classify DC versions, the other five class ACEs continue; the plan records dMSA in SkippedDefinitions with PrerequisiteStatus: Unknown and the original diagnostic. Known inapplicability uses NotApplicable instead. Configure emits a separate Skipped prerequisite row, writes only the selected five definitions, and reports completion with a skipped control. This does not establish dMSA auditing or complete six-class coverage; resolve the prerequisite and rerun to assess dMSA. Protected child SACLs and inheritance propagation are not established by a root ACE read-back.
MdiConfiguration targets RootDSE configurationNamingContext, with WriteProperty (32, 0x20), Success and Failure, this object and all descendants, flags 194 (0xC2), and both GUIDs empty. Microsoft's Configuration container guidance is conditional on current or former Exchange deployments. WELA checks for an msExchOrganizationContainer; no matching object produces NotApplicable. If all historical Exchange configuration was removed, inspect that history manually: absence does not prove Exchange never existed. Configuration is replicated forest-wide; this operation is not scoped to one domain's users.
PkiObjects is a WELA targeted profile, not a claim that MDI prescribes a PKI SACL baseline. Each explicitly selected object gets a Success-only, this-object-only ACE: WriteProperty, Delete, WriteDacl and WriteOwner (852000, 0xD0020), flags 64 (0x40), both GUIDs empty. It accepts only these existing objects under this connection's Configuration naming context:
| Object class | Allowed container | Schema class GUID (validated, not placed in the direct-object ACE) |
|---|---|---|
| pKICertificateTemplate | CN=Certificate Templates,CN=Public Key Services,CN=Services,<ConfigurationDN> |
e5209ca2-3bba-11d2-90cc-00c04fd91ab1 |
| pKIEnrollmentService | CN=Enrollment Services,CN=Public Key Services,CN=Services,<ConfigurationDN> |
ee4aa692-3bba-11d2-90cc-00c04fd91ab1 |
See Microsoft's certificate template schema and enrollment service schema. WriteProperty covers changes such as template attributes and an enrollment service's published certificateTemplates list; object creation, child objects, enrollment access rights and CA AuditFilter are separate. No security enforcement, enrollment permissions, CA settings, DACLs or owners are changed.
PKI parent membership is proven by a one-level LDAP lookup under the exact approved container using the target's binary object GUID. A matching textual DN suffix, including an escaped comma in an object's name, cannot establish this membership.
Verification, concurrency and recovery
The command reads owner, group, DACL and SACL together using the critical LDAP security descriptor flags control. A missing object, incomplete descriptor or permission failure is an error. It records the original complete SDDL and binary descriptor, object GUID, same-DC uSNChanged, target DN and requested ACEs in before.jsonl. A separate ad-sacl-*.json receipt records the exact missing ACE additions and expected descriptor before the write. Keep these files private and treat receipts as trusted administrator input; they contain directory security configuration.
Receipts initially have ReceiptStatus: Pending, which never authorizes automatic rollback. Only after the LDAP write succeeds and a fresh read verifies the original security information and requested ACEs does WELA persist Confirmed, the confirmation time and observed state. Confirmation replaces the intent file while retaining its .pending backup. A failed/stale request, failed read-back, interruption or unpersisted confirmation requires manual recovery review; another writer's later matching ACEs do not turn an unconfirmed intention into WELA-owned changes.
Existing ACEs are retained byte-for-byte. A same-scope ACE with a superset of the rights/outcomes already satisfies the request; WELA adds only missing audit ACEs. After the prompt and durable journal, WELA rereads the same DC's object GUID, USN and complete descriptor and refuses a changed object. The LDAP modify uses a critical SACL-only control, preserving owner/group/DACL on the server. Read-back verifies every original ACE, non-SACL security information and requested auditing; a final read detects later drift. Microsoft documents that uSNChanged is local to a DC, so the command never substitutes another DC for these checks.
Use an exclusive maintenance window for SACL changes. The immediate comparison is not an atomic compare-and-swap: a concurrent writer in the final read/write window can still lose a SACL update. WELA does not claim LDAP transaction protection, lock other writers, or automatically restore a whole descriptor after an uncertain result. Stop other SACL editors/automation, review failures against the journal and current descriptor, and verify again after inheritance/replication have settled.
# Remove additions from one trusted receipt, first as a dry run.
./WELA.ps1 ad-object-sacl -AdServer dc01.example.test -AdSaclAction Rollback `
-AdReceiptPath .\new-ad-backup\ad-sacl-RECEIPT-ID.json -DryRun
./WELA.ps1 ad-object-sacl -AdServer dc01.example.test -AdSaclAction Rollback `
-AdReceiptPath .\new-ad-backup\ad-sacl-RECEIPT-ID.json -Auto `
-BackupPath .\new-rollback-backup -ResultsPath rollback-result.json
Rollback requires a confirmed receipt, checks the DC and object identity, validates that the receipt's expected SACL contains precisely the recorded additions plus the old ACEs, and requires the current SACL to match that expected sequence. It removes only those exact additions, using the current descriptor and a SACL-only write; it never restores old owner/group/DACL data. Repeated rollback is idempotent. Reordered/merged ACEs or any intervening SACL edit make automatic ownership ambiguous and are refused. Pending receipts are refused even if current ACEs match the intended additions. If automatic rollback is refused, compare the original SDDL/ACE bytes, receipt additions and current SACL on the exact DC, identify additions manually, remove only those demonstrably attributable to this run, and preserve all unrelated current entries. Do not restore the complete saved SDDL over later changes.
SaclConfigured/Applied/AlreadyCompliant refer to the selected object's SACL. ChangeRequired, NotApplicable, Unknown, Blocked, runner Skipped, Failed and Overridden remain distinct. Exit 0 means no read/write/verification failures; dry runs and skipped requests do not establish configuration. No Sigma rule uplift is claimed.
Required isolated-DC evidence before closing issue #371
AuditPolicyPrerequisites reports Unknown separately: this LDAP command neither reads nor configures the DC's effective audit policy. Verify Directory Service Access Success (and Failure for Configuration failure auditing), GUID 0cce923b-69ae-11d9-bed3-505054503030, and Directory Service Changes Success, GUID 0cce923c-69ae-11d9-bed3-505054503030, on each DC that will process test operations. Check GPO precedence and effective results; enabling a policy alone does not supply an object SACL. Event 5136 requires matching object auditing and records the modified attribute on a DC.
The automated tests use mock directory responses, in-memory Windows security descriptors and captured LDAP requests. They never bind to or modify live AD. Windows PowerShell 5.1 and PowerShell 7 CI validate native construction, preservation, idempotency, conservative rollback and the shared runner. Live Windows/DC validation has not been performed by this change.
For completion, use isolated patched DC snapshots and dedicated test accounts, groups, computers, templates and enrollment service objects. Record DC build, forest/domain, before/after SDDL, effective audit policy and WELA JSON. Make a benign attribute change on each selected object through the exact DC, then capture matching Security 4662 and 5136 XML, including computer, object DN/GUID, subject, access/property and attribute fields. Inspect inherited ACEs on each descendant class and protected objects; verify other DCs after replication. For certificate template or publication-list changes, collect the event from the DC processing the AD change, not automatically from the CA. Validate central ingestion, rerun for idempotency, exercise rollback and verify unrelated authorization and audit entries remain. No generated-event, replication, retention or ingestion guarantee is made here. Sysmon and external telemetry are out of scope.