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
解説 :
ファイアウォールによって拒否された Azure ファイアウォール アプリケーション ルールの確認
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
解説 :
特定の IP およびポートに対する Azure ファイアウォールの拒否接続 (ネットワーク ルール) を一覧表示する (必要に応じてクエリを変更する)
AzureNetworkAnalytics_CL
| where SubType_s == "FlowLog"
| where SrcIP_s == "ipaddress"
| summarize by DestIP_s, DestPort_d
解説 :
NSG フローログ - 送信元 IP(ディスプレイ宛先ポートおよび IP)に基づいてフローを表示します。必要に応じて、IP アドレスを置き換えます。
AzureNetworkAnalytics_CL
| where SubType_s == "FlowLog"
| where DestIP_s == "ipaddress"
| summarize by SrcIP_s, DestPort_d
解説 :
NSG フローログ - ターゲット IP に基づいてフローを表示する (ディスプレイ ソース ポートと IP)
VMConnection
| where Computer == "computername"
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Received = sum(BytesReceived/1024) by ProcessName
| top 3 by Received desc
解説 :
バイト単位に基づいて上位 3 つの受信プロセスを調査する
VMConnection
| where Computer == "computername"
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Sent = sum(BytesSent/1024) by ProcessName
| top 3 by Sent desc
解説 :
送信バイト数に基づいて上位 3 つの送信プロセスを調査する
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
解説 :
受信バイト数と送信バイト数に基づいて上位 3 つのプロセスを調査する
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
解説 :
ネットワークの送信/秒と受信/秒、時間枠の30分平均を示すグラフ
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
解説 :
受信バイト数と送信バイト数に基づいて上位 3 つの通信パートナーを調査する
VMConnection
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Events = count() by Computer
| top 10 by Events desc
解説 :
受信および送信の通信イベントが最も多い上位 10 台のコンピュータを調査する
VMConnection
| where TimeGenerated between (ago(1d) .. ago(5m))
| summarize Events = count() by DestinationPort
| top 10 by Events desc
解説 :
ほとんどの通信イベントで上位 10 個のポートを調査する
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
解説 :
受信バイト数と送信バイト数に基づいて上位 3 つの通信ポートを調査する
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
解説 :
プロセス名に基づいて上位 3 つの通信ポートを調べる
Heartbeat
| summarize LastHeartBeat = arg_max(TimeGenerated, *) by Computer
解説 :
Microsoft 監視エージェントがコンピューターに展開されている場合、1 分ごとにハートビートを送信します。このクエリを使用すると、各コンピュータから送信された最後のハートビートを確認できます。
Heartbeat
| summarize LastHeartBeat = arg_max(TimeGenerated, *) by Computer
| where LastHeartBeat < ago(5m)
解説 :
5分間ハートビートを送っていないコンピュータを取得する
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
解説 :
VM インサイトを使用する場合、すべてのパフォーマンス カウンターがインサイト メトリック テーブルに収集されます。このクエリは、過去 4 時間に集計されたすべてのディスクの空き領域の割合を示します。
UpdateSummary
| summarize arg_max(TimeGenerated, *) by Computer
| project Computer, LastUpdateApplied, OldestMissingSecurityUpdateInDays, WindowsUpdateSetting, SecurityUpdatesMissing, OtherUpdatesMissing, RestartPending
解説 :
更新の管理を使用する場合は、このクエリを使用して、各コンピュータの最新の状態を表示できます。
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
解説 :
このクエリを使用して、過去 7 日間の各更新ジョブの各ステータスの更新数を確認します。
AzureDiagnostics
| summarize by Category
解説 :
「DatabaseWaitStatistics」カテゴリでは指定した範囲期間内にデータベース内で発生した内部リソースの待機時間の詳細が確認できます。「QueryStoreWaitStatistics」カテゴリではクエリストア内に格納された情報全体からデータベース内で発生した内部リソースの待機時間の詳細が確認できます。「Errors」カテゴリでは発生したエラーを確認できます。
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
解説 :
待機時間の多いリソースに着目することで、データベースのパフォーマンス改善の糸口を発見することができます。
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
解説 :
待機回数の多いリソースに着目することで、データベースのパフォーマンス改善の糸口を発見することができます。
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
解説 :
待機時間の多いリソースに着目することで、データベースのパフォーマンス改善の糸口を発見することができます。クエリストア内にはデフォルトで1ヵ月分の情報が格納されるため、より広い範囲での確認が可能です。
AzureDiagnostics
| where Category == 'Errors'
| project TimeGenerated, error_number_d, Severity, state_d, Message
解説 :
データベースで発生したエラーを把握することは顕在化した障害の解決や、潜在的な障害を未然に防止することにつながります。
AzureDiagnostics
| where Category == 'Errors'
| summarize count() by TimeGenerated
| render timechart
解説 :
データベースで発生したエラーの回数を把握することは顕在化した障害の解決や、潜在的な障害を未然に防止することにつながります。
AzureDiagnostics
| where Category == "ResourceUsageStats"
解説 :
Category オペレーターに “ResourceUsageStats” を指定することで、Azure SQL Managed Instance の様々なリソースの使用状況を確認することができます。
AzureDiagnostics
| where Category == "ResourceUsageStats"
| project avg_cpu_percent_s, io_bytes_read_s, io_bytes_written_s
解説 :
秒ごとの CPU 使用率は avg_cpu_percent_s 列、秒ごとの読み取りバイト数は io_bytes_read_s 列、秒ごとの書き込みバイト数は io_bytes_written 列で確認することが可能です。
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)
解説 :
avg_cpu_percent_s 列、io_bytes_read_s 列および io_bytes_written 列は文字列型なので、平均値を求めるために数値型に変換する必要があります。また、時系列で表示するために TimeGenerated で並べ替えを行います。
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
解説 :
render オペレーターを timechart キーワードとともに使用して折れ線グラフを表示します。
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
解説 :
Dedicated SQL Poolの ExecRequests カテゴリでは、クエリの実行時間を確認することができます。クエリの実行時間/状態によっては、複数行に渡って一つのクエリの情報が表示されるため、summarize オペレータを使用して必要な情報だけを表示するようにします。処理時間はクエリ終了時間(End_Time)と開始時間(Start_Time)の差から計算しています。より長い期間の確認が必要な場合は ago オペレーターの引数の値を増やしてください (サンプルでは 30d (30日)としています)。また、表示件数を増やす場合は limit オペレーターの値を増やしてください。Request_ID 列の値はステップ 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
解説 :
Dedicated SQL Poolの RequestSteps カテゴリでは、分散クエリの各ステップの情報を取得することができます。一番長く時間がかかっているステップを確認することで、対処方法を検討することができます。解析対象としたいクエリの Request_ID の値 (クエリ解析 ステップ 1にて取得)を、クエリ内で指定してください。
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
解説 :
Dedicated SQL Poolの RequestSteps カテゴリでは、分散クエリの各ステップの情報を取得することができます。一番長く時間がかかっているクエリステップを確認することで、対処が必要なクエリの発見が可能です。より長い期間の確認が必要な場合は ago オペレーターの引数の値を増やしてください (サンプルでは 30d (30日)としています)。また、表示件数を増やす場合は limit オペレーターの値を増やしてください。Request_ID 列の値はステップ 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
解説 :
Dedicated SQL Poolの ExecRequests カテゴリでは、クエリ処理状況を確認することができます。解析対象としたいクエリの Request_ID の値 (クエリ解析 ステップ 2にて取得)を、クエリ内で指定してください。
search *
| where TimeGenerated > ago(24h)
| distinct $table
| where $table startswith "WVD"
解説 :
AVD に関連する診断ログは用途毎に別々のテーブルに出力されます。ここでは AVD に関連するテーブルの一覧(ログが 24 時間以内に出力されたものに限る)を表示しています。
AVD の診断ログについては以下のドキュメントを参照ください。
https://docs.microsoft.com/ja-jp/azure/virtual-desktop/diagnostics-log-analytics
WVDAgentHealthStatus
| where TimeGenerated > ago(24h)
| summarize TotalActiveSessions=sum(toint(ActiveSessions))/10 by bin(TimeGenerated, 5m)
| render timechart
解説 :
AVD Agent は 30 秒おきにホスト上のアクティブなセッション数などのステータス情報を ‘WVDAgentHealthStatus’ テーブルに送信しています。
このクエリはアクティブなセッションの数を sum() 関数により集計してグラフ化しています。
WVDAgentHealthStatus
| where TimeGenerated > ago(24h)
| summarize TotalInactiveSessions=sum(toint(InactiveSessions))/10 by bin(TimeGenerated, 5m)
| render timechart
解説 :
インアクティブ(ユーザーが x ボタンで閉じるなどしてアクティブではないがセッションが残っているもの)なセッション数を sum() 関数により集計してグラフ化しています。
WVDAgentHealthStatus
| where TimeGenerated > ago(10m)
| summarize arg_max(TimeGenerated,*) by SessionHostName
| project SessionHostName, ActiveSessions, InactiveSessions, TimeGenerated
解説 :
現時点(過去 10 分以内)で正常稼働しているセッションホストから、アクティブ/インアクティブなセッション数をホスト毎に表示
WVDErrors
| where TimeGenerated > ago(24h)
| where ServiceError == "true"
解説 :
ServiceError カラムが true となっているエラーのみをフィルター表示しています。これらのエラーはマイクロソフト サポートへの問い合わせが必要な問題が発生していることを示しています
WVDErrors
| where TimeGenerated > ago(24h)
| where ServiceError == "false"
| summarize NumOfErrors=count() by ActivityType, Source, CodeSymbolic
| sort by Source
Description :
WVDErrors
| where TimeGenerated > ago(24h)
| where ServiceError == "false"
| summarize NumOfErrors=count() by UserName
Description :
WVDErrors
| where TimeGenerated > ago(24h)
| where ServiceError == "false"
| where UserName == "<User UPN>"
Description :
WVDConnections
| where TimeGenerated > ago(24h)
| extend Multi=split(_ResourceId, "/")
| extend HostPool = tostring(Multi[8])
| summarize count() by ClientSideIPAddress, HostPool
Description :
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
解説 :
‘WVDConnections’ テーブルにはユーザーによる接続開始や切断のログが記録されます。
このクエリではセッションホストに接続してから切断するまでの時間を集計して、ユーザー毎の接続時間の累計値を算出しています。
同じセッションにおける接続と切断のログは ‘CorrelationId’ が一致するため、join により接続と切断のログを一つのレコードに纏めています。
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
解説 :
上に書いたクエリーを拡張して、ユーザー毎の接続時間の日次推移をグラフ化しています。
datetime 型同士で引き算をすると timespan 型になりますが、この型のままではグラフ化できないため、11 行目で ‘1m’ で割ることで分単位のスカラー値に変換しています。
WVDConnectionNetworkData
| where TimeGenerated > ago(24h)
| extend Multi = split(_ResourceId, "/")
| extend HostPool = tostring(Multi[8])
| summarize avg(EstRoundTripTimeInMs) by bin(TimeGenerated, 10m), HostPool
| render timechart
解説 :
‘WVDConnectionNetworkData’ テーブルにはセッションホスト接続時の応答性(レイテンシ)の情報が記録されます。
このクエリでは過去 24 時間におけるホストプール毎のレイテンシの推移を、10 分毎の平均値として表示しています。
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
解説 :
このクエリでは過去 24 時間の特定のユーザーにおけるレイテンシの推移を、10 分毎の平均値として表示しています。
requests
| where timestamp > ago(12h)
解説 :requests
テーブルは、Application Insights で収集したHTTPリクエストのテーブルです。
requests
| where timestamp > ago(12h)
| where success == false
解説 :requests
テーブルの中には、sucess
という boolean
型の行があります。 success
を false
に設定することによって失敗したリクエストを集計することができます。
requests
| summarize RequestsCount=sum(itemCount), AverageDuration=avg(duration), percentiles(duration, 50, 95, 99) by operation_Name
解説 :requests
テーブルの中には、duration
という 返答にかかった時間を表すデータがあります。 avg
を使えば平均を、percentiles
を使えばパーセンタイルでの集計が可能です。
requests
| summarize CountByCountry=count() by client_CountryOrRegion
| top 10 by CountByCountry
| render piechart
Description :
NA
pageViews
解説 :
ページビューを集計するためには、pageViews
のテーブルを使います。
pageViews
| where client_type == 'Browser'
解説 :pageViews
テーブルの中に client_type
のデータがあります。where
句を使い client_type
を Browser に絞ることでブラウザから参照されるデータを抽出できます。
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
解説 :where
句で duration
が空でないものでかつブラウザからのアクセスのものを区切り、extend
句で処理時間の合計値として total_duration
を計算します。
sumamrize
句で アクセスディレクトリのデータである operation_Name
事に平均処理時間を出します。最後に top
句を使い平均処理時間のトップ10を出し desc
で並べかえます。
let threshold = 10;
let unit = 10m;
SigninLogs
| where ResultType !in ("0", "50125", "50140")
| summarize loginfailure = count() by UserPrincipalName, bin(TimeGenerated,unit)
| where loginfailure > threshold
解説 :
このクエリはブルートフォース攻撃を検出するためのサンプルです。
SigninLogs テーブルから unit
に設定した時間内に threshold
で設定した回数を超えるサインインの失敗を検出します。
ResultType にサインインの結果が含まれ、0 はサインインが成功し問題がないことを表します。50125 と 50140 はそれぞれパスワードがリセットされた場合とサインインの記憶を設定した場合に記録されるイベントで、頻繁に発生する可能性のある失敗ではないイベントです。
SigninLogs は Azure AD に対するユーザーなどのサインインを記録します。このログは Azure AD の診断設定から有効化することができ、有効化には Azure AD Premium P1 または P2 のライセンスが必要です。 これらのコードは将来変更される可能性がありますが、の詳細は Azure AD 認証と承認のエラー コード に記載がある他、https://login.microsoftonline.com/error でコードに対応するメッセージを確認することができます。
let threshold = 20;
let unit = 10m;
SigninLogs
| where ResultType !in ("0", "50125", "50140")
| summarize loginfailure = dcount(UserPrincipalName) by bin(TimeGenerated,unit)
| where loginfailure > threshold
解説 :
このクエリはパスワード スプレー攻撃を検出するためのサンプルです。
ブルート フォースは特定のユーザーに複数のパスワードを試行する攻撃ですが、アカウントのロックなどで回避・検出が行われやすいため、パスワードスプレーではパスワードをよく使われやすいものに固定し、ユーザーを変えながらサインインを試行します。
このクエリでは unit
で指定した時間内に記録されたユニークなユーザーによるサインインの失敗を数え、閾値を越えたものを検出します。
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
解説 :
ApplicationGateway は複数のログを生成します。一つはクライアントからアプリケーションのアクセスがあったことを記録する ApplicationGatewayAccessLog カテゴリで、IP アドレスやユーザーエージェント文字列、アクセス先のアプリケーションの URI とアクセス結果の HTTP ステータスなどが記録されています。このログは AzureDiagnostics の ApplicationGatewayAccessLog カテゴリに記録されます。
偵察のフェーズでは HTTP のステータスコードでエラーが記録されることが多いため、ステータスコードがエラー (300) 以上のレコードをフィルタします。予め設定された閾値を上回るエラーを記録した IP アドレスを検出することで偵察と思われるアクセスを発見します。
このクエリはバックエンドのサービスに障害が発生しているようなケースを誤検出する可能性があり、また拠点から複数のユーザーが1つのゲートウェイを経由してインターネットアクセスを行うようなケースでも、同一 IP アドレスからの発信元とみなされるためうまく動作しない可能性があります。緩和策として IP アドレスの他にも UserAgent_s でユーザーエージェント文字列によりアクセス元を区別するようにしていますが、十分ではありません。
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
解説 :
偵察フェーズではパスの総当たりや脆弱性のあるアプリケーションを確認するために複数のアプリケーションのパスに対してアクセスを行うことがあります。この結果単位時間内に複数の requestUri_s に対してエラーが発生します。
dcount は指定された列に対してユニークな数をカウントする関数で、このクエリでは clientIP_s に対してユニークな requestUri_s の数、つまり特定の clientIP_s からアクセスが試行されたアプリケーションの数が集計されます。
レコード httpStatus_d がエラーになるものにフィルタされているため、ある IP からアクセスに失敗したアプリケーションの数を閾値と比較して攻撃を検出することができます。
実際の攻撃では発信元 IP が複数に分散している場合もあるので注意が必要です。
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
解説 :
偵察フェーズではエラーが発生することが多いですが、エラーが続いたあとに HTTP のアクセス成功コードがかえる場合、アプリケーションの不正なアクセスに成功している可能性があります。
このクエリでは ApplicationGatewayAccessLog から IP アドレスごとにアクセスに成功した最後の時刻のデータセットを作成し、元の ApplicationGatewayAccessLog からエラーだけを抽出したデータセットと join します。
アクセスに成功した時刻が AccessTime 列に含まれるため、TimeGenerated がこれより小さい(古い)レコードを数え、アクセス成功前に閾値を超える回数エラーが発生していたことを検出します。
AzureDiagnostics
| where Category== "ApplicationGatewayFirewallLog"
| project TimeGenerated,clientIp_s,hostname_s,requestUri_s,ruleSetVersion_s,ruleId_s,Message,details_message_s,details_data_s
解説 :
WAF が有効な場合 ApplicationGatewayFirewallLog カテゴリに WAF のルールによる検出結果を記録することができます。このクエリでは WAF のルール検出の際によく使う項目を表示しています。
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
解説 :
Administrative テーブルは Administrative
に管理操作を記録します。システムの変更が発生していないときには管理操作は一定のものが記録されますが、急に記録されていない新しい操作が記録された場合には、新しく変更作業が発生したか、セキュリティ侵害の兆候の可能性があります。
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
解説 :
サーバー ワークロードでは同じ役割のコンピューターでは同じプロセスが起動することになるため、ユニークなプロセスが起動している場合には何か特殊な操作を行っている可能性があります。
これは何らかの変更作業に伴うものである可能性もありますし、セキュリティ侵害を示唆するものである可能性もあります。
SecureScores
| where TimeGenerated > ago(7d)
| extend SecureScorePercentage = PercentageScore*100
| summarize AverageScore = avg(SecureScorePercentage) by bin(TimeGenerated, 1d)
解説 :
セキュリティ スコアは無償で利用することができる Microsoft Defender for Cloud のポスチャ マネジメントの機能です。
Azure Policy を使用して、マイクロソフトの推奨設定が行われているかをチェックします。
ワークロードの追加や更新プログラムのリリースにより日々変化するため、90% 以上を維持するようにメンテナンスしましょう。
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)
解説 :
アプリケーションゲートウェイのバックエンドに対する通信は Web Applicaiton Firewall によって件さされますが、検出されなかった攻撃のペイロードはバックエンドが受け取る可能性があります。
ペイロードは様々な動作をすることが考えられますが、インターネット上の C&C サーバーと通信を行うためにアウトバウンドのデータ通信を発生させるものがあります。
このクエリでは Azure Firewall が使われていて、全てのインターネット向け通信は Azure Firewall にルーティングされるように構成されている環境を想定しています。
pplicationGateway のバックエンド サーバーの IP アドレスは serverRouted_s に記録されるため、serverRouted_s のリストを作成します。
Azure Firewall では IP ベースのネットワークルール、URL ベースのアプリケーション ルールともに送信元アドレスが記録されるため、送信元アドレスが予め作成したバックエンドのリストと一致するものがデータ送信を行おうとしたバックエンドです。
SecurityEvent
| where EventID == 4624 and (LogonType == 2 or LogonType == 10) and AccountType == "User"
| summarize count() by Computer, Account
解説 :
一般的に運用中のサーバーには、変更管理の期間を除き対話的ログオンが発生することはありません。
SecurityEvent は Windows のセキュリティ ログが記録されますが対話的ログインのイベントを検出することで簡単なセキュリティ監視を実装することができます。
SecurityEvent を取得するためには Defender for Cloud の有償 SKU が必要です。
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)
解説 :
このクエリを実行するためには Azure Sentinel が必要です。Azure Sentinel では様々なデータソースからログを連携するためのデータコネクタがあり、脅威インテリジェンス(信頼できないIP、URL、ファイルハッシュなど)を取り込むことができます。
この例では脅威インテリジェンスから得られたアクティブな信頼できない IP アドレスに対して Azure Firewall のログを照会し、あやしい外部アクセスがあるかどうかを確認しています。
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.
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.
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.
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
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
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
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
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
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
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.
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
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
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