Azure Hound Reconnaissance Detected
Query
let AzureHoundGraphQueries = dynamic([
"https:/graph.microsoft.com/version/servicePrincipals/<UUID>/owners",
"https:/graph.microsoft.com/version/groups/<UUID>/members",
"https:/graph.microsoft.com/version/groups/<UUID>/owners",
"https:/graph.microsoft.com/version/servicePrincipals/<UUID>/appRoleAssignedTo",
"https:/graph.microsoft.com/version/roleManagement/directory/roleAssignments",
"https:/graph.microsoft.com/version/applications/<UUID>/owners",
"https:/graph.microsoft.com/version/devices/<UUID>/registeredOwners",
"https:/graph.microsoft.com/version/organization",
"https:/graph.microsoft.com/version/groups",
"https:/graph.microsoft.com/version/servicePrincipals",
"https:/graph.microsoft.com/version/applications",
"https:/graph.microsoft.com/version/roleManagement/directory/roleDefinitions",
"https:/graph.microsoft.com/version/devices",
"https:/graph.microsoft.com/version/users"
]);
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(AzureHoundGraphQueries, GraphEndpointsCalled)
| extend ConfidenceScore = round(todouble(array_length(MatchingQueries)) / todouble(array_length(AzureHoundGraphQueries)), 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
The query is designed to detect potential malicious activity in your environment related to Microsoft Graph queries similar to AzureHound. It looks for specific URLs in the Microsoft Graph API and matches them with activity logs. It calculates a confidence score based on the number of matching queries and compares it to a threshold. If the score is above the threshold, it identifies the potential malicious calls and extracts IP addresses associated with them. The query also includes incident configuration settings and entity mappings for further analysis.
Details

Fabian Bader
Released: October 14, 2023
Tables
MicrosoftGraphActivityLogs
Keywords
DevicesIntuneUserMicrosoft GraphServicePrincipalsGroupsOwnersAppRoleAssignedToRoleAssignmentsApplicationsRoleDefinitionsOrganizationUsers
Operators
|=:.()<>!hasextendiffreplace_regexmake_setsummarizeprojectset_intersectroundtodoublearray_lengthwherejoinproject-awaysortbag_packmv-expand
Severity
MediumTactics
ReconnaissanceInitialAccess
Frequency: 30m
Period: 40m