Query Details

Azure Hound Reconnaissance Detected

Query

id: bd676415-d309-41e0-87f7-4a9e52d81c14
name: AzureHound reconnaissance detected
version: 1.0.0
kind: Scheduled
description: Microsoft Graph queries that are similar to AzureHound where detected in your environment. An attacker might have started with the reconnaissance stage of an attack in your environment. This requires access to a compromised identity.
severity: Medium
queryFrequency: 30m
queryPeriod: 40m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Reconnaissance
  - InitialAccess
relevantTechniques:
  - T1595
  - T1589
  - T1591
  - T1078
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, *2
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDynamicProperties:
    - alertProperty: ConfidenceScore
      value: ConfidenceScore
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: ObjectId
sentinelEntitiesMappings:
  - columnName: IPInformation
suppressionDuration: 5h

Explanation

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 profile picture

Fabian Bader

Released: October 14, 2023

Tables

MicrosoftGraphActivityLogs

Keywords

Devices,Intune,User,MicrosoftGraph,ServicePrincipals,Groups,Owners,AppRoleAssignedTo,RoleAssignments,Applications,RoleDefinitions,Organization,Users

Operators

|=:.|()<>!hasextendiffreplace_regexmake_setsummarizeprojectset_intersectroundtodoublearray_lengthwherejoinproject-awaysortbag_packsummarizemv-expand

Actions