Azure-Sentinel/Hunting Queries/MultipleDataSources/AzureResourceCreationWithNe...

105 строки
7.1 KiB
YAML

id: ac25d05d-362d-4a8d-b4e7-58c0edd2379c
name: Anomalous Resource Creation and related Network Activity
description: |
'Indicates when an anomalous number of resources are created successfully in Azure via the AzureActivity log.
This is then joined with the AzureNetworkAnalytics_CL data to identify any network related activity for the created resource.
The anomaly detection identifies activities that have occured both since the start of the day 1 day ago and the start of the day 7 days ago.
The start of the day is considered 12am UTC time.
Resource creation could indicated malicious or spurious use of your Azure Resource allocation. Resources can be abused in relation to digital
currency mining, command and control, exfiltration, distributed attacks and propagation of malware, among others. Verify that this resource creation
is expected.
Resources:
https://docs.microsoft.com/azure/azure-monitor/insights/azure-networking-analytics
https://docs.microsoft.com/azure/network-watcher/traffic-analytics-schema'
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
- connectorId: AzureNetworkWatcher
dataTypes:
- AzureNetworkAnalytics_CL
tactics:
- Impact
relevantTechniques:
- T1496
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let lookback = totimespan((endtime-starttime)*7);
let activity = AzureActivity
| where TimeGenerated >= startofday(ago(lookback))
// We look for any Operation that created and then succeeded where ActivitySubstatusValue has a value so that we can provide context
| where OperationNameValue endswith "write"
| where ActivityStatusValue has "Succeeded"
| make-series dResourceCount=dcount(ResourceId) default=0 on EventSubmissionTimestamp in range(startofday(ago(7d)), now(), 1d) by Caller, Resource, OperationNameValue
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dResourceCount)
// Comment slope reference below to see all returns
| where Slope > 0.2
| join kind=leftsemi (
// Last day's activity is anomalous
AzureActivity
| where TimeGenerated between(starttime..endtime)
// We look for any Operation that created and then succeeded where ActivitySubstatusValue has a value so that we can provide context
| where OperationNameValue endswith "write"
| where ActivityStatusValue has "Succeeded"
| make-series dResourceCount=dcount(ResourceId) default=0 on EventSubmissionTimestamp in range(startofday(ago(1d)), now(), 1d) by Caller, Resource, OperationNameValue
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dResourceCount)
// Comment slope reference below to see all returns
| where Slope > 0.2
) on Caller, Resource, OperationNameValue
// Expanding the fields that were grouped so we can match on a time window when we join the details later
| mvexpand EventSubmissionTimestamp, dResourceCount
// Making sure the fields are the right type or the join fails
| extend todatetime(EventSubmissionTimestamp), tostring(dResourceCount)
| join kind= inner (
AzureActivity
| where TimeGenerated between(starttime..endtime)
// We look for any Operation that created and then succeeded where ActivitySubstatusValue has a value so that we can provide context
| where OperationNameValue endswith "write"
| where ActivityStatusValue has "Succeeded" and isnotempty(ActivitySubstatusValue)
| summarize by EventSubmissionTimestamp = bin(EventSubmissionTimestamp, 1d), Caller, CallerIpAddress, OperationNameValue, ActivityStatusValue, Resource, ResourceGroup, ResourceId, SubscriptionId
) on EventSubmissionTimestamp, Caller, Resource, OperationNameValue;
let NetworkAnalytics =
union isfuzzy=true
(AzureNetworkAnalytics_CL
| where TimeGenerated between(starttime..endtime)
// Controlling for Schema Version and later parsing - This is Version 2 and Public IPs only
| where (isnotempty(FASchemaVersion_s) and isnotempty(DestPublicIPs_s))
| extend SchemaVersion = FASchemaVersion_s
| extend PublicIPs = tostring(split(DestPublicIPs_s,"|")[0])
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), FirstProcessedTimeUTC = min(FlowStartTime_t), LastProcessedTimeUtc = max(FlowEndTime_t),
Regions = makeset(Region_s), AzureRegions = makeset(AzureRegion_s), VMs = makeset(VM_s), MACAddresses = makeset(MACAddress_s), PublicIPs = makeset(PublicIPs), DestPort = makeset(DestPort_d), SrcIP = makeset(SrcIP_s),
ActivityCount = count() by NSGRule_s, NSGList_s, SubNet = Subnet1_s, FlowDirection_s, Subscription = Subscription1_g, Tags_s, SchemaVersion
//NSGList_s contains the subscription ID, remove that as we already have a field for this and now it will match what we get for SchemaVersion 1
| extend NSG = case(isnotempty(NSGList_s), strcat(split(NSGList_s, "/")[-2],"/",split(NSGList_s, "/")[-1]), "NotAvailable")
// Depending on the SchemaVersion, we will need to provide the NSG_Name for matching against the resource identified in AzureActivity
| extend NSG_Name = tostring(split(NSG, "/")[-1])
),
(
AzureNetworkAnalytics_CL
| where TimeGenerated between(starttime..endtime)
// Controlling for Schema Version and later parsing - This is Version 1
| where isempty(FASchemaVersion_s)
// Controlling for public IPs only
| where isnotempty(PublicFrontendIPs_s) or isnotempty(PublicIPAddresses_s)
| where PublicFrontendIPs_s != "null" or PublicIPAddresses_s != "null"
| extend SchemaVersion = SchemaVersion_s
// The Public IP can be indicated in one of 2 locations, assigning here for easy union results
| extend PublicIPs = case(isnotempty(PublicFrontendIPs_s), PublicFrontendIPs_s,
PublicIPAddresses_s)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), FirstProcessedTimeUTC = min(TimeProcessed_t), LastProcessedTimeUtc = max(TimeProcessed_t),
Regions = makeset(Region_s), AzureRegions = makeset(DiscoveryRegion_s), VMs = makeset(VirtualMachine_s), MACAddresses = makeset(MACAddress_s), PublicIPs = makeset(PublicIPs),
SrcIP = makeset(PrivateIPAddresses_s), Name = makeset(Name_s), DestPort = makeset(DestinationPortRange_s),
ActivityCount = count() by NSG = NSG_s, SubNet = Subnetwork_s, Subscription = Subscription_g, Tags_s, SchemaVersion
// Some events don't have an NSG listed, populating so it is clear it is not available in th datatype
| extend NSG = case(isnotempty(NSG), NSG, "NotAvailable")
// Depending on the SchemaVersion, we will need to provide the NSG_Name for matching against the resource identified in AzureActivity
| extend NSG_Name = tostring(split(NSG, "/")[-1])
)
| project StartTimeUtc, EndTimeUtc, FirstProcessedTimeUTC, LastProcessedTimeUtc, PublicIPs, NSG, NSG_Name, SrcIP, DestPort, SubNet, Name, VMs, MACAddresses, ActivityCount, Regions, AzureRegions, Subscription, Tags_s, SchemaVersion
;
activity | join kind= leftouter (NetworkAnalytics
) on $left.Resource == $right.NSG_Name
| extend timestamp = StartTimeUtc, AccountCustomEntity = Caller, IPCustomEntity = CallerIpAddress