# Graph + Exchange REST Cheatsheet All examples assume: - `$GT` = Graph token (`investigator` tier) - `$EXO_R` = Exchange read token (`investigator-exo` tier) — Get-* cmdlets - `$EXO_W` = Exchange write token (`exchange-op` tier) — Set-*/Remove-* cmdlets - `$UT` = User Manager graph token (`user-manager` tier) — user write ops - `$TID` = tenant ID, `$UPN`/`$UID` = user identifiers Acquire tokens: ```bash GT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID investigator) EXO_R=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID investigator-exo) EXO_W=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID exchange-op) # remediation only UT=$(bash .claude/skills/remediation-tool/scripts/get-token.sh $TID user-manager) # remediation only ``` ## Graph API (`https://graph.microsoft.com/v1.0`) ### User lookup / status ```bash # By UPN curl -s -H "Authorization: Bearer $GT" \ "https://graph.microsoft.com/v1.0/users/$UPN?\$select=id,displayName,userPrincipalName,mail,accountEnabled,createdDateTime,lastPasswordChangeDateTime" # All users (filter, paged) curl -s -H "Authorization: Bearer $GT" \ "https://graph.microsoft.com/v1.0/users?\$top=999&\$filter=accountEnabled%20eq%20true" ``` ### Mailbox ```bash # Visible inbox rules (Graph v1.0 — does NOT return hidden rules) /users/$UPN/mailFolders/inbox/messageRules # Mailbox settings (auto-reply, delegates meeting option, NOT forwarding flags) /users/$UPN/mailboxSettings # Recent sent / deleted /users/$UPN/mailFolders/sentitems/messages?$top=25&$orderby=sentDateTime%20desc /users/$UPN/mailFolders/deleteditems/messages?$top=25&$orderby=receivedDateTime%20desc ``` ### Authentication methods ```bash /users/$UPN/authentication/methods # Watch for new methods added within the attack window ``` ### OAuth + app role assignments ```bash /users/$UPN/oauth2PermissionGrants # user-level consents /users/$UPN/appRoleAssignments # apps assigned to this user /servicePrincipals/$SP_ID/appRoleAssignments # what scopes a SP has ``` ### Sign-ins (needs Entra ID P1 or higher) ```bash # Interactive sign-ins v1.0 (does NOT include non-interactive/service-principal) /auditLogs/signIns?$filter=userId eq '$UID' and createdDateTime ge $FROM&$top=200 # All sign-in event types (beta endpoint) /beta/auditLogs/signIns?$filter=userId eq '$UID' and (signInEventTypes/any(t:t eq 'nonInteractiveUser')) # Foreign successful sign-ins tenant-wide /auditLogs/signIns?$filter=(status/errorCode eq 0) and (location/countryOrRegion ne 'US') ``` ### Directory audits ```bash # Changes targeting a specific user /auditLogs/directoryAudits?$filter=targetResources/any(t:t/id eq '$UID') # Tenant-wide consent / auth-method / role events /auditLogs/directoryAudits?$filter=activityDateTime ge $FROM # Then client-side filter by activityDisplayName ~ Consent|Authentication Method|Add service principal|Add member to role ``` ### Identity Protection (needs IdentityRiskyUser.Read.All) ```bash /identityProtection/riskyUsers /identityProtection/riskyUsers/$UID /identityProtection/riskDetections?$filter=userId eq '$UID' ``` ### B2B guests ```bash # Get guest by gmail/external address /users?$filter=startswith(userPrincipalName,'dunedolly21') # Invite audits /auditLogs/directoryAudits?$filter=activityDisplayName eq 'Invite external user' ``` ## Exchange Online REST (`https://outlook.office365.com/adminapi/beta/{tenant-id}/InvokeCommand`) POST with JSON body `{"CmdletInput":{"CmdletName":"","Parameters":{...}}}`. - **Read ops** (Get-*): use `$EXO_R` — Security Investigator token (`investigator-exo` tier) - **Write ops** (Set-*, Remove-*): use `$EXO_W` — Exchange Operator token (`exchange-op` tier) ### Inbox rules (INCLUDING hidden) ```json {"CmdletInput":{"CmdletName":"Get-InboxRule","Parameters":{"Mailbox":"user@domain.com","IncludeHidden":true}}} ``` Why this matters: attackers commonly create hidden rules that Graph v1.0 cannot see. ### Mailbox forwarding / properties ```json {"CmdletInput":{"CmdletName":"Get-Mailbox","Parameters":{"Identity":"user@domain.com"}}} ``` Check: `ForwardingAddress`, `ForwardingSmtpAddress`, `DeliverToMailboxAndForward`, `GrantSendOnBehalfTo`, `HiddenFromAddressListsEnabled`. ### Mailbox permissions (delegates / FullAccess) ```json {"CmdletInput":{"CmdletName":"Get-MailboxPermission","Parameters":{"Identity":"user@domain.com"}}} ``` Filter out `NT AUTHORITY\\SELF` — anything else is a delegate. ### SendAs permissions ```json {"CmdletInput":{"CmdletName":"Get-RecipientPermission","Parameters":{"Identity":"user@domain.com"}}} ``` ### Transport rules (tenant-wide mail flow) ```json {"CmdletInput":{"CmdletName":"Get-TransportRule","Parameters":{}}} ``` Check for rules that reroute, delete, or exfiltrate mail. ### SMTP AUTH ```json {"CmdletInput":{"CmdletName":"Get-CASMailbox","Parameters":{"Identity":"user@domain.com"}}} ``` Check `SmtpClientAuthenticationDisabled`. To disable SMTP AUTH on a single mailbox (remediation): `Set-CASMailbox -SmtpClientAuthenticationDisabled $true`. ## Rate limits / pagination - Graph signIns endpoints cap `$top` at 999. For >999 results, follow `@odata.nextLink`. - Exchange REST has undocumented throttling — if you hit 429, back off 30–60s. - Token is valid ~60 minutes. Script caches for 55 min.