Advanced

Learn the most commonly used queries for Azure services.

Network

Review Azure Firewall Application Rules denied by firewall

let q1 = view() {
AzureDiagnostics
| where Category == "AzureFirewallApplicationRule" 
| where msg_s contains " to "
|parse kind = regex msg_s with Protocol @" request from " SourceIP ":" SourcePort @" to " TargetURL ":" targetPort @". Action: " Action @".\sN" Reason
};
let q2 = view() {
AzureDiagnostics
| where Category == "AzureFirewallApplicationRule" 
| where msg_s !contains " to "
|parse kind = regex msg_s with Protocol @" request from " SourceIP ":" SourcePort @". Action: " Action @". Reason:" Reason
};
union withsource="temptable" q1,q2 
| where SourceIP == "xx.xx.xx.xx"
| summarize count() by TargetURL, targetPort
//#| summarize count() by FQDN | order by count_ desc 
| order by count_ desc 

Description :
Review Azure Firewall Application Rules denied by firewall

List Azure Firewall denied connections (Network Rule) for certain IP and port (modify the query as required)

AzureDiagnostics
| where Category == "AzureFirewallNetworkRule"
| parse msg_s with Protocol " request from " SourceIP ":" SourcePortInt:int " to " TargetIP ":" TargetPortInt:int *
| parse msg_s with * ". Action: " Action1a
| parse msg_s with * "was " Action1b " to " NatDestination
| parse msg_s with Protocol2 " request from " SourceIP2 " to " TargetIP2 ". Action:" Action2
| extend SourcePort = tostring(SourcePortInt),TargetPort = tostring(TargetPortInt)
| extend Action = case(Action1a == "", case(Action1b == "",Action2,Action1b), Action1a),Protocol = case(Protocol == "", Protocol2, Protocol),SourceIP = case(SourceIP == "", SourceIP2, SourceIP),TargetIP = case(TargetIP == "", TargetIP2, TargetIP),SourcePort = case(SourcePort == "", "N/A", SourcePort),TargetPort = case(TargetPort == "", "N/A", TargetPort),NatDestination = case(NatDestination == "", "N/A", NatDestination)
| project TimeGenerated,Protocol, SourceIP,SourcePort,TargetIP,TargetPort,Action, NatDestination
| where SourceIP == "10.1.4.25" and TargetPort == 22 and Action == "Deny"
| order by TimeGenerated desc 

Description :
List Azure Firewall denied connections (Network Rule) for certain IP and port (modify the query as required)

NSG Flowlog - Display flows based on Source IP (modify the query as required)

  AzureNetworkAnalytics_CL
  | where SubType_s == "FlowLog" 
  | where SrcIP_s == "ipaddress"
  | summarize by DestIP_s, DestPort_d

Description :
NSG Flowlog - Display flows based on Source IP (display destination port and IP). Replace the ip address as required.

NSG Flowlog - Display flows based on Target IP (display source port and IP)

AzureNetworkAnalytics_CL
  | where SubType_s == "FlowLog" 
  | where DestIP_s == "ipaddress"
  | summarize by SrcIP_s, DestPort_d

Description :
NSG Flowlog - Display flows based on Target IP (display source port and IP)

Investigate top 3 incoming processes based on Bytes Recieved

VMConnection
| where Computer == "computername"
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Received = sum(BytesReceived/1024) by ProcessName
| top 3 by Received desc

Description :
Investigate top 3 incoming processes based on Bytes Recieved

Investigate top 3 outgoing processes based on Bytes Sent

VMConnection
| where Computer == "computername"
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Sent = sum(BytesSent/1024) by ProcessName
| top 3 by Sent desc

Description :
Investigate top 3 outgoing processes based on Bytes Sent

Investigate top 3 processes based on Bytes Received and Sent

VMConnection
| where Computer == "computername"
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Received = sum(BytesReceived/1024), Sent = sum(BytesSent/1024) by ProcessName
| extend TotalMb = Received + Sent
| extend SentMb = Sent
| extend ReceivedMb = Received
| project ProcessName, SentMb, ReceivedMb, TotalMb
| top 3 by TotalMb desc

Description :
Investigate top 3 processes based on Bytes Received and Sent

Graph showing network Sent/sec and Received/sec, 30 minute average for the timeframe

Perf
| where Computer == "computername"
| where TimeGenerated between (ago(1d) .. ago(5m))
| where ObjectName == "Network Adapter"
| where InstanceName == "Microsoft Hyper-V Network Adapter _2"
| summarize avg(CounterValue) by CounterName, bin(TimeGenerated, 30min)
| order by TimeGenerated desc
| render timechart 

Description :
Graph showing network Sent/sec and Received/sec, 30 minute average for the timeframe

Investigate top 3 communication partners based on Bytes Received and Bytes Sent

VMConnection
| where Computer == "computername"
| where TimeGenerated between (ago(1d) .. ago(5m))
| extend DNS = tostring(parse_json(RemoteDnsQuestions)[0])
| summarize Received = sum(BytesReceived/1024), Sent = sum(BytesSent/1024) by DestinationIp, DNS
| extend TotalMb = Received + Sent
| extend SentMb = Sent
| extend ReceivedMb = Received
| project DestinationIp, SentMb, ReceivedMb, TotalMb, DNS
| top 3 by TotalMb desc

Description :
Investigate top 3 communication partners based on Bytes Received and Bytes Sent

Investigate top 10 computers with most inbound and outbound communications events

VMConnection
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Events = count() by Computer
| top 10 by Events desc

Description :
Investigate top 10 computers with most inbound and outbound communications events

Investigate top 10 ports with most communications events

VMConnection
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Events = count() by DestinationPort
| top 10 by Events desc

Description :
Investigate top 10 ports with most communications events

Investigate top 3 communication ports based on Bytes Received and Bytes Sent

VMConnection
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Received = sum(BytesReceived/1024), Sent = sum(BytesSent/1024) by DestinationPort
| extend TotalMb = Received + Sent
| extend SentMb = Sent
| extend ReceivedMb = Received
| project DestinationPort, SentMb, ReceivedMb, TotalMb
| top 3 by TotalMb desc

Description :
Investigate top 3 communication ports based on Bytes Received and Bytes Sent

Investigate top 3 communication ports based on ProcessName

VMConnection
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Received = sum(BytesReceived/1024), Sent = sum(BytesSent/1024) by ProcessName
| extend TotalMb = Received + Sent
| extend SentMb = Sent
| extend ReceivedMb = Received
| project ProcessName, SentMb, ReceivedMb, TotalMb
| top 3 by TotalMb desc

Description :
Investigate top 3 communication ports based on ProcessName

Compute

Get the last heartbeat per computer

Heartbeat 
| summarize LastHeartBeat = arg_max(TimeGenerated, *) by Computer

Description :
If the Microsoft Monitoring agent is deployed to a computer, it sends a heart beat every minute. With this query, you can see the last heart beat sent by each computer.

See computers that have not sent a heart beat in 5 minutes

Heartbeat 
| summarize LastHeartBeat = arg_max(TimeGenerated, *) by Computer
| where LastHeartBeat < ago(5m)

Description :
See computers that have not sent a heart beat in 5 minutes

See the % Free Disk Space for all computers aggregated over the last 4 hours

InsightsMetrics
| where TimeGenerated > ago(4h)
| where Namespace == "LogicalDisk"
| where Name == "FreeSpacePercentage"
| extend DiskInstance = tostring(parse_json(Tags).["vm.azm.ms/mountId"])
| summarize DiskFreeSpace = avg(Val) by bin(TimeGenerated, 4h), Computer, DiskInstance

Description :
When using VM Insights, all performance counters are collected into the Insight Metrics table. This query shows the % Free space across all disks aggregated for the last 4 hours

Show latest status from Update Management for all computers

UpdateSummary 
| summarize arg_max(TimeGenerated, *) by Computer
| project Computer, LastUpdateApplied, OldestMissingSecurityUpdateInDays, WindowsUpdateSetting, SecurityUpdatesMissing, OtherUpdatesMissing, RestartPending

Description :
If using Update Management, you can use this query to show the latest status for each computer, including how many updates are missing for each computer

Show the status of each Update job for the last 7 days

UpdateRunProgress
| where TimeGenerated > ago(7d)
| summarize Failed = countif(InstallationStatus == "Failed"), FailedtoStart = countif(InstallationStatus == "FailedtoStart"), InProgress = countif(InstallationStatus == "InProgress"), Succeeded = countif(InstallationStatus == "Succeeded"), NotIncluded = countif(InstallationStatus == "NotIncluded"), NotStarted = countif(InstallationStatus == "NotStarted") by UpdateRunName

Description :
Use this query to see the number of updates in each status for each update job for the last 7 days

Data

Azure SQL Database: Show the categories that can be output by SQL Database

AzureDiagnostics
| summarize by Category

Description :
In the “Database Wait Statistics” category, you can see the details of the waiting time of internal resources that occurred in the database within the specified range period. In the “Query Store Wait Statistics” category, you can see the details of the waiting time of internal resources generated in the database from the entire information stored in the query store. You can see the errors that occurred in the “Errors” category.

Azure SQL Database: View resource wait time details in a pie chart

AzureDiagnostics
| where Category == 'DatabaseWaitStatistics'
| summarize sum(delta_wait_time_ms_d) by wait_type_s
| sort by sum_delta_wait_time_ms_d desc
| render piechart 

Description :
By focusing on resources with high latency, you can discover clues to improve database performance.

Azure SQL Database: Display the details of the resource wait count in a pie chart

AzureDiagnostics
| where Category == 'DatabaseWaitStatistics'
| summarize sum(delta_waiting_tasks_count_d) by wait_type_s
| sort by sum_delta_waiting_tasks_count_d desc
| render piechart 

Description :
By focusing on resources with high wait times, you can find clues to improve database performance.

Azure SQL Database: View the details of the resource wait time information stored in the query store in a pie chart

AzureDiagnostics
| where Category == 'QueryStoreWaitStatistics'
| summarize sum(total_query_wait_time_ms_d) by wait_category_s
| sort by sum_total_query_wait_time_ms_d desc
| render piechart 

Description :
By focusing on resources with high latency, you can discover clues to improve database performance. By default, one month’s worth of information is stored in the query store, so you can check in a wider range.

Azure SQL Database: View errors that occurred in SQL Database

AzureDiagnostics
| where Category == 'Errors'
| project TimeGenerated, error_number_d, Severity, state_d, Message

Description :
Understanding the errors that occur in the database will help resolve the actual failures and prevent potential failures.

Azure SQL Database: Display the number of errors that occurred in SQL Database in a line graph in chronological order

AzureDiagnostics
| where Category == 'Errors'
| summarize count() by TimeGenerated
| render timechart  

Description :
Understanding the number of errors that occur in the database will help resolve the actual failures and prevent potential failures.

Azure SQL Managed Instance: View resource usage for Azure SQL Managed Instance

AzureDiagnostics
| where Category == "ResourceUsageStats"

Description :
You can check the usage status of various resources of Azure SQL Managed Instance by specifying “ResourceUsageStats” as the Category operator.

Azure SQL Managed Instance: View average CPU usage, read bytes, and write bytes for Azure SQL Managed Instance

AzureDiagnostics
| where Category == "ResourceUsageStats"
| project avg_cpu_percent_s, io_bytes_read_s, io_bytes_written_s

Description :
You can see the CPU usage per second in the avg_cpu_percent_s column, the number of bytes read per second in the io_bytes_read_s column, and the number of bytes written per second in the io_bytes_written column.

Azure SQL Managed Instance: Display the average CPU usage, read bytes, and write bytes average for Azure SQL Managed Instance every 5 minutes, sorted in time, in ascending order.

AzureDiagnostics
| where Category == "ResourceUsageStats" 
| sort by  asc 
| summarize avg(todouble(avg_cpu_percent_s)), avg(todouble(io_bytes_read_s)), avg(todouble(io_bytes_written_s)) by bin(TimeGenerated, 5m)

Description :
The avg_cpu_percent_s, io_bytes_read_s, and io_bytes_written columns are string types and must be converted to numeric types to get the average. It also sorts by TimeGenerated to display in chronological order.

Azure SQL Managed Instance: View the average 5-minute average CPU usage, read bytes, and write bytes for Azure SQL Managed Instance in a line chart, sorted in ascending order by time.

AzureDiagnostics
| where Category == "ResourceUsageStats" 
| sort by TimeGenerated asc 
| summarize avg(todouble(avg_cpu_percent_s)), avg(todouble(io_bytes_read_s)), avg(todouble(io_bytes_written_s)) by bin(TimeGenerated, 5m)
| render timechart

Description :
Use the render operator with the timechart keyword to display a line chart.

Azure Synapse Analytics Dedicated SQL Pool: View the top 20 longest-running queries within the last 30 days (Query analysis step 1)

AzureDiagnostics
| where Category == 'ExecRequests'
| where StatementType_s !in ('Batch','Execute')
| summarize Session_ID=any(SessionId_s),Request_ID=any(RequestId_s),Submit_Time=max(SubmitTime_t),Start_Time=max(StartTime_t),End_Time=max(EndTime_t),Command=any(Command_s),Status=min(Status_s), Statement_Type=any(StatementType_s),Resource_class=any(ResourceClass_s) by RequestId_s
| summarize ElapsedTime_min=anyif((End_Time - Start_Time)/1m,Start_Time > ago(30d)),Session_ID=any(Session_ID), Submit_Time=any(Submit_Time) ,Start_Time=any(Start_Time), End_Time=any(End_Time),Command=any(Command),Status=any(Status),Statement_Type=any(Statement_Type),Resource_class=any(Resource_class) by Request_ID
| order by ElapsedTime_min desc
| limit 20

Description :
In the ExecRequests category of the Dedicated SQL Pool, you can see the execution time of the query. Depending on the execution time / state of the query, the information for one query may be displayed over multiple lines, so use the summarize operator to display only the information you need. The processing time is calculated from the difference between the query end time (End_Time) and the start time (Start_Time). If you need to check for a longer period, increase the value of the previous operator argument (30d (30 days) in the sample). Also, if you want to increase the number of items displayed, increase the value of the limit operator. The value in the Request_ID column will be used in step 2.

Azure Synapse Analytics Dedicated SQL Pool: Show the number of data processed in each step of the distributed query (Query analysis step 2)

AzureDiagnostics
| where Category  == 'RequestSteps'
| where RequestId_s == 'QIDnnnnnn' //Put your QueryID here
| where Status_s != 'Running'
| summarize max(StartTime_t),max(EndTime_t),max(RequestId_s),max(OperationType_s),max(RowCount_d),max(Command_s),max(Status_s) by StepIndex_d
| order by StepIndex_d asc

Description :
In the RequestSteps category of the Dedicated SQL Pool, you can get information about each step in a distributed query. You can consider what to do by looking at the steps that are taking the longest time. Specify the Request_ID value (obtained in query analysis step 1) of the query you want to analyze in the query.

Azure Synapse Analytics Dedicated SQL Pool: View the top 20 most processed query steps in the last 30 days (Query analysis step 3)

AzureDiagnostics
| where Category  == 'RequestSteps'
| extend elapsedTime = EndTime_t - StartTime_t
| extend elapsedTime_min = elapsedTime/1m
| where EndTime_t >= ago(30d)
| where StartTime_t >= ago(30d)
| order by RowCount_d desc
| project RequestId_s,OperationType_s, RowCount_d,elapsedTime_min , StartTime_t, EndTime_t, Status_s
| limit 20

Description :
In the RequestSteps category of the Dedicated SQL Pool, you can get information about each step in a distributed query. By checking the query step that takes the longest time, you can find the query that needs to be dealt with. If you need to check for a longer period, increase the value of the previous operator argument (30d (30 days) in the sample). Also, if you want to increase the number of items displayed, increase the value of the limit operator. The value in the Request_ID column will be used in step 4.

Azure Synapse Analytics Dedicated SQL Pool: Display the processing time of the entire query of the distributed query step with a large number of processing (Query analysis step 4)

AzureDiagnostics
| where Category  == 'RequestSteps'
| where RequestId_s == 'QIDnnnnn' //Insert your QID here
| where Status_s != 'Running'
| summarize max(StartTime_t),max(EndTime_t),max(RequestId_s),max(OperationType_s),max(RowCount_d),max(Command_s),max(Status_s) by StepIndex_d
| order by StepIndex_d asc

Description :
You can check the query processing status in the ExecRequests category of the Dedicated SQL Pool. Specify the Request_ID value (obtained in query analysis step 2) of the query you want to analyze in the query.

AVD

List all tables related to AVD

search * 
| where TimeGenerated > ago(24h)
| distinct $table
| where $table startswith "WVD"

Description :
Diagnostic logs related to AVD are sent out to separate tables for each type. This query lists the all tables related to AVDs (logs must have sent within the last 24 hours). For more information for diagnostic logs for AVD
https://docs.microsoft.com/ja-jp/azure/virtual-desktop/diagnostics-log-analytics

Display changes in number of active sessions in 5 minute intervals in the last 24 hours

WVDAgentHealthStatus
| where TimeGenerated > ago(24h)
| summarize TotalActiveSessions=sum(toint(ActiveSessions))/10 by bin(TimeGenerated, 5m)
| render timechart 

Description :
AVD Agent send various status information including the number of active sessions every 30 seconds to ‘WVDAgentHealthStatus’ table. This query display changes in number of inactive sessions (e.g. press ‘x’ button to close the window but the session remains) aggregated by sum() function.

Display changes in number of inactive sessions in 5 minute intervals in the last 24 hours

WVDAgentHealthStatus
| where TimeGenerated > ago(24h)
| summarize TotalInactiveSessions=sum(toint(InactiveSessions))/10 by bin(TimeGenerated, 5m)
| render timechart 

Description :
Display changes in number of inactive sessions (e.g. press ‘x’ button to close the window but the session remains) aggregated by sum() function

List the number of active/inactive sessions per session-hosts

WVDAgentHealthStatus
| where TimeGenerated > ago(10m)
| summarize arg_max(TimeGenerated,*) by SessionHostName
| project SessionHostName, ActiveSessions, InactiveSessions, TimeGenerated

Description :
List the number of active/inactive sessions from currently (at least last 10 mins) available session-hosts

List errors may caused by AVD control-plane in the last 24 hours

WVDErrors 
| where TimeGenerated > ago(24h) 
| where ServiceError == "true" 

Description :
List errors for ‘ServiceError’ column is set to ’true’, these records indicate you need to escalate the issue to Microsoft support

Count the number of errors group by each error type

WVDErrors 
| where TimeGenerated > ago(24h) 
| where ServiceError == "false" 
| summarize NumOfErrors=count() by ActivityType, Source, CodeSymbolic
| sort by Source

Description :

Count the number of errors group by each user in the last 24 hours

WVDErrors 
| where TimeGenerated > ago(24h) 
| where ServiceError == "false" 
| summarize NumOfErrors=count() by UserName

Description :

List of errors for specified user as <User UPN> in the last 24 hours

WVDErrors 
| where TimeGenerated > ago(24h) 
| where ServiceError == "false" 
| where UserName == "<User UPN>"

Description :

List source IP addresses connecting to session-host per HostPool over the last 24 hours

WVDConnections
| where TimeGenerated > ago(24h)
| extend Multi=split(_ResourceId, "/")
| extend HostPool = tostring(Multi[8])
| summarize count() by ClientSideIPAddress, HostPool

Description :

List per-user connection duration for specified HostPool as <Host Pool> over the last 24 hours

WVDConnections  
| where TimeGenerated > ago(24h)  
| extend Multi=split(_ResourceId, "/")
| extend HostPool = tostring(Multi[8])
| where HostPool == "<Host Pool>"
| where State == "Connected"  
| project CorrelationId, UserName, StartTime=TimeGenerated
| join kind=inner(WVDConnections  
| where State == "Completed"  
| project EndTime = TimeGenerated, CorrelationId) on CorrelationId  
| project Duration = EndTime - StartTime, UserName
| summarize ConnectionTimeTotal = sum(Duration) by UserName

Description :
The ‘WVDConnections’ table contains connect/disconnect event by users. This query aggregates the time between connect and disconnect event from a session host and calculates the cumulative connection time for each user. Since the ‘CorrelationId’ of the connect and disconnect events for the same session match, ‘join’ is used to combine the connect and disconnect event into a single record.

Display connection duration per user for specified HostPool as <Host Pool> over the past week

WVDConnections
| where TimeGenerated > ago(7d)
| extend Multi=split(_ResourceId, "/")
| extend HostPool = tostring(Multi[8])
| where HostPool == "<Host Pool>"
| where State == "Connected"
| project CorrelationId, UserName, StartTime = TimeGenerated  
| join kind=inner(WVDConnections  
| where State == "Completed"  
| project EndTime = TimeGenerated, CorrelationId) on CorrelationId  
| project DurationInMinutes = (EndTime - StartTime)/1m, UserName, EndTime
| summarize ConnectionTimeInMinutes=sum(DurationInMinutes) by bin(EndTime, 24h), UserName
| render timechart

Description :
This query extend the above query to provide graphical view to see transition of connection duration for each user.
In Line:11, subtracting ‘datetime’ types result in ’timespan’ type, but since this type cannot be rendered as is, convert to a scalar value in minutes by divide by ‘1m’.

Display connection latency per HostPool between session host and client device over the last 24 hours

WVDConnectionNetworkData
| where TimeGenerated > ago(24h)
| extend Multi = split(_ResourceId, "/")
| extend HostPool = tostring(Multi[8])
| summarize avg(EstRoundTripTimeInMs) by bin(TimeGenerated, 10m), HostPool
| render timechart 

Description :
The ‘WVDConnectionNetworkData’ table contains estimated latency of session host. This query display the latency per HostPool over the last 24 hours an every 10 minutes average.

Display connection latency per HostPool between session host and client device for specified user as <User UPN> over the last 24 hours

WVDConnectionNetworkData
| where TimeGenerated > ago(24h)
| join kind=inner (WVDConnections
| distinct CorrelationId, UserName) on CorrelationId
| where UserName == "<User UPN>"
| summarize avg(EstRoundTripTimeInMs) by bin(TimeGenerated, 10m)
| render timechart 

Description :
This query display the connection latency for specified user over the last 24 hours as an every 10 minutes average.

Application Insights

Count request within past 12 hours

requests
| where timestamp > ago(12h) 

Description :
The ‘requests’ table is a table of HTTP requests collected in Application Insights.

Count failed request within past 12 hours

requests
| where timestamp > ago(12h) 
| where success == false

Description :
In the ‘requests’ table, there is a row of type ‘boolean’ called ‘success’. You can aggregate failed requests by setting ‘sucess’ to ‘false’.

Count performance with requests and summaize avarage, 50, 95, 99 percentile

requests
| summarize RequestsCount=sum(itemCount), AverageDuration=avg(duration), percentiles(duration, 50, 95, 99) by operation_Name

Description :
In the ‘requests’ table, there is data that represents the time it took to respond ‘duration’. You can use ‘avg’ to aggregate averages and ‘percentiles’ to aggregate in percentiles.

requests
| summarize CountByCountry=count() by client_CountryOrRegion
| top 10 by CountByCountry
| render piechart

Description :
NA

Show page view data

pageViews

Description :
To aggregate pageviews, use the table in ‘pageViews’.

Show page view from browser data

pageViews
| where client_type == 'Browser'

Description :
NA

pageViews
| where notempty(duration) and client_Type == 'Browser'
| extend total_duration=duration*itemCount
| summarize avg_duration=(sum(total_duration)/sum(itemCount)) by operation_Name
| top 10 by avg_duration desc

Description :
NA

Security

Detect continuous logon failures for the same user

let threshold = 10;
let unit = 10m;
SigninLogs
| where ResultType !in ("0", "50125", "50140")
| summarize loginfailure = count() by UserPrincipalName, bin(TimeGenerated,unit)
| where loginfailure > threshold 

Description :

This query is a sample for detecting brute force attacks. Detects sign-in failures from the SigninLogs table that exceed the number of times set in threshold within the time set in unit. ResultType contains the result of the sign-in, where 0 means the sign-in was successful and there are no problems; 50125 and 50140 are events that are logged when the password is reset and when the sign-in memory is set, respectively, and are not failures, which can occur frequently. The following is an example.

SigninLogs records the sign-in of users and others to Azure AD. This logging can be enabled through Azure AD diagnostic settings and requires an Azure AD Premium P1 or P2 license. These codes are subject to change in the future, but for more information on [Azure AD Authentication and Authorization Error Codes](https://docs.microsoft.com/azure/active-directory/develop/reference-aadsts- error-codes), and you can also check the messages corresponding to the codes at https://login.microsoftonline.com/error.

Detect continuous logon failures for different users

let threshold = 20;
let unit = 10m;
SigninLogs
| where ResultType !in ("0", "50125", "50140")
| summarize loginfailure = dcount(UserPrincipalName) by  bin(TimeGenerated,unit)
| where loginfailure > threshold 

Description :
This query is a sample for detecting password spray attacks. Brute force is an attack that attempts multiple passwords for a specific user, but since it is easy to evade and detect by locking accounts, password sprayers fix passwords to the most commonly used ones and attempt sign-in while changing users. This query counts sign-in failures by unique users recorded within the time specified by unit and detects those that exceed the threshold.

Display IP addresses of clients whose number of errors logged by Application Gateway in the past is higher than the threshold value

let threshold = 10;
let lookback = 10m;
AzureDiagnostics
| where TimeGenerated > ago(lookback)
| where Category == "ApplicationGatewayAccessLog"
| where httpStatus_d >= 300
| summarize ErrCount= count() by clientIP_s,userAgent_s
| where ErrCount > threshold

Description :
Application Gateway accesses are logged in the ApplicationGatewayAccessLog category in AzureDiagnostics. During the reconnaissance phase, since errors are often logged with HTTP status codes, records with a status code of error (300) or higher are filtered. We find accesses that appear to be reconnaissance by detecting IP addresses that have recorded errors above a pre-defined threshold. This query may mis-detect cases where back-end services have failed, or where multiple users from a location access the Internet through a single gateway, but they are considered to originate from the same IP address. This may cause the system to not work properly. As a mitigation measure, in addition to IP addresses, UserAgent_s is used to distinguish access sources by user agent strings, but this is not sufficient.

Display client IP addresses that are causing access errors for multiple applications in Application Gateway

let threshold = 5;
let lookback = 10m;
AzureDiagnostics
| where TimeGenerated > ago(lookback)
| where Category == "ApplicationGatewayAccessLog"
| where httpStatus_d >= 300
| summarize UriCount = dcount(requestUri_s) by clientIP_s
| where UriCount > threshold

Description :
During the reconnaissance phase, accesses may be made to multiple application paths in order to brute force the paths and identify vulnerable applications. This results in multiple requestUri_s errors within a unit of time. dcount is a function that counts the number of unique counts for a given column. In this query, the number of unique requestUri_s for clientIP_s, i.e., the number of applications that have attempted access from a particular clientIP_s, is tallied. The record httpStatus_d is filtered for errors, so the number of applications that fail to access from a given IP can be compared to a threshold to detect attacks. Note that in actual attacks, the source IPs may be distributed across multiple locations.

Application Gateway displays IP addresses of clients who successfully accessed the application after multiple access errors

let lookback = 10m;
let threshold = 10;
let SuccessIPs = AzureDiagnostics
| where TimeGenerated > ago(lookback)
| where Category == "ApplicationGatewayAccessLog"
| where httpStatus_d between (200 .. 299) 
| summarize AccessTime = max(TimeGenerated) by clientIP_s;
AzureDiagnostics
| where TimeGenerated > ago(lookback)
| where httpStatus_d >= 300
| join kind=inner SuccessIPs on clientIP_s 
| where TimeGenerated < AccessTime
| summarize ErrBeforeSuccess = count() by clientIP_s
| where ErrBeforeSuccess > threshold

Description :
Although errors often occur during the reconnaissance phase, if the HTTP access success code changes after a series of errors, the application may have succeeded in gaining unauthorized access. This query creates a dataset of the last successful access time for each IP address from the ApplicationGatewayAccessLog and joins it with a dataset of only the errors from the original ApplicationGatewayAccessLog. Since the time of the successful access is included in the AccessTime column, TimeGenerated counts records that are smaller (older) than this and detects that the error occurred more than a threshold number of times before the successful access.

Display attacks detected by the Web Application Firewall

AzureDiagnostics
| where Category== "ApplicationGatewayFirewallLog"
| project TimeGenerated,clientIp_s,hostname_s,requestUri_s,ruleSetVersion_s,ruleId_s,Message,details_message_s,details_data_s

Description :
If WAF is enabled, the ApplicationGatewayFirewallLog category can record the detection results of WAF rules. This query shows the most frequently used items for WAF rule detection.

zureActivity detects new Administrative operations that had not occurred in the past month

let past1month = AzureActivity
| where TimeGenerated between (ago(30d) .. startofday(now()))
| where CategoryValue == "Administrative"
| distinct  OperationNameValue;
AzureActivity
| where TimeGenerated >  startofday(now())
| where CategoryValue == "Administrative"
| where OperationNameValue !in (past1month)
| project TimeGenerated, OperationNameValue, OperationName, ResourceGroup, ResourceProviderValue, Caller

Description :
The Administrative table records administrative operations in Administrative. When no system changes have occurred, administrative operations are recorded consistently, but if new, unrecorded operations are suddenly recorded, this may indicate that new change work has occurred or that a security breach has occurred.

View computers with processes that are not running on other computers

VMProcess 
| summarize UniqueProcesses = dcount(Computer) by ExecutableName
| where UniqueProcesses == 1
| join (
VMProcess
| project Computer, ExecutableName, ExecutablePath
) on ExecutableName
| distinct ExecutableName, Computer, ExecutablePath
| order by Computer, ExecutableName

Description :
Since server workloads will have the same process running on computers in the same role, if a unique process is running, it may be performing some special operation. This could be due to some change work, or it could indicate a security breach.

Check the weekly change in Secure Score

SecureScores 
| where TimeGenerated > ago(7d)
| extend SecureScorePercentage = PercentageScore*100
| summarize AverageScore = avg(SecureScorePercentage) by bin(TimeGenerated, 1d)

Description :
Security Score is a Posture Management feature of Microsoft Defender for Cloud that is available free of charge. Use Azure Policy to check that Microsoft’s recommended settings are in place. Maintain it at 90% or better, as it changes daily with the addition of workloads and the release of updates.

Detect data transmissions from resources in the ApplicationGateway back-end pool

let backend = AzureDiagnostics
| where Category == "ApplicationGatewayAccessLog"
| where serverRouted_s <> ""
| distinct serverRouted_s;
AzureDiagnostics
| where Category == "AzureFirewallNetworkRule" or Category == "AzureFirewallApplicationRule"
| parse msg_s with *  " request from " SourceIP ":" SourcePort " to " Dest ":" DestPort "." *
| project TimeGenerated,SourceIP,SourcePort,Dest,DestPort
| where SourceIP in (backend)

Description :
Communications to the back end of the Application Gateway are filtered by the Web Applicaiton Firewall, but the back end may receive undetected attack payloads. Payloads may behave in a variety of ways, but some generate outbound data communications to communicate with C&C servers on the Internet. This query assumes an environment where the Azure Firewall is in use and all Internet-facing communications are configured to be routed to the Azure Firewall. Create a list of serverRouted_s since the IP addresses of the back-end servers of the pplicationGateway are recorded in serverRouted_s. Since Azure Firewall records the source address for both IP-based network rules and URL-based application rules, the backend that attempted to send data is the one whose source address matches the list of backends created in advance.

View who has interactively logged in to the computer

SecurityEvent 
| where EventID == 4624 and (LogonType == 2 or LogonType == 10) and AccountType == "User"
| summarize count() by Computer, Account

Description :
Generally, servers in operation do not have interactive logons except for periods of change management. SecurityEvent is logged by Windows security logs, but can implement simple security monitoring by detecting interactive login events. A paid SKU of Defender for Cloud is required to obtain SecurityEvents.

Detect outbound access to IP addresses included in threat intelligence

let maliciousIPs = 
ThreatIntelligenceIndicator
| where NetworkIP <> "" and Active == true and ExpirationDateTime > now()
| distinct NetworkIP;
AzureDiagnostics
| where Category == "AzureFirewallNetworkRule"
| parse msg_s with *  " request from " SourceIP ":" SourcePort " to " DestIP ":" DestPort "." *
| where DestIP  in (maliciousIPs)

Description :
Azure Sentinel is required to perform this query; Azure Sentinel has data connectors to federate logs from various data sources and can pull in threat intelligence (untrusted IPs, URLs, file hashes, etc.). In this example, the Azure Firewall logs are queried against active untrusted IP addresses obtained from threat intelligence to see if there is any suspicious external access.

Resource Graph

View subscriptions with no Key Vaults

ResourceContainers 
| where type=='microsoft.resources/subscriptions'
| parse id with "/subscriptions/" SubscriptionID
| project subscriptionId, SubscriptionName = name
| join kind=leftouter (
Resources
| where type == 'microsoft.keyvault/vaults'
| project id, name, subscriptionId
) on subscriptionId
| join kind= leftouter (
Resources
| where type == 'microsoft.keyvault/vaults'
| summarize ResourceCount = count() by subscriptionId
) on subscriptionId
| extend RCount = iff(isnull(ResourceCount), 0, ResourceCount)
| project-away ResourceCount, subscriptionId1, subscriptionId2
| extend HealthStatus = iff(RCount == 1, "Not Compliant", "Compliant")

Description :
You can use this query in Azure Resource Graph to identify any subscriptions that do not contain KeyVaults.

View status of Azure Monitor Agents for all virtual machines

resources
| where type == "microsoft.compute/virtualmachines"
| project ComputerName = name, subscriptionId
| join kind=leftouter  (
resources
| where type == "microsoft.compute/virtualmachines/extensions"
| where name in ("AzureMonitorLinuxAgent", "AzureMonitorWindowsAgent")
| parse id with * "/virtualMachines/" ComputerName "/extensions/" *
| extend AMAStatus = properties.provisioningState,
    WorkspaceID = tostring(properties.settings.workspaceId)
| project AMA = name, ComputerName, AMAStatus
) on ComputerName
| join kind=leftouter  (
resources
| where type == "microsoft.compute/virtualmachines/extensions"
| where name in ("MicrosoftMonitoringAgent", "OmsAgentForLinux")
| parse id with * "/virtualMachines/" ComputerName "/extensions/" *
| extend MMAStatus = properties.provisioningState,
    WorkspaceID = tostring(properties.settings.workspaceId)
| project MMA = name, ComputerName, MMAStatus, WorkspaceID
) on ComputerName
| join kind=leftouter  (
resources
| where type == "microsoft.operationalinsights/workspaces"
| extend WorkspaceID = tostring(properties.customerId)
| project WorkspaceName = name, WorkspaceID
) on WorkspaceID
| project-away WorkspaceID1, ComputerName1, ComputerName2
| extend ["Both Agents"] = iff(isnotempty(AMA) and isnotempty(MMA), "True", "False")

Description :
You can use this query in Azure Resource Graph to identify which virtual machines have both the Azure Monitor Agent and the Microsoft Monitoring Agent deployed, and which workspace the MMA reports to.

View Orphan Managed disks

Resources
| where type =~ 'microsoft.compute/Disks'
| where managedBy == ""
| project name, id

Description :
You can use this query in Azure Resource Graph to identify any orphaned disks.

View Orphaned NICs

resources
| where type == "microsoft.network/networkinterfaces"
| where isnull (properties.virtualMachine) and isnull (properties.privateEndpoint)

Description :
This query can be used in Azure Resource Graph to identify any detached NIC

View Orphaned NSGs

resources
| where type =~ 'microsoft.network/networksecuritygroups' and isnull(properties.networkInterfaces) and isnull(properties.subnets)
| project name, resourceGroup

Description :
This query can be used in Azure Resource Graph to identify any used Network Security Group

View Orphaned Route Tables

resources
| where type == "microsoft.network/routetables" and isnull(properties.subnets)

Description :
This query can be used in Azure Resource Graph to identify any unused route table

Show Storage accounts with public endpoints

resources
| where type == "microsoft.storage/storageaccounts" and properties.allowBlobPublicAccess == 'true'

Description :
This query can be used in Azure Resource Graph to identify any storage account with public endpoints

Show Storage accounts with non-encrypted transfers enabled

  resources
  | where type == "microsoft.storage/storageaccounts" and properties.allowBlobPublicAccess == 'true'        

Description :
This query can be used in Azure Resource Graph to identify any storage accounts without an https requirement

VNets: Peering Configuration

resources
| where ['type'] == 'microsoft.network/virtualnetworks'
| mv-expand peerings=properties.virtualNetworkPeerings
| project VNetName=name, peeringName=peerings.name, peeringTarget=split(peerings.properties.remoteVirtualNetwork.id,"/",8)[0], peeringState=peerings.properties.peeringState,useRemoteGateways=peerings.properties.useRemoteGateways,
allowGatewayTransit=peerings.properties.allowGatewayTransit

Description :
Use this query to review the peering configuration of all Virtual Networks in scope

Subnet details (NSG/UDR)

resources
| where ['type'] == 'microsoft.network/virtualnetworks'
//| project dynProperties=todynamic(properties)
| mv-expand subnetNames=properties.subnets
| project VNetName=name, Subnet=subnetNames.name,AddressPrefix=subnetNames.properties.addressPrefix,
RouteTable=split(subnetNames.properties.routeTable.id,"/",8)[0],
NSG=split(subnetNames.properties.networkSecurityGroup.id,"/",8)[0],type="microsoft.network/subnets"

Description :
This query shows details about the subnets, Addresses, NSGs, UDRs.

Display count of VMs by Azure location

Resources | where type == "microsoft.compute/virtualmachines"
| summarize Total=count() by Region=location

Description :
Use this query to display a count of VMs by Azure location

Display a count of VMs by OS Platform

resources
| where ['type'] == 'microsoft.compute/virtualmachines'
| project osType=properties.storageProfile.osDisk.osType
| summarize count() by tostring(osType)

Description :
Use this query to display a count of VMs by OS Platform

List Network Interfaces associated with specific Application Security Group specified as <ASG>

Resources 
| where type == "microsoft.network/networkinterfaces"
| where isnotnull (properties.ipConfigurations[0].properties.applicationSecurityGroups)
| project subscriptionId, resourceGroup, name, ASGs=properties.ipConfigurations[0].properties.applicationSecurityGroups
| mv-expand ASGs
| project subscriptionId, resourceGroup, name, ASG=ASGs.id
| where ASG endswith "<ASG>"

Description :
NA

Last modified March 12, 2022: Add Compute section (#76) (bc26ef9)