Skip to content

fix: only count ACEs that actually grant Full Control on System Manag… - #14

Merged
Mayyhem merged 4 commits into
SpecterOps:mainfrom
chryzsh:fix/smc-dacl-ace-scope
Oct 6, 2026
Merged

Mayyhem merged 4 commits into
SpecterOps:mainfrom
chryzsh:fix/smc-dacl-ace-scope

Conversation

@chryzsh

@chryzsh chryzsh commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

What's wrong

In a domain running Exchange, the LDAP collector reports Exchange Trusted Subsystem and Organization Management as holding GenericAll on CN=System Management. Neither does. _expand_group_targets then walks Exchange Trusted Subsystem and registers every Exchange server as a scan target, so the whole per-host pipeline runs against them.

_parse_sd_generic_all (src/openhound_sccm/collectors/ldap.py:786) reads the access mask and nothing else. It never touches AceFlags, and it decodes the object-ACE flags only to work out where the SID starts before throwing the meaning away.

An access mask of 0x000F01FF on its own doesn't mean Full Control:

  • an ACE with INHERIT_ONLY (0x08) grants nothing on this container. It's there so AD can copy it to child objects.
  • an object ACE with an ObjectType GUID grants its mask over one property set or extended right, not over the object. One with an InheritedObjectType GUID only applies to child objects of that class.

Exchange's setup /PrepareAD writes both kinds at the domain root with that exact mask, and they inherit down onto System Management.

What it looks like

A System Management descriptor from an Exchange domain, fed to _parse_sd_generic_all:

Principal Now With this change
Domain Admins reported reported
the site server reported reported
SCCM servers group reported reported
Enterprise Admins reported reported
LOCAL SYSTEM reported dropped
Organization Management reported dropped
Exchange Trusted Subsystem (3 ACEs) reported dropped

Exchange Trusted Subsystem had three ACEs there. One scoped by ObjectType, two INHERIT_ONLY and scoped by InheritedObjectType. Its members are the Exchange servers that were being collected.

The fix

  • read AceFlags and skip INHERIT_ONLY ACEs
  • skip object ACEs carrying ACE_OBJECT_TYPE_PRESENT or ACE_INHERITED_OBJECT_TYPE_PRESENT
  • skip LOCAL SYSTEM, BUILTIN\Administrators, CREATOR OWNER

Plain and container-inherit ACEs are unaffected, so real site servers and the group holding Full Control still come through. Each skip writes a debug line.

Tests

tests/ldap_smc_dacl_scope_test.py, 8 cases on synthetic descriptors. Plain ACE kept, container-inherit ACE kept, unscoped object ACE kept, INHERIT_ONLY dropped, both scoped object ACE shapes dropped, built-in principal dropped, plus one case shaped like a real Exchange-domain DACL.

I checked they catch the bug rather than just passing: reverted the parser to HEAD and 5 of the 8 failed, restored it and all 8 passed. Full suite is 1048 passed, 5 skipped.

Same bug in the deprecated PowerShell

powershell_deprecated/ConfigManBearPig.ps1:3458 has this problem plus a worse one:

$genericAllAccounts = $acl.Access | Where-Object {
    $_.AccessControlType -eq "Allow" -and
    $_.ActiveDirectoryRights -eq "GenericAll"
    $_.IdentityReference -notlike "NT AUTHORITY\*"
}

Missing -and on the second line. Only the last statement is the filter output, so every ACE that isn't NT AUTHORITY passes, Allow or Deny, GenericAll or not. Not touched here since it's deprecated, but worth knowing if anyone reads it.

🤖 Generated with Claude Code

…ement

_parse_sd_generic_all read the access mask and nothing else. It decoded the
object-ACE flags purely to work out where the SID started, then threw the
meaning away, and it never looked at AceFlags at all.

Exchange's setup /PrepareAD writes ACEs at the domain root for Exchange
Trusted Subsystem and Organization Management with a 0x000F01FF mask, and
they inherit down onto CN=System Management. None of them grant anything on
the container:

  - some are INHERIT_ONLY, present only so AD can copy them to child objects
  - the rest carry an ObjectType GUID, which scopes the mask to a single
    Exchange property set, or an InheritedObjectType GUID, which scopes it
    to one child object class

Both got reported as GenericAll. _expand_group_targets then walked Exchange
Trusted Subsystem and registered every Exchange server as a scan target.
Checked against a real domain DACL: 10 principals reported, of which only
the site server and the SCCM group were real.

Now skip INHERIT_ONLY ACEs, skip object ACEs carrying either GUID, and skip
LOCAL SYSTEM / BUILTIN\Administrators / CREATOR OWNER. Plain and
container-inherit ACEs are unaffected.

tests/ldap_smc_dacl_scope_test.py covers both directions with synthetic
descriptors: five of the eight cases fail without this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chryzsh

chryzsh commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Same problem in garrettfoster13/sccmhunter#139

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants