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,typeis 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
- Example:
- Keywords:
NULL: Used to check if a field is not present.UNRESOLVED: Used with thesidfield 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_sid | SD | String | The SID of the object's owner. |
| group_sid | SD | String | The SID of the object's primary group. |
| control | SD | Numeric | The SECURITY_DESCRIPTOR_CONTROL flags for the SD. |
| acecount | SD | Numeric | The number of ACEs in the evaluated ACL (DACL or SACL). |
| sid | ACE | String | The SID of the trustee in the ACE. |
| type | ACE | Numeric | The type of the ACE (e.g., ALLOWED, DENIED, AUDIT). |
| mask | ACE | Numeric | The 32-bit access mask of the ACE. |
| flags | ACE | Numeric | The AceFlags of the ACE header (e.g., INHERITED_ACE). |
| objflags | ACE | Numeric | For object-specific ACEs, the Flags field of the ACE body. |
| property | ACE | String | For object-specific ACEs, the GUID of the property or property set. |
| in_object | ACE | String | For 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 operator | Flags |= INHERITED + OBJECT_INHERITED |
| &= | And bitwise operator | Flags &= 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
- Supported Operators:
- Keywords:
NULL: Used to check if a field is empty (e.g.,property == NULL).UNRESOLVED: Used with thesidfield 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 | 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_OBJECTDENIED,DENIED_CALLBACK,DENIED_CALLBACK_OBJECT,DENIED_OBJECTAUDIT,AUDIT_CALLBACK,AUDIT_CALLBACK_OBJECT,AUDIT_OBJECTMANDATORY_LABEL
flags and objflags Field Constants
CONTAINER_INHERIT/CONTAINER_INHERIT_ACEOBJECT_INHERIT/OBJECT_INHERIT_ACENO_PROPAGATE/NO_PROPAGATE_INHERIT_ACEINHERIT_ONLY/INHERIT_ONLY_ACEINHERITED/INHERITED_ACESUCCESSFUL/SUCCESSFUL_ACCESS_ACE_FLAGFAILED/FAILED_ACCESS_ACE_FLAG
control Field Constants
DACL_AUTO_INHERIT_REQ,DACL_AUTO_INHERITED,DACL_DEFAULTED,DACL_PRESENT,DACL_PROTECTEDSACL_AUTO_INHERIT_REQ,SACL_AUTO_INHERITED,SACL_DEFAULTED,SACL_PRESENT,SACL_PROTECTEDGROUP_DEFAULTED,OWNER_DEFAULTED,RM_CONTROL_VALID,SELF_RELATIVE
mask Field Constants
- Generic Rights:
GENERIC_READ,GENERIC_WRITE,GENERIC_EXECUTE,GENERIC_ALL(and abbreviationsGR,GW,GE,GA) - Standard Rights:
DELETE,READ_CONTROL,WRITE_DAC,WRITE_OWNER,SYNCHRONIZE,ACCESS_SYSTEM_SECURITY(and abbreviationsSD,RC,WD,WO) - Directory Service Rights:
CREATE_CHILD,DELETE_CHILD,LIST,SELF,READ_PROP,WRITE_PROP,DELETE_TREE,LIST_OBJECT,CONTROL_ACCESS(and abbreviationsCC,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.
- Find objects owned by the local Administrator account.
owner_sid == "Administrator" - Find objects where the DACL has inheritance blocked (is "protected").
control |= DACL_PROTECTED - Find objects where the primary group is "Domain Users".
group_sid == "Domain Users" - 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.- Find objects with any permission entry for the "Everyone" group.
ace(sid == "Everyone") - 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.
- Find objects where "Domain Admins" are explicitly allowed some right.
ace(sid == "Domain Admins" && type == ALLOWED) - Find objects where either "SYSTEM" or the "Administrators" group have an ACE.
ace(sid == "SYSTEM" || sid == "Administrators") - 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
maskandflags.- Find ACEs that grant exactly
Read ControlandWrite DACpermissions and nothing else.ace(mask == READ_CONTROL + WRITE_DAC) - Find any ACE that has
Read ControlorWrite DACpermissions.ace((mask & READ_CONTROL + WRITE_DAC) != 0) - Find any ACE that has the
INHERITED_ACEflag set, using a complex LHS.ace((flags & INHERITED_ACE) != 0) - Find any ACE that has the
INHERITED_ACEflag set, using a complex LHS.ace((flags & INHERITED_ACE + CONTAINER_INHERIT_ACE) != 0) - 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
propertyandin_objectfor Active Directory objects.- Find ACEs that grant a right specifically on the "user" object class.
ace(property == "user") - Find "Allow" ACEs that are generic and do not apply to a specific property.
ace(property == NULL && type == ALLOWED) - Find ACEs configured to be inherited only by
groupobjects.ace(in_object == "group")
Category 6: Queries with Special Keywords & Placeholders
These examples use
UNRESOLVEDand the pre-processor placeholders.- Find objects with ACEs containing SIDs that could not be resolved to a name.
ace(sid == UNRESOLVED) - Use a placeholder to find any object with a "Deny" ACE.
ace(%denied%) - 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.
- 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) - 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) ) - 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"))
- Find objects owned by the local Administrator account.