5.8 KiB
ACG mailbox security investigation + hardening (mike@)
Date: 2026-07-16 (UTC)
User
- User: Mike Swanson (mike)
- Machine: GURU-5070
- Role: admin
Trigger
Mike's Outlook "view" kept resetting — suspected someone else was changing his mailbox
settings. Requested a full investigation of mike@azcomputerguru.com.
Outcome (TL;DR)
- No compromise, no delegates. Nobody else has access to the mailbox. The changing view is Mike's own many clients (New Outlook vs classic, OWA, Apple Mail, mobile, 3 AAD-joined PCs) fighting over roaming view settings — not another person.
- Real finding: an active distributed password-spray attack on the account — all failing (smart-lockout + enforced MFA + legacy-auth block). No successful foreign sign-in.
- Hardened: pruned stale credentials, made a true break-glass account, added sign-in alerting.
- Also fixed a recurring fleet-wide friction: Exchange REST must use the
exchange-optier (docs + breach-check script corrected).
Investigation findings (tenant ce61461e-81a0-4c84-bb4a-7b354a9a356d)
- Sign-ins (30d, interactive): 200 records, all failures —
50053smart-lockout (×195),50126bad password (×5) — from DE (52), US-spoofed (108), CN, KR, IN, RU, BR, etc. Only 6 successful sign-ins, all US (Phoenix/SLC), legitimate apps. Password spray, contained. - Delegates: FullAccess = SELF only; SendAs = SELF only; SendOnBehalf = empty; forwarding = none. No one else can access the mailbox.
- OAuth grants (4): all legit — Apple Internet Accounts (iPhone/Mac Mail, holds
EWS.AccessAsUser.All), Alignable (Contacts.Read), ZeroTier, Tailscale. No rogue app. - Inbox rules (46, all enabled): Mike's own routing (forwarding vendor/notification mail to
howard@/rob@/wwilliams@/webmaster@/info@ + client seastman@glaztech.com). No malicious catch-all.
Two external-Gmail forwards to confirm as intentional:
energybilling@tesla.com -> ourfamily7479@gmail.com,vailschools.org -> ursulaclark76@gmail.com. - MFA/CA: "Require MFA for all users" = enabled/All; "Block legacy authentication" = enabled/All. Posture is sound — spray can't convert a lucky guess.
- Auth methods: Authenticator ×2 (Samsung), phone +1 520-289-1912, SSPR email superguru@gmail.com, 21 Windows Hello keys (18 stale/unnamed). (Confirm phone + superguru@ are Mike's.)
Actions taken
- Pruned 20 stale Windows Hello keys on mike@ (18 orphaned 2020–2025 + ACG-TECH-02L + ACG-YOGA,
both retired per Mike). Only
GURU-5070(2026-04-22) remains. Other methods untouched. (user-manager tier; 20× HTTP 204.) - Break-glass exclusions — excluded
sysadmin@azcomputerguru.onmicrosoft.com(8c363d17-02ed-45e1-97d0-49e5819e6ff6, cloud-only GA) from BOTH MFA CA policies so an MFA outage can't lock it out, while every real user stays gated:- "Require multifactor authentication for all users" (
1e912cc2-334a-4f12-9d81-a95dc3822b8e) - "Require multifactor authentication for admins" (
5f10aa47-e796-40db-89eb-fdae60dea0c7, 14 roles preserved) (tenant-admin tier; both HTTP 204, verified.)
- "Require multifactor authentication for all users" (
- Break-glass sign-in alerting — LIVE. New script
.claude/skills/remediation-tool/scripts/breakglass-signin-alert.shposts[SECURITY]to #dev-alerts on ANY sysadmin@ sign-in (state-tracked, seeded 2026-07-17T04:23Z, no history alerted). Scheduled taskACG-BreakGlass-SignIn-Alerton GURU-5070, every 4h, test-run OK (result=0). Baseline: sysadmin@ has zero sign-ins in 2026 (break-glass untouched).
Blocked / pending (needs Mike)
- Rotate the sysadmin@ break-glass password — DO IN PORTAL. Graph reset 403'd
("Insufficient privileges") by design: resetting a Global Admin's password needs Privileged
Authentication Administrator, which our apps deliberately don't hold (granting it would be worse
than the gap). Mike (a GA) resets it in Entra: Users -> sysadmin@ -> Reset password; set a long
(20+ char) password,
force change = off. Then vault it (intended pathmsp-tools/acg-m365-breakglass.sops.yaml) — hand me the password and I'll store it. - Confirm phone
+1 520-289-1912and SSPR emailsuperguru@gmail.comare Mike's. - Confirm the two external-Gmail forward rules are intentional.
Recommendations (open)
- Alert home: the task runs on GURU-5070 only while it's on — move to an always-on fleet host for robust monitoring.
- Consider a 2nd break-glass account (Microsoft-recommended pair).
- No break-glass exclusion existed on ANY CA policy before today; now sysadmin@ is the designated one.
Fleet fix — Exchange REST tier (recurring friction, 6th time)
Root-caused the perennial Exchange 401: the Security Investigator app (investigator-exo) has the
Exchange Admin role but NOT the Exchange.ManageAsApp app-permission the adminapi requires, so it
401s on every Exchange cmdlet. Only the Exchange Operator app (exchange-op) has both →
all Exchange REST (read + write) goes through exchange-op. Corrected:
SKILL.md(tier table, breach-check default, "before calling" note — which had told us to assign the role to the wrong SP)references/gotchas.md(role table + authoritative callout + 401-vs-403 rule + SP->role map)scripts/user-breach-check.sh(was mintinginvestigator-exo-> mailbox delegate/forwarding checks silently returned empty on every run; nowexchange-op)- memory
reference_exchange_online_exchangeop_tier+ MEMORY.md index; errorlog--correction
Other client threads this session (separate contexts)
Khalsa Montessori RMM onboarding (17 Windows enrolled via SC, client LOWER-TIGER-5022);
BirthBiologic SharePoint permissions audit -> Syncro #32187 comment; Reliant Well Drilling Google
Business Profile (none exists — GBP creation outlined). Logged/tracked in their own places.