105 строки
7.1 KiB
YAML
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
|