the finer things of cyber

Export AAD Identities for Detections/Incident Response

What are AAD Identities?

Azure Active Directory (AAD) is the identity provider for Microsoft cloud services (M365/Azure). Examples of these identities include:

  • Users — simple enough. Often synced from on-prem Active Directory.
  • Groups — simple enough. Often synced from on-prem Active Directory.
  • Devices — devices that are registered or joined to AAD. From a corporate laptop to an employee’s BYOD phone.
  • Application Registrations — a custom application created in the tenant (e.g. an app the company sells to customers). Also often used for service accounts (e.g. unattended scripts that need to access protected resources)
  • Service Principals — an instance of an application registration in a tenant. These are created automatically when an application registration is created. Very confusing!
  • Managed Identities — a form of Service Principal that is managed by Microsoft and associated with an Azure resource (e.g. Logic App or a Data Factory)

All of these identities access resources, have authentication methods, and are used to link activity across the Microsoft tools. But what is their value in detection/IR?

Why export identities?

Physical asset information is extremely important for cybersecurity. Most enterprises have an asset tracking systems and leverage that asset data in their SIEMs.

It is, however, less common to have the same level of asset information for enterprise identities. This is unfortunate, because identities are now the key to resource access (#ZeroTrust 🤮), so accurate identity information at your fingertips is extremely important. Imagine the below detection:

Scenario: Alert on sign-ins from Global Administrators in another country.

AAD Sign-in logs do not include role information for the user. Therefore, this detection is not possible with one data source. So, what options do we have? I’ll use Sentinel terms here, but the options are the same for most modern SIEMs.

Logic App
You could generate an alert for any foreign AAD sign-ins and then have a Logic App query MS Graph to determine if the user is a Global Admin. However, this will not scale if you have many foreign users or don’t like cluttering your incident panels.

Watchlist
You could generate a Sentinel watchlist for Global Admin users. However, that will be static. To update it automatically you could create a Logic App to pull the information from MS Graph. However, this will also not scale to other roles or identity attributes.

What if we could just save all our identity information in a table? 🤔

Options to Export Identities

Let’s review the options I’m aware of:

  • Microsoft Graph Data Connect — this is focused on M365 data and is meant for business applications, not security. It does not support all fields (authMethods) or identity types (Application Registrations, SPs, Roles)…It is a good option for general bulk export, though.
  • DeviceInfo — a good start! But it only includes devices seen by Defender! So it is not a source of AAD devices. Confusing!
  • IdentityInfo — a good table in Defender, but in Sentinel it requires UEBA. Confusing!
  • AuditLogs — these are events that are happening to identities; they are not the current state of identities. If a Global Admin wasn’t added in the last 90 days, that doesn’t help us.

So none of these options work! That’s why I made one.

Export-AAD.ps1

This PowerShell script (GitHub) can export all: Users, Devices, Groups, Application Registrations, and Service Principals (Enterprise Applications) from your AAD tenant. The files are saved in JSON files ready for you to send to a SIEM.

It is meant to be run on a schedule and the TimeGenerated column reflects the time of the run. This helps you find the latest date in your SIEM (see query below).

You can configure whatever properties you want added, as long as Graph supports them. See the README for a list of the defaults.

It supports batch queries (where 20 requests are in 1 HTTP request) and some parallelism.

The execution speed is limited by Graph API throttling and the script will sleep for a few seconds to be nice.

Now we can create our query. This will show all sign-ins from all Global Administrators (as of the last inventory table update).

let MostRecentRun = toscalar(AADUsers_CL | summarize max(TimeGenerated));
AADUsers_CL
| where TimeGenerated == MostRecentRun
| where MemberOf has "graph.directoryRole"
| mv-expand MemberOf
| extend Role = MemberOf.displayName
| where Role == "Global Administrator"
| join SigninLogs on $left.ObjectId == $right.UserId

Please let me know if you try it and like it or run into problems with a GitHub issue!