YARA-L でマルチステージ クエリを作成する
このドキュメントでは、YARA-L のマルチステージ クエリを使用して、あるクエリステージの出力を次のステージの入力に直接渡す方法について説明します。このプロセスでは、単一のモノリシック クエリよりもデータ変換を細かく制御できます。
マルチステージ クエリを既存の機能と統合する
マルチステージ クエリは、Google Security Operations の次の既存の機能と連携して動作します。
複合検出ルール: マルチステージ クエリは、複合検出ルールを補完します。複合ルールとは異なり、検索を使用するマルチステージ クエリは結果をリアルタイムで返すことができます。
期間とマルチイベント ルール: マルチステージ クエリを使用して、データ内の異なる期間を比較することで異常を検出できます。たとえば、最初のクエリ ステージを使用して長期間にわたってベースラインを確立し、後のステージを使用してそのベースラインに対する最近のアクティビティを評価できます。マルチイベント ルールを使用して、同様の比較を作成することもできます。
YARA-L のマルチステージ クエリは、ダッシュボードと検索の両方でサポートされています。
結合は、複数のソースのデータを関連付けて、調査のコンテキストをより多く提供するのに役立ちます。関連するイベント、エンティティ、その他のデータをリンクすることで、複雑な攻撃シナリオを調査できます。詳細については、検索で結合を使用するをご覧ください。
マルチステージ YARA-L 構文を定義する
マルチステージ クエリを構成する際は、次の点に注意してください。
- 制限ステージ: マルチステージ クエリには、ルートステージに加えて、1 ~ 4 個の名前付きステージを含める必要があります。
- 順序の構文: 名前付きステージの構文は、常にルートステージの構文の前に定義します。
マルチステージ YARA-L クエリを作成する
マルチステージ YARA-L クエリを作成する手順は次のとおりです。
ステージの構造と構文
[調査] > [検索] に移動します。クエリステージを定義する場合は、次の構造要件に従ってください。
構文: 次の構文を使用して、各ステージに名前を付け、他のステージと区別します。
stage <stage name> { }
中かっこ: すべてのステージ構文を中かっこ {} で囲みます。
順序: ルートステージを定義する前に、名前付きのすべてのステージの構文を定義します。
参照: 各ステージは、クエリで前に定義されたステージを参照できます。
ルートステージ: クエリにはルートステージが必要です。ルートステージは、すべての名前付きステージの後に処理されます。
次のステージの例 daily_stats は、毎日のネットワーク統計情報を収集します。
stage daily_stats {
metadata.event_type = "NETWORK_CONNECTION"
$source = principal.hostname
$target = target.ip
$source != ""
$target != ""
$total_bytes = cast.as_int(network.sent_bytes + network.received_bytes)
match:
$source, $target by day
outcome:
$exchanged_bytes = sum($total_bytes)
}
アクセス ステージの出力
名前付きステージの出力は、ステージ フィールドを使用して後続のステージからアクセスできます。ステージ フィールドは、ステージの match 変数と outcome 変数に対応しており、統合データモデル(UDM)フィールドと同様に使用できます。
ステージ フィールドにアクセスするには、次の構文を使用します。
$<stage name>.<variable name>
アクセス ウィンドウのタイムスタンプ(省略可)
名前付きステージでホップ ウィンドウ、スライディング ウィンドウ、タンブリング ウィンドウを使用する場合は、次の予約済みフィールドを使用して、各出力行のウィンドウの開始とウィンドウの終了にアクセスします。
$<stage name>.window_start$<stage name>.window_end
window_start と window_end は、Unix エポックからの経過秒数で表される整数フィールドです。さまざまなステージのウィンドウのサイズは異なる場合があります。
制限事項
マルチステージ クエリには、次の機能的および構造的な制約があります。
構造とステージの上限
ルートステージ: クエリごとに許可されるルートステージは 1 つだけです。
名前付きステージ: 最大 4 つの名前付きステージがサポートされています。
ステージ参照: ステージは、同じクエリ内で論理的に前に定義されたステージのみを参照できます。
結合: すべてのステージで、データテーブル以外の結合は最大 4 つまで許可されます。
結果の要件: 名前付きの各ステージ(ルートステージを除く)には、
matchセクションまたはoutcomeセクションのいずれかを含める必要があります。outcomeセクションには集計は必要ありません。
ウィンドウと互換性の上限
機能のサポート: マルチステージ クエリは、検索とダッシュボードでサポートされていますが、ルールではサポートされていません。
ウィンドウ タイプ: 1 つのクエリ内で異なるウィンドウ タイプを混在させないでください。
ウィンドウの依存関係: ホップ ウィンドウまたはスライディング ウィンドウを使用するステージは、ホップ ウィンドウまたはスライディング ウィンドウを使用する別のステージに依存できません。
タンブリング ウィンドウ サイズ: さまざまなステージのタンブリング ウィンドウのサイズは異なる場合がありますが、サイズの差は 720 倍未満でなければなりません。
例: ステージの集計の差
次のウィンドウ構成の例は許可されていません。
stage monthly_stats {
metadata.event_type = "NETWORK_CONNECTION"
$source = principal.hostname
$target = target.ip
$source != ""
$target != ""
$total_bytes = cast.as_int(network.sent_bytes + network.received_bytes)
match:
$source, $target by month
outcome:
$exchanged_bytes = sum($total_bytes)
}
$source = $monthly_stats.source
$target = $monthly_stats.target
match:
$source, $target by minute
monthly_stats ステージでデータを月単位で集計し、ルート ステージで monthly_stats の出力を分単位で集計する場合、monthly_stats の各行はルート ステージの 43,200 行にマッピングされます(1 か月は 43,200 分であるため)。
ステージとクエリの制限事項
マルチステージ クエリ内の各ステージには、次の制約があります。
単一ステージ クエリに適用される制限のほとんどは、個々のステージにも適用されます。
出力要件: 各ステージで、少なくとも 1 つの一致変数または結果変数(ステージ フィールド)を出力する必要があります。
結合のウィンドウ: 結合で使用される最大ウィンドウ サイズ(ホップ、タンブリング、スライディング)は 2 日です。
結果変数の最大数:
結果変数の上限を増やすことを選択していないお客様の場合は 20
結果変数の上限を増やすことを選択したお客様の場合は 50
マルチステージ クエリには、統計情報クエリと同じ制限が適用されます。
統計クエリ: 120 QPH(API と UI)
Google SecOps からのビューの検索: 1 分あたり 100 個のビュー
マルチステージ結合は、ユーザー インターフェースと
EventService.UDMSearchAPI でサポートされていますが、SearchService.UDMSearchAPI ではサポートされていません。結合のないマルチステージ クエリもユーザー インターフェースでサポートされています。
イベントとグローバルの制限事項
最大イベント数:
マルチステージ クエリでは、同時に処理できるイベントの数が厳しく制限されています。
UDM イベント: 最大 2 つの UDM イベントを指定できます。
エンティティ コンテキスト グラフ(ECG)イベント: ECG イベントは 1 つまで許可されます。
グローバル クエリの制限事項:
これらの上限は、マルチステージ クエリが返せるデータの範囲と量を制御するプラットフォーム全体の制約です。
クエリの期間の場合、標準クエリの最大期間は 30 日です。
結果セットの最大合計サイズは 10,000 件です。
マルチステージ クエリの例
このセクションの例は、完全なマルチステージ YARA-L クエリを作成する方法を示すのに役立ちます。
例: 異常にアクティブなネットワーク接続を検索する(時間)
このマルチステージ YARA-L の例では、ネットワーク アクティビティが通常よりも高い IP アドレスのペアを特定し、3 時間以上高いアクティビティを維持しているペアをターゲットにしています。クエリには、名前付きステージ hourly_stats と root ステージの 2 つの必須コンポーネントが含まれています。
hourly_stats ステージは、ネットワーク アクティビティのレベルが高い principal.ip と target.ip のペアを検索します。
このステージでは、次のフィールドの 1 時間ごとの単一の値が返されます。
送信元 IP の統計情報(文字列):
$hourly_stats.src_ip宛先 IP の統計情報(文字列):
$hourly_stats.dst_ipイベント数の統計情報(整数):
$hourly_stats.count受信バイト数の標準偏差(浮動小数点数):
$hourly_stats.std_recd_bytes受信バイト数の平均(浮動小数点数):
$hourly_stats.avg_recd_bytes時間帯の開始時刻(Unix エポックからの秒数(整数)):
$hourly_stats.window_startUnix エポックからの秒単位の 1 時間単位の終了時間(整数):
$hourly_stats.window_end
ルートステージは hourly_stats ステージの出力を処理します。$hourly_stats で指定されたしきい値を超えるアクティビティを含む principal.ip と target.ip のペアの統計情報を計算します。次に、アクティビティが 3 時間以上高いペアをフィルタします。
stage hourly_stats {
metadata.event_type = "NETWORK_CONNECTION"
$src_ip = principal.ip
$dst_ip = target.ip
$src_ip != ""
$dst_ip != ""
match:
$src_ip, $dst_ip by hour
outcome:
$count = count(metadata.id)
$avg_recd_bytes = avg(network.received_bytes)
$std_recd_bytes = stddev(network.received_bytes)
condition:
$avg_recd_bytes > 100 and $std_recd_bytes > 50
}
$src_ip = $hourly_stats.src_ip
$dst_ip = $hourly_stats.dst_ip
$time_bucket_count = strings.concat(timestamp.get_timestamp($hourly_stats.window_start), "|", $hourly_stats.count)
match:
$src_ip, $dst_ip
outcome:
$list = array_distinct($time_bucket_count)
$count = count_distinct($hourly_stats.window_start)
condition:
$count > 3
ルートステージの一致条件を次のように変更すると、マルチステージ クエリに日単位のウィンドウ集計を導入できます。
match:
$src_ip, $dst_ip by day
例: 異常にアクティブなネットワーク接続を検索する(Z スコアを使用)
このマルチステージ クエリは、Z スコアの計算(平均からの標準偏差の数を測定)を使用して、1 日の平均ネットワーク アクティビティと今日のアクティビティを比較します。このクエリは、内部アセットと外部システム間の異常に高いネットワーク アクティビティを効果的に検索します。
前提条件: 計算された Z スコアを有効にするには、クエリの時間枠が 2 日以上で、当日を含んでいる必要があります。
このマルチステージ クエリには、ネットワーク アクティビティの Z スコアを計算するために連携して動作する daily_stats ステージと root ステージが含まれています。
daily_statsステージは、毎日の初期集計を実行します。各 IP ペア(sourceとtarget)について、1 日に交換された合計バイト数を計算し、次のステージ フィールド(出力行の列に対応)を返します。$daily_stats.source: 単数、文字列$daily_stats.target: 単数、文字列$daily_stats.exchanged_bytes: 単数、整数$daily_stats.window_start: 単数、整数$daily_stats.window_end: 単数、整数
ルートステージは、各 IP ペアの
daily_statsステージの出力を集計します。検索範囲全体で交換された 1 日あたりのバイト数の平均と標準偏差、および本日交換されたバイト数を計算します。これらの 3 つの計算値を使用して Z スコアを決定します。出力には、今日すべての IP ペアの Z スコアが降順で表示されます。
// Calculate the total bytes exchanged per day by source and target
stage daily_stats {
metadata.event_type = "NETWORK_CONNECTION"
$source = principal.hostname
$target = target.ip
$source != ""
$target != ""
$total_bytes = cast.as_int(network.sent_bytes + network.received_bytes)
match:
$source, $target by day
outcome:
$exchanged_bytes = sum($total_bytes)
}
// Calculate the average per day over the time window and compare with the bytes
exchanged today
$source = $daily_stats.source
$target = $daily_stats.target
$date = timestamp.get_date($daily_stats.window_start)
match:
$source, $target
outcome:
$today_bytes = sum(if($date = timestamp.get_date(timestamp.current_seconds()), $daily_stats.exchanged_bytes, 0))
$average_bytes = window.avg($daily_stats.exchanged_bytes)
$stddev_bytes = window.stddev($daily_stats.exchanged_bytes)
$zscore = ($today_bytes - $average_bytes) / $stddev_bytes
order:
$zscore desc
ステージから集計されていない変数をエクスポートする
名前付きステージには、集計されていない outcome セクションを含めることができます。つまり、その outcome セクション内で定義された変数はステージから直接出力されるため、後続のステージはグループ化された集計を必要とせずに、ステージ フィールドとしてアクセスできます。
例: 集計されていない変数をエクスポートする
この例では、集計されていない変数をエクスポートする方法を示します。次のロジックに注意してください。
top_5_bytes_sentステージでは、ネットワーク アクティビティが最も高い 5 つのイベントを検索します。top_5_bytes_sentステージは、出力行の列に対応する次のステージ フィールドを出力します。$top_5_bytes_sent.bytes_sent: 単数、整数$top_5_bytes_sent.timestamp_seconds: 単数、整数
rootステージは、ネットワーク アクティビティが最も高い 5 つのイベントの最新のタイムスタンプと最も古いタイムスタンプを計算します。
stage top_5_bytes_sent {
metadata.event_type = "NETWORK_CONNECTION"
network.sent_bytes > 0
outcome:
$bytes_sent = cast.as_int(network.sent_bytes)
$timestamp_seconds = metadata.event_timestamp.seconds
order:
$bytes_sent desc
limit:
5
}
outcome:
$latest_timestamp = timestamp.get_timestamp(max($top_5_bytes_sent.timestamp_seconds))
$earliest_timestamp = timestamp.get_timestamp(min($top_5_bytes_sent.timestamp_seconds))
マルチステージ クエリでウィンドウ処理を実装する
マルチステージ クエリは、名前付きステージのすべてのタイプのウィンドウ処理(ホップ、スライディング、タンブリング)をサポートしています。名前付きステージにウィンドウが含まれている場合、次の予約済みフィールドを使用して、各出力行のウィンドウの開始と終了にアクセスできます。
$<stage name>.window_start$<stage name>.window_end
例: ホップ ウィンドウ
次の例は、マルチステージ クエリでホップ ウィンドウを使用する方法を示しています。
hourly_statsステージでは、同じ時間内にネットワーク アクティビティが高い IP ペアを検索します。hourly_statsは、出力行の列に対応する次のステージ フィールドを出力します。$hourly_stats.src_ip: 単数、文字列$hourly_stats.dst_ip: 単数、文字列$hourly_stats.count: 単数、整数$hourly_stats.std_recd_bytes: 単数、浮動小数点数$hourly_stats.avg_recd_bytes: 単数、浮動小数点数$hourly_stats.window_start: 単数、整数$hourly_stats.window_end: 単数、整数
ルートステージでは、アクティビティが 3 時間以上高い IP ペアが除外されます。
hourly_statsステージでホップ ウィンドウが使用されているため、時間が重複している可能性があります。
stage hourly_stats {
metadata.event_type = "NETWORK_CONNECTION"
$src_ip = principal.ip
$dst_ip = target.ip
$src_ip != ""
$dst_ip != ""
match:
$src_ip, $dst_ip over 1h
outcome:
$count = count(metadata.id)
$avg_recd_bytes = avg(network.received_bytes)
$std_recd_bytes = stddev(network.received_bytes)
condition:
$avg_recd_bytes > 100 and $std_recd_bytes > 50
}
$src_ip = $hourly_stats.src_ip
$dst_ip = $hourly_stats.dst_ip
$time_bucket_count = strings.concat(timestamp.get_timestamp($hourly_stats.window_start), "|", $hourly_stats.count)
match:
$src_ip, $dst_ip
outcome:
$list = array_distinct($time_bucket_count)
$count = count_distinct($hourly_stats.window_start)
condition:
$count > 3
既知の問題
マルチステージ クエリを実装する場合は、次の制限事項と推奨される回避策を確認することをおすすめします。
すべてのマルチステージ クエリは、統計情報検索クエリのように動作します(出力は、集計されていないイベントやデータテーブルの行ではなく、集計された統計情報で構成されます)。
UDM とエンティティ イベントを一方に含む結合は、データセットのサイズが大きいため、パフォーマンスが低下する可能性があります。結合の UDM イベントとエンティティ イベントの側をできるだけフィルタリングすること(イベントタイプでフィルタリングするなど)を強くおすすめします。
推奨されるプラクティスの一般的なガイダンスについては、Yara-L のベスト プラクティスをご覧ください。結合に固有の情報については、ベスト プラクティスをご覧ください。
さらにサポートが必要な場合 コミュニティ メンバーや Google SecOps のプロフェッショナルから回答を得ることができます。