ACL Query Language

This page details the syntax, fields, operators, and functions of the ACL query language used by the AD Permissions Reporter function in NetTools.

1. General Syntax

The query language is designed to evaluate a boolean expression against a Windows Security Descriptor (SD) and its component Access Control Entries (ACEs). Queries are composed of one or more conditions combined with logical operators.

  • Grouping: Conditions can be grouped using parentheses () to enforce the order of evaluation.
  • Logical Operators:
    • && (AND): The entire expression is true only if the conditions on both the left and right are true.
    • || (OR): The expression is true if either the left or the right condition is true.
    • ! (NOT): Inverts the result of the expression that follows.
General Query Format
[LHS] [ComparisonOperator] [RHS]
Left-Hand Side (LHS) Formats

The LHS of a comparison can take one of two forms.

Simple LHS

This is a single field identifier.

  • Format: [Field]
  • Example: type == ALLOWED (Here, type is the Simple LHS).
Complex LHS

This is a bitwise expression wrapped in parentheses, which is evaluated before the comparison is performed.

  • Format: ( [Field] [BitwiseOperator] [ArithmeticValue] )
  • Components:
    • [Field]: The base numeric field to use (e.g., mask, flags, control).
    • [BitwiseOperator]: & (Bitwise AND) or | (Bitwise OR).
    • [ArithmeticValue]: A number, an enum constant, or another arithmetic expression using + and -.
  • Examples:
    • (flags & INHERITED_ACE)
    • (mask | GENERIC_READ + GENERIC_WRITE)
Right-Hand Side (RHS) Formats

The Right-Hand Side (RHS) of a comparison is the value being compared against. It can take several forms.

  • Strings: A name or SID, enclosed in double quotes if it contains spaces (e.g., "S-1-5-32-544", Administrators).
  • Numbers: Decimal or hexadecimal values (e.g., 10, 0x10000000).
  • Enum Constants: Pre-defined keywords that resolve to numbers (e.g., ALLOWED, DACL_PROTECTED).
  • Arithmetic Expressions: An expression combining numbers and/or enum constants using + (addition) and - (subtraction).
    • Example: READ_CONTROL + WRITE_DAC
  • Keywords:
    • NULL: Used to check if a field is not present.
    • UNRESOLVED: Used with the sid field to find ACEs with unresolved SIDs.
Example:
(owner_sid == "Administrators" && control |= 4) || !(ace(sid == "Everyone"))

2. Queryable Fields

Fields are divided into two contexts: those that apply to the top-level Security Descriptor and those that apply within an individual ACE.

Field Name Context Type Description
owner_sidSDStringThe SID of the object's owner.
group_sidSDStringThe SID of the object's primary group.
controlSDNumericThe SECURITY_DESCRIPTOR_CONTROL flags for the SD.
acecountSDNumericThe number of ACEs in the evaluated ACL (DACL or SACL).
sidACEStringThe SID of the trustee in the ACE.
typeACENumericThe type of the ACE (e.g., ALLOWED, DENIED, AUDIT).
maskACENumericThe 32-bit access mask of the ACE.
flagsACENumericThe AceFlags of the ACE header (e.g., INHERITED_ACE).
objflagsACENumericFor object-specific ACEs, the Flags field of the ACE body.
propertyACEStringFor object-specific ACEs, the GUID of the property or property set.
in_objectACEStringFor object-specific ACEs, the GUID of the inherited object type.

3. Comparison Operators and Values

Comparison Operators:

The language supports a variety of operators for comparing field values.

Operator Meaning Example
== or =Equals: Performs a case-insensitive string comparison or an exact numeric match.sid == "S-1-5-32-544"
type == ALLOWED
!=Not Equals: The inverse of ==.sid != "Everyone"
>Greater Than (Numeric)acecount > 10
<Less Than (Numeric)mask < 256
>=Greater Than or Equal To (Numeric)mask >= GENERIC_READ
<=Less Than or Equal To (Numeric)flags <= 3

Bitwise Check Operators:

operator Meaning Example
|=Or bitwise operatorFlags |= INHERITED + OBJECT_INHERITED
&=And bitwise operatorFlags &= INHERITED
Value Types
  • Strings: Can be enclosed in double quotes (e.g., "S-1-5-10"), or if they contain no spaces or special characters, quotes can be omitted (e.g., Administrators). Name resolution to SID is performed automatically for SID-based fields. GUIDs can also be provided as friendly names (e.g., "user-account") or in standard string format.
  • Numbers: Can be in decimal (e.g., 268435456) or hexadecimal format (e.g., 0x10000000).
  • Arithmetic Expressions: For numeric comparisons, the RHS can be an expression combining numbers and/or enum constants.
    • Supported Operators: + (addition) and - (subtraction).
    • Format: [Value1] + [Value2] - [Value3] ...
    • Example: mask == READ_CONTROL + WRITE_DAC
  • Keywords:
    • NULL: Used to check if a field is empty (e.g., property == NULL).
    • UNRESOLVED: Used with the sid field to find ACEs with trustees that could not be resolved to a name.
  • Enum Constants: The parser recognises a large set of predefined constants that resolve to numeric values. These are context-sensitive. Refer to Section 6 for a comprehensive list.

4. The ace() Clause

To query against individual ACEs within a Security Descriptor, conditions must be wrapped in an ace() clause. The query will succeed if any ACE in the ACL matches the conditions inside the clause.

Syntax: ace(ace_conditions)

  • ace_conditions: A standard boolean expression using ACE-context fields (sid, mask, type, flags, etc.).

Example: Find any ACE that denies DELETE access to the 'Users' group.

ace(type == DENIED && sid == "Users" && mask |= DELETE)

5. Pre-Processor Placeholders

The query engine features a pre-processor that expands special placeholders before parsing.

Placeholder Description Example
%allow%Expands to an OR clause that matches all "Allow" type ACEs. This is a convenient shorthand for checking for any kind of permissive entry.ace(%allow%)
%denied%Expands to an OR clause that matches all "Deny" type ACEs. This is a convenient shorthand for checking for any kind of restrictive entry.ace(%denied%)
%trustee%The user is prompted to provide the trustee name. The application will replace this placeholder with the SID of the user or group provided.owner_sid == %trustee%
%trustee_membership%The user is prompted for the trustee. Which is expanded to a complex OR clause containing the SIDs of the specified trustee and all of its groups (via the tokenGroups attribute).ace(%trustee_membership%)
%trustee_membership trustee%Expands to a complex OR clause containing the SIDs of the named trustee and all of its groups.ace(%trustee_membership "Domain Admins"%)
%trustee_auth%Expands to include both the user's tokenGroups and the standard authenticated user SIDs (e.g., Authenticated Users, Everyone).ace(%trustee_auth%)
%trustee_auth trustee%Expands to include the named user's tokenGroups and the standard authenticated user SIDs.ace(%trustee_auth "jdoe"%)
%propertyset name%Expands to find all permissions associated with a specific attribute. It first resolves the attribute (e.g., Telephone Number") to its GUID. It then finds any Property Set that the attribute is a member of. The placeholder is replaced with an OR condition that matches if the ACE's property field is either the attribute's own GUID or the GUID of any property set it belongs to."ace(%propertyset "Phone-Numbers"%)

Example: Check if a user 'jdoe' or any of their groups has been explicitly granted Full Control.

ace(%trustee_membership - jdoe% && type == ALLOWED && mask |= GENERIC_ALL)

6. Enum Constants Reference

The following constants can be used in place of numeric values. They are context-sensitive and will only work when compared against the appropriate field.

type Field Constants
  • ALLOWED, ALLOWED_CALLBACK, ALLOWED_CALLBACK_OBJECT, ALLOWED_COMPOUND, ALLOWED_OBJECT
  • DENIED, DENIED_CALLBACK, DENIED_CALLBACK_OBJECT, DENIED_OBJECT
  • AUDIT, AUDIT_CALLBACK, AUDIT_CALLBACK_OBJECT, AUDIT_OBJECT
  • MANDATORY_LABEL
flags and objflags Field Constants
  • CONTAINER_INHERIT / CONTAINER_INHERIT_ACE
  • OBJECT_INHERIT / OBJECT_INHERIT_ACE
  • NO_PROPAGATE / NO_PROPAGATE_INHERIT_ACE
  • INHERIT_ONLY / INHERIT_ONLY_ACE
  • INHERITED / INHERITED_ACE
  • SUCCESSFUL / SUCCESSFUL_ACCESS_ACE_FLAG
  • FAILED / FAILED_ACCESS_ACE_FLAG
control Field Constants
  • DACL_AUTO_INHERIT_REQ, DACL_AUTO_INHERITED, DACL_DEFAULTED, DACL_PRESENT, DACL_PROTECTED
  • SACL_AUTO_INHERIT_REQ, SACL_AUTO_INHERITED, SACL_DEFAULTED, SACL_PRESENT, SACL_PROTECTED
  • GROUP_DEFAULTED, OWNER_DEFAULTED, RM_CONTROL_VALID, SELF_RELATIVE
mask Field Constants
  • Generic Rights: GENERIC_READ, GENERIC_WRITE, GENERIC_EXECUTE, GENERIC_ALL (and abbreviations GR, GW, GE, GA)
  • Standard Rights: DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, ACCESS_SYSTEM_SECURITY (and abbreviations SD, RC, WD, WO)
  • Directory Service Rights: CREATE_CHILD, DELETE_CHILD, LIST, SELF, READ_PROP, WRITE_PROP, DELETE_TREE, LIST_OBJECT, CONTROL_ACCESS (and abbreviations CC, DC, LC, WS, RP, WP, DT, LO, CA)
  • Combined Rights: FC (Full Control)

 

Examples

  • Category 1: Basic Security Descriptor (SD) Level Queries

    These queries check fields on the top-level security descriptor without inspecting individual ACEs.

    1. Find objects owned by the local Administrator account.
      owner_sid == "Administrator"
      
    2. Find objects where the DACL has inheritance blocked (is "protected").
      control |= DACL_PROTECTED
      
    3. Find objects where the primary group is "Domain Users".
      group_sid == "Domain Users"
      
    4. Find objects with more than 15 ACEs in their ACL.
      acecount > 15
      

    Category 2: Basic Access Control Entry (ACE) Level Queries

    These queries use the ace() clause to find objects that have at least one ACE matching the specified criteria.

    1. Find objects with any permission entry for the "Everyone" group.
      ace(sid == "Everyone")
      
    2. Find objects that have at least one "Deny" ACE.
      ace(type == DENIED)
      

    Category 3: Queries with Logical Operators (&&, ||, !)

    These examples show how to combine conditions to create more specific queries.

    1. Find objects where "Domain Admins" are explicitly allowed some right.
      ace(sid == "Domain Admins" && type == ALLOWED)
      
    2. Find objects where either "SYSTEM" or the "Administrators" group have an ACE.
      ace(sid == "SYSTEM" || sid == "Administrators")
      
    3. Find objects where the "Everyone" group has an ACE that is NOT for Full Control.
      ace(sid == "Everyone" && !(mask |= FC))
      

    Category 4: Queries with Numeric, Bitwise, and Arithmetic Operations

    These examples demonstrate comparisons and operations on numeric fields like mask and flags.

    1. Find ACEs that grant exactly Read Control and Write DAC permissions and nothing else.
      ace(mask == READ_CONTROL + WRITE_DAC)
      
    2. Find any ACE that hasRead Control or Write DAC permissions.
      ace((mask & READ_CONTROL + WRITE_DAC) != 0) 
    3. Find any ACE that has the INHERITED_ACE flag set, using a complex LHS.
      ace((flags & INHERITED_ACE) != 0)
    4. Find any ACE that has the INHERITED_ACE flag set, using a complex LHS.
      ace((flags & INHERITED_ACE + CONTAINER_INHERIT_ACE) != 0)
    5. Find ACEs that grant permissions to be inherited by both container or non-container objects.
      ace((flags & CONTAINER_INHERIT_ACE + OBJECT_INHERIT_ACE)!= 0)
      

    Category 5: Queries with Object-Specific ACE Fields

    These examples use fields like property and in_object for Active Directory objects.

    1. Find ACEs that grant a right specifically on the "user" object class.
      ace(property == "user")
      
    2. Find "Allow" ACEs that are generic and do not apply to a specific property.
      ace(property == NULL && type == ALLOWED)
      
    3. Find ACEs configured to be inherited only by group objects.
      ace(in_object == "group")
      

    Category 6: Queries with Special Keywords & Placeholders

    These examples use UNRESOLVED and the pre-processor placeholders.

    1. Find objects with ACEs containing SIDs that could not be resolved to a name.
      ace(sid == UNRESOLVED)
      
    2. Use a placeholder to find any object with a "Deny" ACE.
      ace(%denied%)
      
    3. Check if user 'jdoe' or any of their groups are granted the right to change permissions.
      ace(%trustee_membership - jdoe% && mask |= WRITE_DAC)
      

    Category 7: Complex Combined Queries

    These queries mix multiple features to perform a highly specific search.

    1. Find objects not owned by "Domain Admins" that still grant "Authenticated Users" some allowed right.
      (owner_sid != "Domain Admins") && ace(sid == "Authenticated Users" && type == ALLOWED)
      
    2. Find objects with a directly assigned (non-inherited) ACE that allows "Everyone" to delete the object or change its owner.
      ace( !(flags |= INHERITED_ACE) && sid == "Everyone" && (mask |= DELETE || mask |= WRITE_OWNER) )
      
    3. Find OUs that have BadSuccessor permissions.
      ace(((type=allowed && (mask & CC + WO + WD) !=0) || ( type= allowed_object && property = 0feb936f-47b3-49f2-9386-1dedc2c23765)) && (sid != "domain admins" && sid != "system" && sid != administrators && sid != "enterprise admins"))