Skip to content

[Daclread] New module option - Filter interesting-only permissions#973

Open
njutn95 wants to merge 1 commit into
Pennyw0rth:mainfrom
njutn95:filter-interesting-only-dacl
Open

[Daclread] New module option - Filter interesting-only permissions#973
njutn95 wants to merge 1 commit into
Pennyw0rth:mainfrom
njutn95:filter-interesting-only-dacl

Conversation

@njutn95
Copy link
Copy Markdown

@njutn95 njutn95 commented Oct 24, 2025

Description

Add the INTERESTING_ONLY boolean option to the daclread module. This allows filtering a response of 50+ ACEs into 1-5 relevant entries. It does so by removing ACE entries that can be performed with users/groups whose SID is below 1000 (default groups that share common permissions), and excluding Read capabilities, which are in 99% considered irrelevant. This allows the user to see permissions such as "John Doe can change the email address of Will Smith" in a matter of a couple of seconds.

Type of change

Insert an "x" inside the brackets for relevant items (do not delete options)

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Deprecation of feature or functionality
  • This change requires a documentation update
  • This requires a third party update (such as Impacket, Dploot, lsassy, etc)

Setup guide for the review

Run the command using the

netexec ldap <target> -u <user> -p <password> -M daclread -o ACTION=read TARGET=<target_user> INTERESTING_ONLY=True

and

netexec ldap <target> -u <user> -p <password> -M daclread -o ACTION=read TARGET=<target_user> INTERESTING_ONLY=False

whilst targeting a user that can be modified by a non-default user/group.

Screenshots (if appropriate):

Running the command without the flag executes the default module behavior of listing all ACEs.

Screenshot 2025-10-24 at 18 30 27

Running the command with the option flag enabled returns an actionable list of ACEs.

Screenshot 2025-10-24 at 18 30 48

Checklist:

Insert an "x" inside the brackets for completed and relevant items (do not delete options)

  • I have ran Ruff against my changes (via poetry: poetry run python -m ruff check . --preview, use --fix to automatically fix what it can)
  • I have added or updated the tests/e2e_commands.txt file if necessary (new modules or features are required to be added to the e2e tests)
  • New and existing e2e tests pass locally with my changes
  • If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)

@NeffIsBack
Copy link
Copy Markdown
Member

Thanks for the PR! I will take a look at it when i have reviewed the pile of PRs that have accumulated.

@NeffIsBack NeffIsBack added the enhancement New feature or request label Oct 26, 2025
Comment thread nxc/modules/daclread.py
ACE_TYPE The type of ACE to read (Allowed or Denied)
RIGHTS An interesting right to filter on ('FullControl', 'ResetPassword', 'WriteMembers', 'DCSync')
RIGHTS_GUID A right GUID that specify a particular rights to filter on
INTERESTING_ONLY Whether to exclude common or default permissions and only focus on more specific, significant ones
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reword this, the filter is based on trustee SID (RID ≥ 1000) and access-mask patterns, not on “common permissions"

Comment thread nxc/modules/daclread.py
for ace in dacl["Data"]:
parsed_ace = self.parse_ace(ace)
parsed_dacl.append(parsed_ace)
if self.interesting_only:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use .get() and continue when keys are missing, or skip unsupported ACEs early

Comment thread nxc/modules/daclread.py
parsed_dacl.append(parsed_ace)
if self.interesting_only:
# only process SIDs > 1000
if (re.search(r"\(S-1-5-.*-[1-9]\d{3,}\)$", parsed_ace["Trustee (SID)"]) and
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please switch to parsing the canonical SID (e.g. extract RID from ace['Ace']['Sid'] before resolveSID) instead of regex on the formatted "Name (S-1-5-...)" string, more reliable and avoids LDAP lookups for ACEs you’ll drop anyway.

Comment thread nxc/modules/daclread.py
# only process SIDs > 1000
if (re.search(r"\(S-1-5-.*-[1-9]\d{3,}\)$", parsed_ace["Trustee (SID)"]) and
(re.search(r"GenericAll|Write|Create|Delete|FullControl|Modify|ControlAccess", parsed_ace["Access mask"]) or
("ExtendedRight" in parsed_ace["Access mask"] and "ACCESS_ALLOWED" in parsed_ace["ACE Type"]))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will KeyError on standard ACCESS_ALLOWED_ACE / ACCESS_DENIED_ACE because parse_ace() overwrites the dict and drops "ACE Type" (see L416–419). Please either keep "ACE Type" in the standard-ACE branch

Comment thread nxc/modules/daclread.py
self.rights_guid = module_options["RIGHTS_GUID"]

if "INTERESTING_ONLY" in module_options:
self.interesting_only = module_options["INTERESTING_ONLY"].lower() in ["true", "1", "yes"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use .get() to avoid relying on key presence and keep it consistent

Comment thread nxc/modules/daclread.py
# only process SIDs > 1000
if (re.search(r"\(S-1-5-.*-[1-9]\d{3,}\)$", parsed_ace["Trustee (SID)"]) and
(re.search(r"GenericAll|Write|Create|Delete|FullControl|Modify|ControlAccess", parsed_ace["Access mask"]) or
("ExtendedRight" in parsed_ace["Access mask"] and "ACCESS_ALLOWED" in parsed_ace["ACE Type"]))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please switch "ExtendedRight in ..." to an explicit match. It accidentally matches AllExtendedRights and never matches the real flag names (ControlAccess, etc.). Use the actual permission names produced by the enums instead of a substring check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants