MacSecLabs
1.2Free preview

Module 1.2: Local Accounts, Domain Accounts, and Admin Mapping

Operator outcome

Land as an unprivileged domain user, discover your own identity and groups from the shell, read which accounts hold local admin on this Mac, and resolve the nested domain group that quietly grants that admin right. Turn that picture into a privilege target list, and be honest about what the Mac will and will not tell you about domain group membership.

Scenario

A beachhead is not a plan. Once you are on an enterprise Mac, the next question is who matters here: which domain groups ride on this box, and which accounts actually hold local admin. That answer prices every credential you recover later. If admin rights flow through a nested domain group, then any one member of that group is local admin on this Mac, and likely on every Mac configured the same way. If admin is only a handful of local accounts, your path looks different.

Operators misread this in two directions. Some assume all domain users are equivalent and miss the one group that confers sudo. Others read the local admin member list, see only local accounts, and stop, missing the nested group GUID one line further down. The discipline here is to start from your own identity, walk out to the admin group, resolve the nesting, and confirm with an effective-membership check. Five minutes of directory reads replaces both guesses with a map.

There is one boundary worth knowing up front. macOS will tell you which nested group grants admin and what that group is named, but it will not hand you the group's member list from the Mac side. The members live in Active Directory, and the local directory node does not enumerate them. So part of this module is learning where local discovery ends and where you need a recovered account name plus an effective check to keep going.

Module setup

Run this from the browser terminal on your primary Mac. The EVIDENCE directory is created by this setup block under /private/tmp; it is your temporary workspace for this module, not a preloaded course file. The course uses /private/tmp because the domain shell may not have a normal writable home directory.

EVIDENCE="/private/tmp/MRTE-adminmap-evidence"
mkdir -p "$EVIDENCE"
date -u +%Y-%m-%dT%H:%M:%SZ > "$EVIDENCE/start-time.txt"

Workflow

Discover your own identity and groups

Before you touch the admin group, confirm who you are on this box. The simplest read needs no domain prefix and no assumptions. whoami prints the short name your session runs as, and bare id lists the groups the authorization system has already resolved for you.

Run from the browser terminal:

whoami | tee "$EVIDENCE/whoami.txt"
id | tee "$EVIDENCE/id-self.txt"

A healthy lab capture contains lines like these:

developer01
uid=1540535893(developer01) gid=1188867283(MACSECCORP\Domain Users) groups=1188867283(MACSECCORP\Domain Users),12(everyone),62(netaccounts),1193466532(MACSECCORP\Engineering),1564206403(MACSECCORP\MRTE-Mac-Users),701(com.apple.sharepoint.group.1),702(com.apple.sharepoint.group.2)

Strip the macOS noise, everyone, netaccounts, and the sharepoint groups, and three domain groups remain: Domain Users, Engineering, and MRTE-Mac-Users. This list is what the Mac uses for local authorization decisions, so it is the authoritative answer to what your account gets you on this host. Engineering membership matters downstream: it hints at share access in Module 2.8. MRTE-Mac-Users reads like a device-access group that gets you on the box, not over it. Note that developer01 does not carry 80(admin) anywhere in this line. That absence is your starting line for the engagement.

The large numeric uid and gid are values macOS derives from the domain SID, so they vary per domain and per user. They are useful as fingerprints in a report but not as fixed constants.

Read the local admin group directly and by nesting

Now ask the real question: who has admin here. The local admin group record has two fields that matter. GroupMembership lists direct members by name. NestedGroups lists groups pulled in by reference, and on an AD-bound Mac those references are domain groups. Operators who read only the first field miss the domain admin story.

Run from the browser terminal:

dscl . read /Groups/admin GroupMembership NestedGroups | tee "$EVIDENCE/admin-group.txt"

The important lines are:

GroupMembership: root labuser localadmin
NestedGroups: CDD4DF07-E6FE-4EFE-B43D-9F6675DFD224

The direct member list holds only local accounts: root plus two local workstation accounts, labuser and localadmin. No domain user appears there directly. The second line is the one careless operators skip. NestedGroups carries a GeneratedUID, not a name, and whoever that group contains is an admin on this Mac without ever appearing in the member list. Treat the GUID as host-specific. Re-read it on each host instead of reusing it, because the GUID can differ per machine even when the group name is the same.

Resolve the nested group to a name

A GUID is not useful in a report until it has a name. The GUID lives in the local node's record, but the group object itself lives in Active Directory, so the local node cannot name it. Resolve it through the directory search node, which consults both local and AD sources.

Run from the browser terminal:

dscl /Search search /Groups GeneratedUID CDD4DF07-E6FE-4EFE-B43D-9F6675DFD224 | tee "$EVIDENCE/nested-group.txt"

The decision-relevant lines:

MACSECCORP\MRTE-Mac-Admins		GeneratedUID = (
    "CDD4DF07-E6FE-4EFE-B43D-9F6675DFD224"
)

The nested group is the domain group MACSECCORP\MRTE-Mac-Admins. This is the standard enterprise pattern and the finding that matters for the report: control of that domain group is control of local admin on every Mac configured this way. One domain group compromise becomes fleet-wide local admin.

Boundary check: the Mac will not enumerate the nested group's members

The natural next question is who is inside MRTE-Mac-Admins. This is where local discovery ends, and it is worth confirming on purpose so the limit is in evidence, not assumed.

Run from the browser terminal:

dscl /Search -read /Groups/MACSECCORP\\MRTE-Mac-Admins GroupMembership 2>&1 | tee "$EVIDENCE/nested-members-attempt.txt"

Boundary check:

<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)

The Mac knows the group exists and knows it grants admin, but the AD plugin does not hand the membership roster to a directory read. The members live on the domain controller side. The operational consequence is direct: you cannot discover a specific admin account by enumerating this group from the Mac. You arrive at candidate admin account names from other sources, a recovered username, an account list from the lab portal, a name lifted from share metadata or a directory, and then you confirm whether each candidate is actually effective admin here. The confirmation tool is the effective-membership check in the next section.

Confirm an admin-context account by effective membership

The lab provides an admin-context account, itadmin01. You are not discovering it from the Mac; you are confirming what it is. Read its group list first. Group membership is directory data, not a secret, so any authenticated domain user can read another user's groups.

Run from the browser terminal:

id 'MACSECCORP\\itadmin01' | tee "$EVIDENCE/id-itadmin01.txt"

The important lines are:

uid=209973170(itadmin01) gid=1188867283(MACSECCORP\Domain Users) groups=1188867283(MACSECCORP\Domain Users),12(everyone),62(netaccounts),1564206403(MACSECCORP\MRTE-Mac-Users),1305796359(MACSECCORP\MRTE-Mac-Admins),1531016973(MACSECCORP\IT-Operations),... ,80(admin),98(_lpadmin),100(_lpoperator),204(_developer),...

Two reads here. First, itadmin01 carries MACSECCORP\MRTE-Mac-Admins and MACSECCORP\IT-Operations, groups developer01 does not have, and the naming tells you the intent. Second, 80(admin) appears in the resolved group list. That is the effective proof that itadmin01 lands in the local admin group at login, even though itadmin01 is not a direct member of /Groups/admin. The full list also includes service-access groups like _lpadmin and _developer, which means an admin login here inherits package and developer-tool rights as well.

Verify effective membership both ways

dsmemberutil answers the reverse question directly: is this specific user an admin. It accounts for nesting the same way the authorization system does, which is why it is the check to trust over the direct member list.

Run from the browser terminal:

dsmemberutil checkmembership -U itadmin01 -G admin | tee "$EVIDENCE/check-itadmin01.txt"
dsmemberutil checkmembership -U developer01 -G admin | tee "$EVIDENCE/check-developer01.txt"

The decision-relevant lines:

user is a member of the group
user is not a member of the group

The positive check validates the map end to end: itadmin01 is an effective admin here, through the nested domain group. The negative check establishes the starting line for the engagement: developer01 has no local admin, so any later module that produces root-owned evidence from developer01 crossed a real privilege boundary. Record both. Reports that skip the negative check leave reviewers unable to tell whether privilege was gained or assumed.

Reportable finding

The privilege map this module builds is short and drives the rest of the assessment. developer01: Domain Users, Engineering, MRTE-Mac-Users, no local admin. itadmin01: MRTE-Mac-Admins and IT-Operations, effective local admin through a nested domain group. Direct local admins: root plus two local workstation accounts. And the structural finding: local admin on this Mac is granted to the domain group MRTE-Mac-Admins through nesting, a pattern that almost certainly generalizes across the fleet. Every credential recovered later is priced by which row it lands in.

Evidence checklist

  • start-time.txt written before any command
  • whoami.txt and id-self.txt own identity and group memberships
  • admin-group.txt direct members and nested group GUID
  • nested-group.txt GUID resolved to MACSECCORP\MRTE-Mac-Admins through /Search
  • nested-members-attempt.txt the boundary check showing member enumeration is not possible
  • id-itadmin01.txt admin-context group memberships, including effective 80(admin)
  • check-itadmin01.txt positive effective-membership check
  • check-developer01.txt negative boundary check
  • corpedr-report.txt from the detection section below

Detection and artifact visibility

Run as developer01 on the monitored Mac to read the training sensor's view of the activity window:

/usr/local/macseccorp/bin/corpedr-report --since "$EVIDENCE/start-time.txt" | tee "$EVIDENCE/corpedr-report.txt"

The important lines for this module's window:

Host: <primary-host>
Since: <your start time, varies per run>

Alerts: 0
YARA-compatible matches: 0
File events: 0

Under tested lab conditions, directory reads through id, dscl, and dsmemberutil produced zero alerts, zero content matches, and zero file events. These are normal resolution queries that an AD-bound Mac runs at every login, so a handful of them looks identical to ordinary traffic. The honest report line is not that this technique escapes detection. It is that under tested conditions the training sensor recorded no alert for this activity. Group enumeration only becomes distinguishable at volume, and a careful operator reading a few records stays under that threshold.

The claim boundary printed by the tool applies in full. This says nothing about how a kernel-level commercial EDR would see the same activity, and the training sensor does not claim Endpoint Security blocking without an Apple entitlement. Quote the Host and Since lines when you cite the report.

Cleanup

This module changes no system state. The only artifact is the evidence directory. If the evidence was collected for practice and is not part of a report deliverable, remove it.

rm -rf /private/tmp/MRTE-adminmap-evidence

On a real engagement, evidence that feeds the report is the deliverable. Before you delete a captured artifact, confirm it is already copied to the engagement evidence store and the handoff is complete. That handoff step is covered in Module 1.5.

Field checklist

  • Start every admin-mapping pass from your own identity, whoami and bare id, before assuming any domain prefix or group.
  • Read both GroupMembership and NestedGroups on /Groups/admin for every Mac in scope. The nested group is where domain admin rights hide.
  • Resolve nested group GUIDs through the /Search node, per host. The local node cannot name them.
  • Accept that the Mac will not enumerate a nested domain group's members. Candidate admin names come from recovered accounts, not from local enumeration.
  • Trust effective checks, id showing 80(admin) and dsmemberutil checkmembership, over direct member lists.
  • Pair every positive admin finding with a negative boundary check on the account you started from, so reviewers can see privilege gained versus assumed.
  • Flag a nested domain group in local admin as a finding: one domain group compromise becomes fleet-wide local admin. Repeat the workflow on mrte-mac-02 before claiming the nesting holds fleet-wide.

Claim boundary

This module establishes the observed group memberships of developer01 and itadmin01 and the observed composition of the local admin group on mrte-mac-01 at collection time, including a nested domain group resolved live through the search node, and establishes that the nested group's membership roster is not enumerable from the Mac. It does not enumerate all domain users or groups, does not test what any group can do on the DC, and does not show that the same nesting exists on mrte-mac-02, which requires repeating the workflow there. Detection statements are scoped to the MacSecCorp training sensor in detect mode under tested conditions.

Continue macOS Advanced Evasion

This preview includes the written module. Lab downloads, later modules, progress tracking, and certification require course access.

Get full access