ad-acl
Active Directory ACL semantics, in pure Rust, with no FFI and no Windows dependency.
windows-sddl turns an nTSecurityDescriptor
blob into ACEs. This crate answers the next question: what can the trustee actually do
with it?
An ACE carrying WRITE_PROP plus object GUID 5b47d60f-6090-40b2-9f37-2a4de88f3063 is not
"write property" — it is AddKeyCredential: Shadow Credentials, i.e. account takeover
without touching the password.
use ;
let sd = parse?;
for g in grants
What it gives you
classify(&Ace) -> Vec<ControlPrimitive>— one ACE to the primitives it grants.grants(&SecurityDescriptor) -> Vec<Grant>— every trustee/primitive pair, owner included.ControlPrimitive::cost()— attacker cost, ready to use as a graph edge weight.ControlPrimitive::impact()/mitigation()— the report lines for each primitive.catalog— the fixed, forest-independent extended-right and attribute GUIDs.SchemaMap— runtime resolution for the GUIDs that are not fixed.
Recognised primitives include GenericAll, WriteDacl, WriteOwner,
ForceChangePassword, AddMember, AddSelfToGroup, AddKeyCredential, WriteRbcd,
WriteSpn, WriteAltSecurityIdentities, WriteAllowedToDelegateTo, WriteGpLink,
ReadGmsaPassword, ReadLapsPassword, the three replication rights, ReanimateTombstones,
Enroll and CreateDmsa.
Forest-specific GUIDs
LAPS (ms-Mcs-AdmPwd, msLAPS-Password, msLAPS-EncryptedPassword), the gMSA blob
(msDS-ManagedPassword) and the dMSA class (msDS-DelegatedManagedServiceAccount) get
their schemaIDGUID generated when the schema is extended, so they differ per forest and
cannot be hard-coded. Read them from CN=Schema,CN=Configuration,<root> and pass them in:
let schema = from_entries; // (lDAPDisplayName, schemaIDGUID)
let grants = grants_with;
Without the map those ACEs are simply not classified — the crate never guesses.
Scope
Only allow ACEs are interpreted. Deny ACEs are skipped rather than subtracted, so the output is an over-approximation of effective access. That is what attack-path tooling wants; it is not an effective-permissions engine.
License
MIT