Graph Runner Reconnaissance Detected
Query
let GraphRunnerQueries = dynamic([
"https:/graph.microsoft.com/version/groups/<UUID>/members",
"https:/graph.microsoft.com/version/users/<UUID>",
"https:/graph.microsoft.com/version/users",
"https:/graph.microsoft.com/version/users/",
"https:/graph.microsoft.com/version/search/query",
"https:/graph.microsoft.com/version/servicePrincipals(appId='<UUID>')/appRoleAssignedTo",
"https:/graph.microsoft.com/version/servicePrincipals",
"https:/graph.microsoft.com/version/servicePrincipals/<UUID>",
"https:/graph.microsoft.com/version/organization",
"https:/graph.microsoft.com/version/groups",
"https:/graph.microsoft.com/version/applications",
"https:/graph.microsoft.com/version/policies/authorizationPolicy"
]);
let PotentialMaliciousGraphCalls = materialize (
MicrosoftGraphActivityLogs
| where ingestion_time() > ago(35m)
| extend ObjectId = iff(isempty(UserId), ServicePrincipalId, UserId)
| extend ObjectType = iff(isempty(UserId), "ServicePrincipalId", "UserId")
| where RequestUri !has "microsoft.graph.delta"
| extend NormalizedRequestUri = replace_regex(RequestUri, @'[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}', @'<UUID>')
| extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'\d+$', @'<UUID>')
| extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'\/+', @'/')
| extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'\/(v1\.0|beta)\/', @'/version/')
| extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'%23EXT%23', @'')
| extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'\/[a-zA-Z0-9+_.\-]+@[a-zA-Z0-9.]+\/', @'/<UUID>/')
| extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'^\/<UUID>', @'')
| extend NormalizedRequestUri = replace_regex(NormalizedRequestUri, @'\?.*$', @'')
| summarize
GraphEndpointsCalled = make_set(NormalizedRequestUri, 1000),
IPAddresses = make_set(IPAddress)
by ObjectId, ObjectType
| project
ObjectId,
ObjectType,
IPAddresses,
MatchingQueries=set_intersect(GraphRunnerQueries, GraphEndpointsCalled)
| extend ConfidenceScore = round(todouble(array_length(MatchingQueries)) / todouble(array_length(GraphRunnerQueries)), 1)
| where ConfidenceScore > 0.7);
let IPEntities = PotentialMaliciousGraphCalls
| mv-expand IPAddresses
| sort by ObjectId
| extend CurrentRowNumber=row_number(2, prev(ObjectId) != ObjectId)
| extend IPInformation = bag_pack(@"$id", CurrentRowNumber, "Address", IPAddresses, "Type", "ip")
| project ObjectId, IPInformation
| summarize IPInformation = make_set(IPInformation, 150) by ObjectId;
PotentialMaliciousGraphCalls
| join kind=leftouter IPEntities on ObjectId
| project-away IPAddresses, *1, *2Explanation
This query is used to detect potential malicious activity related to GraphRunner, a tool used for Microsoft Graph queries. It checks for specific GraphRunner queries in the Microsoft Graph activity logs and identifies any suspicious calls made to Graph endpoints. It also identifies the IP addresses associated with these calls. The query then joins the identified IP addresses with the potential malicious Graph calls. The results can be used to investigate and respond to any potential attacks. The query has a medium severity level and runs every 30 minutes for a period of 40 minutes.
Details

Fabian Bader
Released: October 19, 2023
Tables
MicrosoftGraphActivityLogs
Keywords
GraphRunnerMicrosoft Graphreconnaissanceattackenvironmentcompromised identityUUIDgroupsmembersuserssearchservicePrincipalsorganizationapplicationspoliciesauthorizationPolicyT1595T1589T1591T1078MicrosoftGraphActivityLogsingestion_timeUserIdServicePrincipalIdRequestUriNormalizedRequestUriGraphEndpointsCalledIPAddressesConfidenceScoreIPEntitiesPotentialMaliciousGraphCallscreateIncidentgroupingConfigurationreopenClosedIncidentlookbackDurationmatchingMethodAllEntitiesgroupByEntitiesgroupByAlertDetailsgroupByCustomDetailsAlertPerResultalertDynamicPropertiesAccountAadUserIdsentinelEntitiesMappingssuppressionDuration
Operators
letdynamicmaterializewhereextendiffreplace_regexsummarizemake_setroundtodoublearray_lengthmv-expandsortCurrentRowNumberbag_packprojectjoinproject-awayfalsetrueenabledreopenClosedIncidentlookbackDurationmatchingMethodgroupByEntitiesgroupByAlertDetailsgroupByCustomDetailsaggregationKindAlertPerResultalertDynamicPropertiesentityTypefieldMappingsidentifiercolumnName
Severity
MediumTactics
ReconnaissanceInitialAccess
Frequency: 30m
Period: 40m