在 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 紀元以來經過的秒數表示。不同階段的視窗大小可能不同。
限制
多階段查詢有下列功能和結構限制:
結構和階段限制
根階段:每個查詢只能有一個根階段。
具名階段:最多支援四個具名階段。
參照階段:階段只能參照同一個查詢中,邏輯上定義在其之前的階段。
聯結:所有階段最多可有四個非資料表聯結。
結果規定:每個具名階段 (根階段除外) 都必須包含
match
區段或outcome
區段。outcome
部分不需要匯總。
視窗和相容性限制
功能支援:搜尋和資訊主頁支援多階段查詢,但規則不支援。
視窗類型:避免在單一查詢中混用不同視窗類型。
視窗依附元件:使用躍點或滑動視窗的階段,無法依附於同樣使用躍點或滑動視窗的另一個階段。
翻滾視窗大小:不同階段的翻滾視窗大小可能不同,但大小差異不得超過 720x。
範例:階段匯總差異
以下範例視窗設定不符合規定:
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 個資料列 (因為一個月有 43,200 分鐘)。
階段和查詢限制
多階段查詢中的每個階段都有下列限制:
適用於單一階段查詢的大部分限制,也適用於每個階段:
輸出規定:每個階段都必須輸出至少一個相符或結果變數 (階段欄位)。
彙整視窗:彙整作業使用的視窗大小上限 (跳躍、滾動或滑動) 為 2 天。
結果變數數量上限:
如果客戶未選擇允許較大的結果變數限制,則為 20
如果客戶選擇允許較大的結果變數限制,則為 50
多階段查詢的限制與統計資料查詢相同:
統計資料查詢:每小時 120 次查詢 (API 和 UI)
Google SecOps 的搜尋檢視畫面:每分鐘 100 個檢視畫面
使用者介面和
EventService.UDMSearch
API 支援多階段聯結,但SearchService.UDMSearch
API 不支援。使用者介面也支援不含聯結的多階段查詢。
活動和全域限制
事件數量上限:
多階段查詢可同時處理的事件數量有嚴格限制:
UDM 事件:最多可使用 2 個 UDM 事件。
實體內容圖 (ECG) 事件:最多可有一個 ECG 事件。
全球查詢限制:
這些限制是平台全域的限制,可控管多階段查詢可回溯的時間長度,以及可傳回的資料量。
查詢時間範圍:標準查詢的時間範圍上限為 30 天。
結果集總大小上限為 10,000 個結果。
多階段查詢範例
本節的範例有助於說明如何建立完整的 YARA-L 多階段查詢。
範例:搜尋異常活躍的網路連線 (小時)
這個多階段 YARA-L 範例會找出網路活動高於正常值的 IP 位址配對,並以活動持續時間超過三小時的配對為目標。查詢包含兩個必要元件:具名階段 hourly_stats
和 root
階段。
hourly_stats
階段會搜尋網路活動量高的 principal.ip
和 target.ip
配對。
這個階段會傳回下列欄位的單一每小時值:
來源 IP (字串) 的統計資料:
$hourly_stats.src_ip
目的地 IP 的統計資料 (字串):
$hourly_stats.dst_ip
事件數的統計資料 (整數):
$hourly_stats.count
接收位元組的標準差 (浮點數):
$hourly_stats.std_recd_bytes
平均接收位元組數 (浮點數):
$hourly_stats.avg_recd_bytes
以秒為單位的時段起始時間,自 Unix Epoch 紀元時間開始算起 (整數):
$hourly_stats.window_start
以秒為單位的時段結束時間 (自 Unix Epoch 紀元時間開始算起,以整數表示):
$hourly_stats.window_end
根階段會處理 hourly_stats
階段的輸出內容。系統會計算 principal.ip
和 target.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 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 分數計算,比較每日平均網路活動與今天的活動 (測量與平均值的標準差數量)。這項查詢可有效搜尋內部資產與外部系統之間異常高的網路活動。
必要條件:查詢時間範圍必須大於或等於 2 天,且包含當天,計算出的 Z 分數才會有效。
這項多階段查詢包含 daily_stats
階段和 root
階段,兩者會共同計算網路活動的 Z 分數:
daily_stats
階段會執行初始每日匯總作業。這個階段會計算每個 IP 配對 (source
和target
) 每天交換的總位元組數,並傳回下列階段欄位 (對應於輸出資料列中的資料欄):$daily_stats.source
:單數,字串$daily_stats.target
:單數,字串$daily_stats.exchanged_bytes
:單數、整數$daily_stats.window_start
:單數、整數$daily_stats.window_end
:單數、整數
根階段會彙整每個 IP 配對的
daily_stats
階段輸出內容。這項指標會計算整個搜尋範圍內每日交換位元組的平均值和標準差,以及今天交換的位元組。系統會使用這三個計算值來判斷 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
階段搜尋網路活動量最高的五個事件。top_5_bytes_sent
階段會輸出下列階段欄位,對應輸出資料列中的資料欄:$top_5_bytes_sent.bytes_sent
:單數、整數$top_5_bytes_sent.timestamp_seconds
:單數、整數
root
階段會計算網路活動量最高的五個事件,其最新和最早的時間戳記。
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 專業人員尋求答案。