Background
Why are we so excited about the Microsoft Graph Activity log that is now in public preview? For cloud incident responders it has been a problem that calls made using the Graph API were only partially audited which causes blind spots for detection and incident response purposes as illustrated by the below incidents.
Notable incidents
There were some notable incidents in which Entra ID (formerly Azure Active Directory) applications were abused by APTs:
- Storm-0558, for which you can find a write-up here, was a large-scale attack. A notable aspect of this attack was the attackers making API calls to read emails. The discovery of this activity was most likely through E5 logging. However, if these actions had been carried out through Entra ID apps and Graph API calls, proper logging might have provided an opportunity to detect this earlier.
- Solarwinds had a significant incident, extensively covered in a write-up by Wired. It involved a attack, allegedly by a nation-state actor, where Entra ID applications were leveraged to access and acquire sensitive data.
Based on publicly available reports, it’s evident that applications were misused during these incidents. One of the challenges was the lack of proper logging for most read operations performed through the Graph API. So let’s dive into this new log!
The Graph
The Graph API can be used to programmatically interact with all Microsoft 365 services. For example reading, writing or deleting data in the Microsoft 365 umbrella. Naturally this is something that’s incredibly powerful and abused by threat actors as demonstrated by the aforementioned incidents.
The Logs
Microsoft Graph activity logs are an audit trail of all HTTP requests that the Microsoft Graph service received and processed for a tenant.
The official documentation for this new log source is available here. The most important prerequisite is that you need a P1 or P2 Entra ID license to access this log.
Acquiring the logs
The log can be enabled through the Entra ID Diagnostic Settings where you have the following configuration options:
These options are pretty self-explanatory and the same for all the logs being made available through Diagnostic Settings. From an incident response perspective ideally the logs are available in Log Analytics so you can perform live querying against the data. If that’s too expensive archiving to a storage account is a good second option where you can grab the logs after the fact and load them in your favourite log management solution to investigate.
We will also update our Microsoft-Extractor-Suite to support acquisition of this log.
Analysing the logs
In this blog we will analyse the logs in a Log Analytics Workspace. The logs are stored within the MicrosoftGraphActivityLogstable in Log Analytics.
Here are some fields that we think are useful for incident detection and response:
Now let’s talk about the good stuff, here are some IR tasks you can perform using this log in combination with other Microsoft Entra ID artefacts.
Example 1 — Identify which applications are making requests
Imagine a scenario where we’re interested in figuring out which applications are making Graph API calls. To perform this task:
- Search for all applications making requests using a basic KQL search:
MicrosoftGraphActivityLogs|summarize NumberOfRequests=count() by AppId
- Lookup AppID value in Entra ID to identify Application Name:
Based on this we know there’s a SP called Azure Security Insights which is responsible for making these requests.
- An extra validation is searching open sources for the AppID since they’re globally unique, we’ve created a GitHub repository which can be useful.
In Example 3 we will show you how to use lookup in KQL to filter out Microsoft App entries.
Example 2 — Identify large data transfers/exfiltration
One thing that’s always relevant in an incident response case is, was data accessed or exfiltrated. Using the ResponseSizeBytes and a bit of field manipulation we can make a nice overview of data transferred in the last 7 days using Graph API calls by Entra ID applications.
MicrosoftGraphActivityLogs
| where TimeGenerated > ago(7d)
|summarize NumberOfBytes=sum(ResponseSizeBytes) by AppId
| sort by NumberOfBytes
| extend HumanReadable=format_bytes(NumberOfBytes,2)
Example 3 — Identify calls made by non-Microsoft applications
One of the first things we noticed with this log is the huge amount of Graph API calls that are recorded which is largely due to (internal) Microsoft Entra ID apps. To filter through the noise it might be useful to filter only on request made by non-Microsoft applications. One way of doing that is simply removing all calls made based on App IDs belonging to default applications. To perform this task:
- Go to Entra ID and select ‘Enterprise Applications’
- Under the Application type filter select ‘All Applications’
- Now you should have over 500 applications, you’re number might be different
- Export the list by clicking on ‘Download (Export)’, or just use our export
- Run a KQL query that loads the external csv and filters out all calls made by Microsoft apps.
let applist=externaldata(id:string,displayName:string,appId:string,applicationType:string)[@”https://raw.githubusercontent.com/invictus-ir/entra-apps/main/applist.csv"]
with (format=”csv”, ignoreFirstRecord=True);
let app_lookup=applist
|project id,displayName,applicationType;
MicrosoftGraphActivityLogs
| extend ObjectId = iff(isempty(UserId), ServicePrincipalId, UserId)
| extend ObjectType = iff(isempty(UserId), “ServicePrincipalId”, “UserId”)
| where ObjectId !in (app_lookup)
|project TimeGenerated,ObjectId,ObjectType,RequestMethod,ResponseStatusCode,IpAddress,RequestUri,ResponseSizeBytes,AppId,UserAgent,Scopes,Roles
This search also creates two new field the ObjectId and ObjectType based on if the call was made by a Service Principal or a User. Thanks to Fabian Bader from Cloudbrothers on his excellent blog.
Conclusion
We’re glad that this log is available as incident responders we always want more logs to help us paint a more complete picture of an incident. This log definitely helps with that, however we wouldn’t be Dutch if we didn’t have something to complain. The following characteristics make this log less useful:
Data volume, this log is very noise as it logs all requests made by all Entra ID apps. At the moment of writing there are more than 550 apps in each Entra ID tenant.
Correlation, you’ll have to combine multiple log sources to fully leverage the value of this new log. For example, a Graph API request to create a new user needs to be correlated with the Entra ID Audit Log to verify its occurrence and gather associated user details.
Limited data reduction options , we suspect that most organizations will not blindly enable the log due to the sheer size and costs you’ll incur in either your Log Analytics or Storage Accounts. It would be good to have some data reduction options before turning this on at the moment it’s only on/off for everything.
Overall verdict, we love logging and we can’t wait to use this log in our incident response engagements. We can’t wait for this log to become generally available. Be on the lookout for more blogs on this log we think there’s lots more to discover.