Storage Alerts Correlation with CommonSecurityLogs & AuditLogs


Description

'This query combines different Storage alerts with CommonSecurityLogs and AuditLogs helping analysts investigate any possible storage related attacks faster thus reducing Mean Time To Respond'

Query · kql

SecurityAlert
| where AlertName has_any ('Access from  a suspicious IP to a storage file share','	Access from a Tor exit node to a storage blob container')
| extend EntitiesDynamicArray = parse_json(Entities) | mv-expand EntitiesDynamicArray
// Parsing relevant entity column extract hostname and IP address
| extend EntityType = tostring(parse_json(EntitiesDynamicArray).Type), EntityAddress = tostring(EntitiesDynamicArray.Address)
| extend IpAddress = iif(EntityType == 'ip', EntityAddress, '')
| where isnotempty(IpAddress) 
| join kind=inner (
CommonSecurityLog 
| where DeviceVendor =~ "Fortinet"
| where ApplicationProtocol has_any ("SSL","RDP")
| where LogSeverity has_any ("2","3")
| where isnotempty(SourceIP) and isnotempty(DestinationIP) and SourceIP != "0.0.0.0"
| where DeviceAction !in ("close", "client-rst", "server-rst", "deny") and DestinationPort != 161
| project DeviceProduct,LogSeverity,DestinationPort,DestinationIP,Message,SourceIP,SourcePort,Activity,SentBytes,ReceivedBytes
) on $left.IpAddress == $right.DestinationIP
| join kind=inner (
AuditLogs
| where LoggedByService =~ "Core Directory"
| where Category =~ "RoleManagement"
| extend IpAddress = case(
isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)) and tostring(parse_json(tostring(InitiatedBy.user)).ipAddress) != 'null', tostring(parse_json(tostring(InitiatedBy.user)).ipAddress), 
isnotempty(tostring(parse_json(tostring(InitiatedBy.app)).ipAddress)) and tostring(parse_json(tostring(InitiatedBy.app)).ipAddress) != 'null', tostring(parse_json(tostring(InitiatedBy.app)).ipAddress),
'Not Available')
| extend InitiatedBy = iff(isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)), 
tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName), tostring(parse_json(tostring(InitiatedBy.app)).displayName)), UserRoles = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
| extend TargetResourceName = tolower(tostring(TargetResources.[0].displayName)) 
)
on IpAddress
| summarize count () by TimeGenerated,IpAddress, UserRoles,SourcePort, DestinationPort, AccountCustomEntity =InitiatedBy

Rule dependencies

Depends on

  • correlates · Sentinel SecurityAlert
    An external product alert, not indexed on this site.
Raw source Storage Alerts Correlation with CommonSecurityLogs & AuditLogs · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
id: 860a8df2-8d19-4c60-bf61-de1c02422797
name: Storage Alerts Correlation with CommonSecurityLogs & AuditLogs
description: |
  'This query combines different Storage alerts with CommonSecurityLogs and AuditLogs helping analysts investigate any possible storage related attacks faster
   thus reducing Mean Time To Respond'
requiredDataConnectors:
  - connectorId: AzureSecurityCenter
    dataTypes:
      - SecurityAlert (ASC)
  - connectorId: Fortinet
    dataTypes:
      - CommonSecurityLog
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
tactics:
  - InitialAccess
  - Impact
relevantTechniques:
  - T1190
  - T1078
query: |
  SecurityAlert
  | where AlertName has_any ('Access from  a suspicious IP to a storage file share','	Access from a Tor exit node to a storage blob container')
  | extend EntitiesDynamicArray = parse_json(Entities) | mv-expand EntitiesDynamicArray
  // Parsing relevant entity column extract hostname and IP address
  | extend EntityType = tostring(parse_json(EntitiesDynamicArray).Type), EntityAddress = tostring(EntitiesDynamicArray.Address)
  | extend IpAddress = iif(EntityType == 'ip', EntityAddress, '')
  | where isnotempty(IpAddress) 
  | join kind=inner (
  CommonSecurityLog 
  | where DeviceVendor =~ "Fortinet"
  | where ApplicationProtocol has_any ("SSL","RDP")
  | where LogSeverity has_any ("2","3")
  | where isnotempty(SourceIP) and isnotempty(DestinationIP) and SourceIP != "0.0.0.0"
  | where DeviceAction !in ("close", "client-rst", "server-rst", "deny") and DestinationPort != 161
  | project DeviceProduct,LogSeverity,DestinationPort,DestinationIP,Message,SourceIP,SourcePort,Activity,SentBytes,ReceivedBytes
  ) on $left.IpAddress == $right.DestinationIP
  | join kind=inner (
  AuditLogs
  | where LoggedByService =~ "Core Directory"
  | where Category =~ "RoleManagement"
  | extend IpAddress = case(
  isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)) and tostring(parse_json(tostring(InitiatedBy.user)).ipAddress) != 'null', tostring(parse_json(tostring(InitiatedBy.user)).ipAddress), 
  isnotempty(tostring(parse_json(tostring(InitiatedBy.app)).ipAddress)) and tostring(parse_json(tostring(InitiatedBy.app)).ipAddress) != 'null', tostring(parse_json(tostring(InitiatedBy.app)).ipAddress),
  'Not Available')
  | extend InitiatedBy = iff(isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)), 
  tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName), tostring(parse_json(tostring(InitiatedBy.app)).displayName)), UserRoles = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)
  | extend TargetResourceName = tolower(tostring(TargetResources.[0].displayName)) 
  )
  on IpAddress
  | summarize count () by TimeGenerated,IpAddress, UserRoles,SourcePort, DestinationPort, AccountCustomEntity =InitiatedBy
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.