feat: add acl_abuse LDAP module for ACE chain enumeration#1176
feat: add acl_abuse LDAP module for ACE chain enumeration#1176sup3rDav3 wants to merge 2 commits intoPennyw0rth:mainfrom
Conversation
- Detects WriteDACL, GenericAll, WriteOwner on AD objects - Detects ForceChangePassword via extended rights GUID - Detects WriteProperty on member, servicePrincipalName, msDS-KeyCredentialLink - Detects DS-Replication-Get-Changes and Get-Changes-All (DCSync path) - Includes domain root object scanning for replication rights - Attack suggestions printed inline for each finding - Optional JSON export via OUTPUT_FILE - Tested against Windows Server 2022 domain controller
| Nice ! Could you replace |
| Thanks for the PR! Will test it when I have reviewed the rest of the PRs that have piled up, but I'll do a quick code review for now. |
NeffIsBack left a comment
There was a problem hiding this comment.
Quick code review, without diving too deep into the logic
| @@ -0,0 +1,393 @@ | |||
| from ldap3.protocol.microsoft import security_descriptor_control | |||
There was a problem hiding this comment.
As @azoxlpf said, please replace it with the impacket equivalent
| context.log.fail( | ||
| "Could not resolve target principal — check TARGET_USER or auth credentials" | ||
| ) |
There was a problem hiding this comment.
Please do such logs in one line. That just wastes space.
| context.log.display( | ||
| f"Resolved {len(principal_sids)} principal SID(s), enumerating ACEs..." | ||
| ) |
| self.context.log.fail( | ||
| "Could not determine current username — use TARGET_USER option" | ||
| ) |
| user_entries = self._ldap_search( | ||
| f"(&(objectClass=user)(sAMAccountName={username}))", | ||
| ["objectSid", "memberOf", "distinguishedName", "sAMAccountName"], | ||
| ) |
| "object_classes": obj_classes | ||
| if isinstance(obj_classes, list) | ||
| else [obj_classes], |
There was a problem hiding this comment.
Logic please in one line, otherwise this is confusing
| self.context.log.success( | ||
| f"Found {total} abusable ACE(s) — {critical} CRITICAL, {high} HIGH" | ||
| ) |
| def _ldap_search(self, filter_str, attributes, controls=None): | ||
| try: | ||
| response = self.conn.search( | ||
| searchFilter=filter_str, | ||
| attributes=attributes, | ||
| searchControls=controls, | ||
| ) | ||
| if not response: | ||
| return [] | ||
| return [e for e in response if isinstance(e, SearchResultEntry)] | ||
| except Exception as e: | ||
| self.context.log.debug(f"LDAP search failed ({filter_str}): {e}") | ||
| return [] | ||
| | ||
| def _parse_attributes(self, entry): | ||
| attrs = {} | ||
| try: | ||
| for attr in entry["attributes"]: | ||
| name = str(attr["type"]) | ||
| vals = attr["vals"] | ||
| parsed_vals = [] | ||
| for v in vals: | ||
| try: | ||
| parsed_vals.append(bytes(v)) | ||
| except Exception: | ||
| parsed_vals.append(str(v)) | ||
| if len(parsed_vals) == 1: | ||
| attrs[name] = parsed_vals[0] | ||
| else: | ||
| attrs[name] = parsed_vals | ||
| except Exception as e: | ||
| self.context.log.debug(f"Failed to parse entry attributes: {e}") | ||
| return attrs |
There was a problem hiding this comment.
Can both be removed after reworking the rest.
| return ldaptypes.LDAP_SID( | ||
| data=ace["Ace"]["Sid"].getData() | ||
| ).formatCanonical() |
| netexec nfs TARGET_HOST -u "" -p "" --enum-shares | ||
| netexec nfs TARGET_HOST -u "" -p "" --get-file /NFStest/test/test.txt ../test.txt | ||
| netexec nfs TARGET_HOST -u "" -p "" --put-file ../test.txt /NFStest/test | ||
| nxc ldap {host} -u {user} -p {pass} -M acl_abuse |
There was a problem hiding this comment.
That's at the wrong position. Please sort it into its category
There was a problem hiding this comment.
In general, this has the wrong syntax
Description
Adds a new LDAP module
acl_abusethat enumerates abusable ACEs from a targetprincipal to other AD objects, surfacing attack paths with inline attack suggestions.
Detections include:
msDS-KeyCredentialLink (shadow credentials path)
This module was developed with the assistance of Claude (Anthropic) via claude.ai.
Claude assisted with debugging LDAP/impacket attribute access and ACE mask value corrections. All code was reviewed, tested, and verified by the author against a live Windows Server 2022 domain controller.
Type of change
Setup guide for the review
Environment:
Target:
Test setup — run on DC as Domain Admin:
Run the module:
Expected output:
Screenshot:
Checklist:
tests/e2e_commands.txtfile if necessary