System

User Accounts & Authentication

Overview

macOS stores user account information in directory service plists, login/logout events in system logs, and remote access configuration in SSH directories. User account forensics establishes who had accounts on the system, when they logged in and out, what privilege escalation occurred, and what remote access was configured.

Forensic Significance

Evidence TypeForensic Value
User accountsAll local accounts, UIDs, shells, home directories
Login/logout historyUser session timeline
Last logged-in userMost recent interactive user
Sudo historyPrivilege escalation events
SSH authorized keysWho can remotely access this system
SSH known hostsRemote systems the user has connected to
Login itemsApplications that auto-start at login

File Locations

ArtifactPathFormat
User account plists/var/db/dslocal/nodes/Default/users/*.plistPlist
Last logged-in user/Library/Preferences/com.apple.loginwindow.plistPlist
Login items (user)~/Library/Preferences/com.apple.loginitems.plistPlist
Login items (modern)~/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btmBinary
Authorization database/var/db/auth.dbSQLite
SSH authorized keys~/.ssh/authorized_keysText
SSH known hosts~/.ssh/known_hostsText
SSH config~/.ssh/configText
SSH private keys~/.ssh/id_*PEM / OpenSSH
Sudo logUnified Logs / /var/log/system.logLog

Key Data

User Account Plists

Each file in /private/var/db/dslocal/nodes/Default/users/ (/var is a symlink to /private/var) represents one account in the local directory node, named <name>.plist. Files are binary property lists, readable only by root. Following the Open Directory convention, every top-level value is stored as an array, even single values such as uid (for example uid is ["501"], a string inside an array). The directory contains system and service accounts (such as _www and root) as well as human accounts.

KeyDescription
nameRecord names: the short (login) name first, followed by any aliases (for example an email-style alias)
realnameFull display name
uidNumeric user ID (stored as a string)
gidPrimary group ID (stored as a string)
generateduidDirectory services GUID for the account
homeHome directory path
shellLogin shell. /usr/bin/false or /sbin/nologin disable interactive login
IsHiddenSet to 1 if the account is hidden from the login window
authentication_authorityAuthentication mechanisms for the account, as ;-delimited markers (see below)
accountPolicyDataEmbedded binary plist holding timestamps and failed-login counters (see below)
ShadowHashDataPassword hash data (salted PBKDF2-SHA512). Sensitive, see below
KerberosKeys, HeimdalSRPKeyKey material. Sensitive, see below
hintPassword hint typed by the user. Sensitive, see below

authentication_authority markers

The authentication_authority array contains strings whose markers describe how the account authenticates:

MarkerMeaning
;ShadowHash;The account has a local password hash (stored in ShadowHashData)
;SecureToken;The account holds a Secure Token and can unlock FileVault and authorize disk-encryption operations
;Kerberosv5;The account has a local Kerberos principal

accountPolicyData

accountPolicyData is a data value containing a second, embedded property list. Its timestamps are Unix epoch seconds (fractional), not Core Data time.

KeyDescription
creationTimeWhen the account was created
passwordLastSetTimeWhen the password was last changed
failedLoginCountFailed logins since the last successful login
failedLoginTimestampWhen the most recent failed login occurred

Sensitive keys

The same plist contains credential material: ShadowHashData (password hash), KerberosKeys, HeimdalSRPKey, and the password hint. Treat a raw copy of an account plist as sensitive. The macfor collector deliberately never copies these keys (see macfor Collection below).

Login Window Preferences

defaults read /Library/Preferences/com.apple.loginwindow
KeyDescription
lastUserNameMost recent user to log in
autoLoginUserAuto-login user (if configured)
SHOWFULLNAMEWhether login shows name field vs. user list

SSH Known Hosts

Each line represents a remote server the user has connected to via SSH:

server.example.com,192.168.1.100 ssh-ed25519 AAAAC3Nza...

This reveals remote systems the user has accessed, including internal servers, cloud instances, and development environments.

SSH Authorized Keys

Each line represents a public key that can log in as this user:

ssh-ed25519 AAAAC3Nza... user@remote-machine

The comment field (e.g., user@remote-machine) indicates where the key was generated.

Key Fields for Analysis

Enumerate All Users

# List all local user accounts
dscl . -list /Users UniqueID | sort -k2 -n

# Read specific user details
dscl . -read /Users/username

# Check for hidden users
dscl . -list /Users IsHidden | grep 1

Inspecting the Account Plists Directly

Work on a copy of the plist, as root, and handle it as sensitive material.

# Dump an account plist as XML (shows array-wrapped values and the markers)
sudo plutil -p /private/var/db/dslocal/nodes/Default/users/alice.plist

# Extract the embedded accountPolicyData plist and read it
sudo plutil -extract accountPolicyData raw -o - \
  /private/var/db/dslocal/nodes/Default/users/alice.plist \
  | base64 -d > /tmp/alice-policy.plist
plutil -p /tmp/alice-policy.plist

# Convert a Unix epoch value (for example creationTime) to UTC
date -u -r 1700000000

plutil -p prints the policy values as seconds since 1970; convert them as shown. On a live system dscl . -read /Users/alice returns the same directory data without root, including accountPolicyData and AuthenticationAuthority; the password itself is masked (Password: ********) and ShadowHashData is not shown. Password hashes are not needed for account triage; avoid extracting ShadowHashData unless the case requires it and you are authorized to.

Login History (Unified Logs)

# Login events
log show --predicate 'eventMessage contains "loginwindow" AND eventMessage contains "login"' --last 30d

# Sudo events
log show --predicate 'process == "sudo"' --last 30d

# SSH authentication
log show --predicate 'process == "sshd"' --last 30d

Analysis Notes

  • Hidden accounts: Accounts with IsHidden = 1 do not appear on the login screen. These could be legitimate service accounts or unauthorized hidden accounts.
  • UID analysis: UIDs below 500 are typically system accounts. Regular user accounts start at 501. An account with a low UID that is not a known system account is suspicious, as is a low-UID account with an interactive shell and a password hash (;ShadowHash;), a common shape for a backdoor account.
  • Account creation timeline: creationTime in accountPolicyData dates each account. Compare it against the incident window to spot accounts created shortly before or during suspicious activity, and use passwordLastSetTime to spot unexpected password resets.
  • Brute-force indicators: A high failedLoginCount with a recent failedLoginTimestamp suggests password guessing against the account. The counter resets on a successful login, so a low count does not rule out earlier attempts. Correlate with Unified Logs.
  • FileVault-capable accounts: Accounts with ;SecureToken; can unlock FileVault. An unexpected Secure Token holder is an account that could decrypt the disk.
  • SSH key analysis: Review authorized_keys for unexpected entries. Check private keys for keys without passphrases (files with no encrypted header).
  • sudo audit: Sudo events in Unified Logs show which users escalated to root and what commands they ran.
  • Login items: Applications in login items auto-start when the user logs in, making this a persistence mechanism. Cross-reference with Launch Agents.

Version Differences

macOS VersionChanges
10.15 (Catalina)Full Disk Access required to read user plists
13 (Ventura)Login Items managed via System Settings; backgrounditems.btm format
14 (Sonoma)Enhanced login item management

macfor Collection

The system.users plugin (User Accounts, Community, also included in the Pro binary) collects local account metadata from the dslocal user plists. It targets macOS 10.12+.

SettingValue
Plugin IDsystem.users
Artifactlocal_accounts
Source/private/var/db/dslocal/nodes/Default/users/*.plist
OutputParsed records only (records/system.users/parsed.jsonl and/or user_accounts.parquet); no raw files
Record typeuser_account
Requires rootYes on a live system

On a live Mac the directory is readable only by root. Without root the plugin is reported as not applicable (reason: "local account plists are not readable (requires root)"), visible in --dry-run. It works without root against a mounted image or --from tree.

The plugin records all accounts in the node, including system and service accounts, which is what makes low-UID and hidden accounts visible. A plist that fails to parse is recorded as an error and the remaining accounts are still collected.

# Live system
sudo macfor collect --plugin system.users --output ./evidence.zip

# Mounted image or directory tree
macfor collect --from /Volumes/Image --plugin system.users --output ./evidence.zip

Metadata Only

The plugin decodes an allowlist of keys and never copies the raw plist, because it contains credential material: ShadowHashData (password hash), KerberosKeys, HeimdalSRPKey, and the password hint. None of these, nor passwd or jpegphoto, reach the evidence container. For authentication_authority, only the presence of the ;ShadowHash; and ;SecureToken; markers is recorded. The SHA-256 of each source plist is stored so an analyst can verify a record against the original file.

Record Fields

FieldTypeDescription
typestringAlways "user_account"
usernamestringShort (login) name, the first name value
aliases[]stringAdditional record names; omitted if none
real_namestringFull name
uid, gidintOmitted if missing or non-numeric
generated_uidstringDirectory services GUID
home_dir, shellstringHome directory and login shell
hiddenboolIsHidden is set
service_accountboolUID below 500, missing UID, or a non-interactive shell (/usr/bin/false, /bin/false, /sbin/nologin, /usr/sbin/nologin)
has_passwordboolauthentication_authority contains ;ShadowHash; (presence only)
secure_tokenboolauthentication_authority contains ;SecureToken;
creation_timetimeFrom accountPolicyData.creationTime, RFC 3339 UTC
password_last_set_timetimeFrom accountPolicyData.passwordLastSetTime, RFC 3339 UTC
failed_login_countintFrom accountPolicyData.failedLoginCount
failed_login_timetimeFrom accountPolicyData.failedLoginTimestamp, RFC 3339 UTC
source_filestringPath to the account plist
source_sha256stringSHA-256 of the account plist

Sample record:

{"type":"user_account","username":"alice","aliases":["alice@example.com"],"real_name":"Alice Smith","uid":501,"gid":20,"generated_uid":"4A1B2C3D-0000-1111-2222-333344445555","home_dir":"/Users/alice","shell":"/bin/zsh","hidden":false,"service_account":false,"has_password":true,"secure_token":true,"creation_time":"2023-11-14T22:13:20.5Z","password_last_set_time":"2023-11-16T02:00:00Z","failed_login_count":3,"failed_login_time":"2023-11-17T05:46:40Z","source_file":"/private/var/db/dslocal/nodes/Default/users/alice.plist","source_sha256":"eec7..."}

Manifest source.users

Separately from the plugin, every collection writes the accounts the source enumerated into manifest.json under source.users, regardless of root and of --plugin / --category filters. Each entry has username, uid, gid, and (omitted when empty) name, home_dir, and shell. A null value means enumeration failed; [] means no accounts. statistics.users_processed equals the length of the list.

"source": {
  "type": "live",
  "users": [
    { "username": "alice", "uid": 501, "gid": 20, "name": "Alice Smith", "home_dir": "/Users/alice", "shell": "/bin/zsh" }
  ]
}

On a live system the list is built from the dslocal plists (root), falling back to dscl . -readall /Users and then /etc/passwd, keeping only human accounts (UID 500 or above, interactive shell, excluding nobody). For a directory source (--from), it is every folder under Users/ except Shared and hidden folders, enriched from the matching dslocal plist when present. This manifest list drives per-user collection by other plugins; it is not a substitute for the full system.users records.

Not Collected

Apple ID linkage (LinkedIdentity), group membership and admin status, login/logout history, deleted accounts, and network or directory (Active Directory / LDAP) accounts are not collected by this plugin. Use the Unified Log, last, and group data from dscl for those.

Tool Support

ToolSupport
macforsystem.users parses dslocal account plists into metadata-only user_account records (requires root on a live system); the manifest source.users lists enumerated accounts (Community)
plutil (macOS built-in)Read binary plists and extract accountPolicyData
dscl (macOS built-in)Directory service command-line tool
log (macOS built-in)Query login/sudo events
last (macOS built-in)Show login history

References