ad-acl 0.1.0

Active Directory ACL semantics: turn a security descriptor into the concrete control primitives it grants (DCSync, Shadow Credentials, RBCD, WriteSPN, ReadGMSAPassword, …).
Documentation
  • Coverage
  • 91.46%
    75 out of 82 items documented1 out of 21 items with examples
  • Size
  • Source code size: 45.22 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 876.83 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 9s Average build duration of successful builds.
  • all releases: 9s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Repository
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • icedracon

ad-acl

crates.io docs.rs

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 ad_acl::{grants, ControlPrimitive};

let sd = windows_sddl::parse(&raw)?;
for g in grants(&sd) {
    if g.primitive == ControlPrimitive::DcsyncGetChangesAll {
        println!("{} can DCSync", g.trustee);
        println!("  impact: {}", g.primitive.impact());
        println!("  fix:    {}", g.primitive.mitigation());
    }
}

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 = ad_acl::SchemaMap::from_entries(pairs); // (lDAPDisplayName, schemaIDGUID)
let grants = ad_acl::grants_with(&sd, &schema);

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