Hunting Through APIs

In today’s blog, we’re diving into the world of hunting through APIs. In the blog, the advantages, limitations, and scopes of the Graph API, Azure Monitor API, and Defender ATP API are discussed. For all of these solutions, a ready-to-use PowerShell script is shared.

These APIs can enhance security operations, automate threat detection, and enable bigger automation potential. In this blog the following topics are discussed:

The next blog explains how these APIs can be used in Logic Apps, so stay tuned for the next one!

Available Data

When working with the different APIs, understanding the scope and available tables of each API is crucial for planning your API strategy. The summary of the available data is shown in the table below.

APIDefender XDR DataSentinel Data
Azure Monitor API    ❌  ✅  
Graph API    ✅  ✅  
Defender ATP API  ⚠️ - MDE Data only  ❌  

Azure Monitor API: This API enables querying data within the Log Analytics Workspace and therefore the data you ingest into Sentinel. The actual data returned is dependent on the configuration of your Sentinel connectors. The Azure Monitor API can list and visualize the results.

Graph API: The Graph API can query both Defender XDR and Sentinel tables. When Unified XDR is enabled, it offers a unified querying experience across these platforms. If Unified XDR is not enabled, you can still query all Defender XDR tables.

Defender ATP API: This API is specifically designed to query tables within Defender for Endpoint. It can return both the Devices and Defender Vulnerability Management tables, this limits the capabilities of the API as the scope is very limited.

Defender ATP API: It is not recommended to use the Defender ATP Advanced Hunting API, as it is an older version with limited capabilities (More info available in the documentation). This biggest limitation is that the Defender ATP API is limited to Defender For Endpoint data only, the Graph API is more futureproof as it supports hunting across Unified XDR.

Table Support

Diving into the more specific comparison the differences in scope become very clear. Knowing these results the best practice is to use the Graph API as it has support for both Defender XDR and Sentinel data. If you have not migrated to Unified XDR yet, a combination of Graph API and Azure Monitor API is recommended, this gives full query coverage for the tables in both solutions.

Table CategoryAzure Monitor API Graph API  Graph API (Without Unified XDR)Defender ATP API
Alerts & behaviors❌  ✅  ✅  
Apps & identities❌  ✅  ✅  
Email & collaboration✅  ✅  
Devices❌  ✅  ✅  
Defender Vulnerability Management✅  ✅  
Email & collaboration✅  ✅  
Cloud Infrastructure✅  ✅  
Sentinel - Connector Data✅  ❌  
Sentinel - Custom Logs✅  ❌  

Permissions

For each API a different permission is needed, these permissions should be assigned to Service Principals or Managed Identities. The permissions required for each API are listed below. Logic App permissions should not be assigned to personal accounts as this has multiple disadvantages.

APIApplication PermissionAdmin Consent
Azure Monitor API  Log Analytics API  Data.ReadRequired
Graph API    GraphThreatHunting.Read.AlRequired
Defender ATP API  WindowsDefenderATP  AdvancedQuery.Read.AllRequired

Azure Monitor API

Configuration steps:

  1. Create App Registrations
  2. API Permissions -> Add Permissions -> APIs my organization uses
  3. Log Analytics API -> Application Permissions -> Data.Read
  4. Grant admin consent

/images/LogicAppKQL/AzureMonitor.png
Azure Monitor API Permissions

Graph API

Configuration steps:

  1. Create App Registrations
  2. API Permissions -> Add Permissions -> Microsoft Graph
  3. Application Permissions -> ThreatHunting -> ThreatHunting.Read.All
  4. Grant admin consent

/images/LogicAppKQL/Graph.png
Graph API Permissions

Defender ATP API

Configuration steps:

  1. Create App Registrations
  2. API Permissions -> Add Permissions -> APIs my organization uses
  3. WindowsDefenderATP -> Application Permissions -> AdvancedQuery -> AdvancedQuery.Read.All
  4. Grant admin consent

/images/LogicAppKQL/WindowsATP.png
Defender ATP Permissions

API Limitations

Next up the API limitations, be aware of these limitations before deciding on an API strategy. The limitations again highlight one of the limitations of the Defender ATP API, the query timeframes are limited to the 30 days of the MDE tables, whereas the Azure Monitor and Graph API can return the results based on the retention settings of the tables.

Both the Query Execution Time and Total Execution Time highlight the importance of query optimization. Using KQL best practices will allow you to execute more calls, as the total CPU time needed per query will be limited. The query resources used for the APIs can be found in the query resources report in Defender XDR.

DescriptionAzure Monitor API Graph API  Defender ATP API
TimeframeTable retention  Table retention30 Days
Max Rows500.000  100.000100.000
Calls200 requests per 30 seconds per Microsoft Entra user or client IP address.  You can make up to at least 45 calls per minute per tenant. The number of calls varies per tenant based on its size.45 calls per minute, and up to 1,500 calls per hour.
Query Execution Time (Seconds)600  180200  
Total Execution TimeNot specified15-minute CPU cycles10 minutes of running time every hour and 3 hours of running time a day.

High query volumes: In case you work with very high query volumes it is recommended to use the Graph API for Defender XDR data only, the Azure Monitor API should in that case only be used to query your Sentinel data. This approach can double the amount of executed requests per minute.

Service limits documentation:

Graph API Benchmark

The Graph API call limitations are dependable on the size of your tenant, with 45 calls per minute being a low number this had to be put to the test. The tenant size that was used for this benchmark is small (20 users). The results are shown in the visual below, in this small test tenant it is possible to constantly perform more than 45 calls per minute. These results show the potential to call the API 100s times per minute for large tenants.

/images/LogicAppKQL/GraphBenchmark.png
Graph API Benchmark Results

Benchmark Graph API

You can benchmark the Graph API Limit yourself by running the query below.

let BinSize = 1m;
MicrosoftGraphActivityLogs
| where TimeGenerated > ago(1h)
| where RequestUri has "runHuntingQuery"
| where ResponseStatusCode == 200
| summarize count() by bin(TimeGenerated, BinSize)
| render columnchart with(title="SuccessFul Queries Executed per Minute")

Hunting Through PowerShell

We know which data can be queried through each API and what permissions are required it becomes time to execute queries. The PowerShell scripts below run a KQL query that is executed over the corresponding API. Before you can execute the PowerShell scripts an App Registration must be completed in Azure, with the permissions as mentioned above. Once the App Registration is completed Admin consent is needed before the permissions can be used. The example script uses a secret for easy use, this secret needs to be added to the application to make the API connections work.

Azure Monitor API
# Set Service Principal Variables
$TenantID = "<TentantID>"
$AppID = "<AppID>"
$Secret = ConvertTo-SecureString "<Secret>" -AsPlainText -Force #Certificate Authentication is recommended.
# Set LA Workspace Variable
$LogAnalyticsWorkspaceID = "205af896-4c2c-4158-9d70-700d13670ae7"

$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppID, $Secret
Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $Credential

# Set the query you want to execute
$KQL = 'AuditLogs | sample 100'

$Results = Invoke-AzOperationalInsightsQuery -WorkspaceId $LogAnalyticsWorkspaceID -Query $KQL -ErrorAction Stop | select -ExpandProperty Results

$Results | Format-Table
Graph API
# Set Service Principal Variables
$TenantID = "<TentantID>"
$AppID = "<AppID>"
$Secret = "<Secret>" #Certificate Authentication is recommended.
$SecureClientSecret = ConvertTo-SecureString -String $Secret -AsPlainText -Force
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppID, $SecureClientSecret


# Hunting query to execute
$KQL = 'DeviceEvents | where ActionType startswith "asr" | project Timestamp, DeviceName, ActionType | take 50'

Import-Module Microsoft.Graph.Security

Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential -NoWelcome

$params = @{
    Query = $KQL
    Timespan = "P180D"
}

$Results = Start-MgSecurityHuntingQuery -BodyParameter $params

$Results.Results

$Results.Results | ForEach-Object {
 [PSCustomObject]@{
        Timestamp = $_.AdditionalProperties["Timestamp"]
        DeviceName = $_.AdditionalProperties["DeviceName"]
        ActionType = $_.AdditionalProperties["ActionType"]
        # Add other properties as needed
 }
} | Format-Table -AutoSize
Defender ATP API
# Example Source: https://learn.microsoft.com/en-us/defender-endpoint/api/run-advanced-query-sample-powershell
# Set Service Principal Variables
$AppID = "<AppID>"
$TenantID = "<TentantID>"
$Secret = "<Secret>" #Certificate Authentication is recommended.

# Hunting query to execute
$KQL = 'DeviceEvents | where ActionType startswith "asr" | project Timestamp, DeviceName, ActionType | take 50'

# Request Token
$resourceAppIdUri = 'https://api.securitycenter.microsoft.com'
$oAuthUri = "https://login.microsoftonline.com/$TenantID/oauth2/token"
$body = [Ordered] @{
    resource = "$resourceAppIdUri"
    client_id = "$AppID"
    client_secret = "$Secret"
    grant_type = 'client_credentials'
}
$response = Invoke-RestMethod -Method Post -Uri $oAuthUri -Body $body -ErrorAction Stop
$aadToken = $response.access_token

$url = "https://api.securitycenter.microsoft.com/api/advancedqueries/run"
$headers = @{ 
    'Content-Type' = 'application/json'
    Accept = 'application/json'
    Authorization = "Bearer $aadToken" 
}
$body = ConvertTo-Json -InputObject @{ 'Query' = $KQL }
$webResponse = Invoke-WebRequest -Method Post -Uri $url -Headers $headers -Body $body -ErrorAction Stop
$response =  $webResponse | ConvertFrom-Json
$results = $response.Results
$schema = $response.Schema

$results

Hunting the Hunters

The executed API calls can be traced to the application or user that makes the API calls. The executed API calls covering Defender XDR are available in the query resources report, but the fun does not end there.

Graph API

The most obvious one to monitor is the Graph API runHuntingQuery call, this can can be monitored using the query below. The logs only list who executed the query and if it was successful. The body of the Graph API call is not logged in the MicrosoftGraphActivityLogs table, meaning that you cannot get information on the executed query.

/images/LogicAppKQL/GraphResult.png
Graph API Log Results

MicrosoftGraphActivityLogs
| where RequestUri has "runHuntingQuery"
// Only list app based results
| where isnotempty(AppId)
| where ResponseStatusCode == 200
| project TimeGenerated, RequestUri, AppId, ResponseSizeBytes

Azure Monitor

For the Azure Monitor API we can again leverage the LAQueryLogs to determine who executed which query. Combining the LAQueryLogs with the AADSpnSignInEventsBeta it is also possible to enrich the ApplicationId with the ApplicationName, the analysts will love you for it. The results state the executed query, the request client application, the target LAW and the application/user that performed the action.

LAQueryLogs
| where QueryText contains "AuditLogs | sample 123"
| join kind=leftouter (AADSpnSignInEventsBeta | distinct Application, ApplicationId) on $left.AADClientId == $right.ApplicationId
| project-reorder TimeGenerated, QueryText, RequestClientApp, RequestTarget, AADClientId, Application

The next blog will explain how the above APIs can be leveraged in Logic Apps, stay tuned for more! :)

Happy hunting! 🏹