YARA-L 2.0 已知問題
本文適用對象為偵測工程師,說明如何對規則邏輯進行偵錯,以及最佳化 YARA-L 2.0 執行作業。並說明如何處理非標準引擎行為,例如欄位取消巢狀結構、匯總中的笛卡爾乘積展開,以及擴充最終一致性。只要遵循這些方法,就能避免邏輯錯誤,以免結果值過高或偵測不到。
YARA-L 2.0 採用的執行模型,會在評估期間將重複欄位展開為個別事件列。由於這項轉換是在引擎層級進行,因此參照多個重複欄位或對未簽署的統一資料模型 (UDM) 類型執行算術時,需要使用語法解決方法,避免編譯器錯誤、執行階段錯誤或不正確的結果集。本文將概述這些技術限制,以及解決這些限制的邏輯模式。
事前準備
測試或修改 YARA-L 2.0 規則前,請確認帳戶具備下列技術權利:
必要的 IAM 角色
roles/chronicle.viewer(資安營運檢視者):可查看現有規則和偵測中繼資料。roles/chronicle.editor(資安營運編輯者):修改規則邏輯並儲存變更。
所需權限
chronicle.rules.runTest:必須具備這項權限,才能對歷來資料執行「執行測試」功能。chronicle.detections.get:在偵測資訊主頁中檢查未巢狀事件的輸出內容。
重要術語
- UDM (統合式資料模型):標準化結構定義,用於建構整個平台擷取的所有安全性遙測資料。
- 取消巢狀結構:在引擎層級,將包含重複欄位 (陣列) 的單一 UDM 事件展開為多列。每列代表陣列中的一個不重複元素,因此在評估規則時,資料列可能會增加。
- T₀ (初始執行):規則首次對傳入的遙測資料執行。這項作業會在「串流」階段進行,通常是在背景擴充程序 (例如 GeoIP 或 ASN 修正) 完成之前。
結果匯總,並取消巢狀結構重複欄位
如果規則參照事件變數中含有多個元素的重複欄位,每個元素都會分割成獨立的事件列。
舉例來說,事件 $e 的重複欄位 target.ip 中有兩個 IP 位址,系統會將其拆分為兩個 $e 執行個體,每個執行個體都有不同的 target.ip 值。
rule outbound_ip_per_app {
meta:
events:
$e.principal.application = $app
match:
$app over 10m
outcome:
$outbound_ip_count = count($e.target.ip) // yields 2.
condition:
$e
}
事件記錄:取消巢狀結構前後
本節的表格說明如何將包含 IP 位址陣列的單一事件,轉換為兩筆不同的記錄。
取消巢狀結構前
下表顯示取消巢狀結構前的事件記錄:
| metadata.id | principal.application | target.ip |
|---|---|---|
aaaaaaaaa |
Google SecOps |
[192.0.2.20、192.0.2.28] |
取消巢狀結構後
下表顯示取消巢狀結構後的事件記錄:
| metadata.id | principal.application | target.ip |
|---|---|---|
aaaaaaaaa |
Google SecOps |
192.0.2.20 |
aaaaaaaaa |
Google SecOps |
192.0.2.28 |
巢狀重複欄位 (笛卡爾乘積)
如果規則參照巢狀結構在另一個重複欄位中的重複欄位 (例如 security_results.action),則會同時在兩個層級 (父項和子項) 取消巢狀結構。這會產生所有元素的笛卡兒積。
在以下範例中,$e 事件在 security_results 上有兩個重複值,在 security_results.actions 上也有兩個重複值,這些值會取消巢狀結構,並分成四個例項。
rule security_action_per_app {
meta:
events:
$e.principal.application = $app
match:
$app over 10m
outcome:
$security_action_count = count($e.security_results.actions) // yields 4.
condition:
$e
}
巢狀結構取消巢狀結構前的事件記錄
原始記錄會將動作儲存在巢狀陣列結構中。
| metadata.id | principal.application | security_results |
|---|---|---|
aaaaaaaaa |
Google SecOps |
[ { actions: [ ALLOW, FAIL ] }、{ actions: [ CHALLENGE, BLOCK ] } ] |
巢狀結構取消巢狀結構後的事件記錄
展開後,每個不重複動作都會自成一列,這可能會導致非不重複匯總的計數不符合預期。
| metadata.id | principal.application | security_results.actions |
|---|---|---|
aaaaaaaaa |
Google SecOps |
允許 |
aaaaaaaaa |
Google SecOps |
不通過 |
aaaaaaaaa |
Google SecOps |
挑戰 |
aaaaaaaaa |
Google SecOps |
封鎖 |
對不相關領域的影響
如果規則參照一或多個重複欄位,且其父項欄位也是重複欄位,則規則評估中的這種取消巢狀結構行為可能會產生非預期的結果匯總。sum()、array() 和 count() 等非相異匯總,無法計入因取消巢狀結構行為而產生的同一事件其他欄位重複值。
在以下範例中,事件 $e 只有一個主機名稱 (google.com),但結果 (hostnames) 會匯總四個未巢狀結構的相同事件 $e 執行個體,每個執行個體都有重複的 principal.hostname 值。由於 security_results.actions 的重複值已取消巢狀結構,因此會產生四個主機名稱 (而非一個)。
rule security_action_per_app {
meta:
events:
$e.principal.application = $app
match:
$app over 10m
outcome:
$hostnames = array($e.principal.hostname) // yields 4.
$security_action_count = count($e.security_results.action) // yields 4.
condition:
$e
}
取消巢狀結構前,含有不相關欄位的事件記錄
主機名稱是單一值,但與重複的安全性結果並列。
| metadata.id | principal.application | principal.hostname | security_results |
|---|---|---|---|
aaaaaaaaa |
Google SecOps |
google.com |
[ { action: [ ALLOW, FAIL ] }、{ action: [ CHALLENGE, BLOCK ] } ] |
取消巢狀結構並加入不相關欄位後的事件記錄
主機名稱現在會重複四列,導致 array() 函式擷取四次。
| metadata.id | principal.application | principal.hostname | security_results.action |
|---|---|---|---|
aaaaaaaaa |
Google SecOps |
google.com |
允許 |
aaaaaaaaa |
Google SecOps |
google.com |
不通過 |
aaaaaaaaa |
Google SecOps |
google.com |
挑戰 |
aaaaaaaaa |
Google SecOps |
google.com |
封鎖 |
取消巢狀結構行為的解決方法
為確保取消巢狀結構時結果值正確無誤,請使用所選匯總的不重複版本。下列函式會忽略取消巢狀結構時建立的重複資料列:
max()min()array_distinct()count_distinct()
含有多個事件變數的結果匯總
如果規則包含多個事件變數,則偵測中包含的每個事件組合,在匯總中都會有獨立的項目。舉例來說,如果對列出的事件執行下列範例規則:
events:
$e1.field = $e2.field
$e2.somefield = $ph
match:
$ph over 1h
outcome:
$some_outcome = sum(if($e1.otherfield = "value", 1, 0))
condition:
$e1 and $e2
event1:
// UDM event 1
field="a"
somefield="d"
event2:
// UDM event 2
field="b"
somefield="d"
event3:
// UDM event 3
field="c"
somefield="d"
系統會計算所有事件組合的總和,讓您在計算結果值時,同時使用這兩個事件變數。計算時會使用下列元素:
1: $e1 = event1, $e2 = event2
2: $e1 = event1, $e2 = event3
3: $e1 = event2, $e2 = event1
4: $e1 = event2, $e2 = event3
5: $e1 = event3, $e2 = event1
5: $e1 = event3, $e2 = event2
因此,即使 $e2 只能對應 3 個不同的事件,潛在總和仍可能達到 6。
這會影響總和、計數和陣列。如果是計數和陣列,使用 count_distinct 或 array_distinct 即可解決問題,但總和沒有解決方法。
運算式開頭的括號
運算式開頭不得使用括號,否則規則編輯器會觸發剖析錯誤。
語法無效
parsing: error with token: ")"
invalid operator in events predicate
以下範例會產生這類錯誤:
($event.metadata.ingested_timestamp.seconds -
$event.metadata.event_timestamp.seconds) / 3600 > 1
有效的語法變體
下列語法變化版本會傳回相同結果,但語法有效:
$event.metadata.ingested_timestamp.seconds / 3600 -
$event.metadata.event_timestamp.seconds / 3600 > 1
1 / 3600 * ($event.metadata.ingested_timestamp.seconds -
$event.metadata.event_timestamp.seconds) > 1
1 < ($event.metadata.ingested_timestamp.seconds -
$event.metadata.event_timestamp.seconds) / 3600
結果中的索引陣列需要匯總
您無法直接在 outcome 區段中為重複欄位建立陣列索引。需要暫時的預留位置變數。
outcome:
$principal_user_dept = $suspicious.principal.user.department[0]
解決方法
將特定陣列索引擷取至 events 區段中的預留位置變數,然後在結果中參照該預留位置。
events:
$principal_user_dept = $suspicious.principal.user.department[0]
outcome:
$principal_user_department = $principal_user_dept
OR 條件不存在
如果您在兩個獨立事件變數之間套用 OR 條件,且規則比對不存在,則規則會成功編譯,但可能會產生偽陽性偵測。
舉例來說,下列規則語法可能會比對出含有 $event_a.field = "something" 的事件,即使不應該這麼做:
events:
not ($event_a.field = "something" **or** $event_b.field = "something")
condition:
$event_a and #event_b >= 0
解決方法
將不存在檢查分隔成每個變數的個別區塊,以維持邏輯完整性。
events:
not ($event_a.field = "something")
not ($event_b.field = "something")
condition:
$event_a and #event_b >= 0
使用無正負號事件欄位進行算術運算
如果您在算術 (+、-、*) 或模數 (%) 運算中使用整數常值,且 UDM 欄位為無正負號整數,就會發生錯誤。標準整數常值預設為帶正負號的 64 位元整數 (INT64),與定義為不帶正負號 64 位元整數 (UINT64) 的 UDM 欄位不相容,例如 network.received_bytes、network.sent_bytes 和 principal.file.size。
這項嚴格的類型限制適用於 events 和 outcome 區段,包括 sum() 或 max() 匯總和除數運算式。YARA-L 語法檢查工具會將有正負號和無正負號的整數視為一般 int 類型。因此,如果規則混合 UINT64 UDM 欄位和 INT64 整數常值,可能會通過儲存時的語法驗證,但在執行規則或點選「執行測試」時失敗。規則失敗時,會產生一般「規則錯誤」(cannot complete [arithmetic/mod] operation between unsigned and signed integer)。
舉例來說,下列 events 比較在儲存時通過語法驗證,但在執行期間失敗,因為 $e.network.received_bytes (UINT64) 乘以帶正負號的整數常值 2 (INT64):
events:
$total_bytes = $e.network.total_bytes
$total_bytes = $e.network.received_bytes * 2
解決方法
您可以透過下列任一方法解決這項類型不符問題:
- 明確整數轉換 (
cast.as_int()或cast.as_uint()):使用cast.as_int()將不帶正負號的 UDM 欄位轉換為帶正負號的整數,或使用cast.as_uint()將整數常值轉換為不帶正負號的整數。如需整數結果,或使用僅接受整數運算元的模數 (%) 運算子,請使用這個方法。 - 浮點強制轉型:使用浮點常值 (例如
2.0或100.0),強制整數常數做為float。在同一行沒有其他除法運算子的簡單運算式中,您也可以使用除法運算,例如(2/1)。在已包含除法運算子 (/) 的運算式中,請使用浮點常值或cast.as_int(),而非(N/1)除法強制轉型。同一行中有多個/字元可能會被解讀為規則運算式常值,導致編譯期間發生剖析錯誤,或規則編輯器中的語法醒目顯示功能中斷。
events 節中的解決方法
使用 cast.as_int()、浮點常值或除法強制轉型,修正 events 區段中的錯誤:
使用
cast.as_int():events: $total_bytes = $e.network.total_bytes $total_bytes = cast.as_int($e.network.received_bytes) * 2使用浮點常值:
events: $total_bytes = $e.network.total_bytes $total_bytes = $e.network.received_bytes * 2.0使用除法強制轉型:
events: $total_bytes = $e.network.total_bytes $total_bytes = $e.network.received_bytes * (2/1)
outcome 節中的解決方法
下列 outcome 運算式儲存時不會發生語法錯誤,但在執行階段會失敗,因為 sum($e.network.sent_bytes) 和 sum($e.network.received_bytes) 會傳回與帶正負號 INT64 常值 100 和 1 結合的 UINT64 值:
outcome:
// Saves, but fails at runtime:
$upload_ratio_pct = sum($e.network.sent_bytes) * 100 / (sum($e.network.received_bytes) + 1)
請使用浮點常值或 cast.as_int() 重新編寫運算式,以便儲存及執行,而不會發生錯誤:
使用浮點常值:
outcome: $upload_ratio_pct = sum($e.network.sent_bytes) * 100.0 / (sum($e.network.received_bytes) + 1.0)使用
cast.as_int():outcome: $upload_ratio_pct = sum(cast.as_int($e.network.sent_bytes)) * 100 / (sum(cast.as_int($e.network.received_bytes)) + 1)
GeoIP 擴充和最終一致性
在初始擴充階段 (串流和延遲敏感),系統會優先考量速度,而非即時準確度,因此可能會遺漏資料,並產生偽陽性。系統會在背景繼續充實資料,但執行規則時可能無法使用資料。這是正常的最終一致性程序。
為避免因擴充延遲而誤判,請先明確檢查欄位是否為空,再評估其值。
舉例來說,假設有以下規則事件:
$e.principal.ip_geo_artifact.network.asn = "16509" AND
$e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom"
這項規則的前提是事件必須同時包含 $e.principal.ip_geo_artifact.network.asn = "16509" 和 $e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom",這兩個欄位都經過擴充。如果未及時完成擴充,規則就會產生偽陽性。
為避免這種情況,建議您改用下列方式檢查這項規則:
$e.principal.ip_geo_artifact.network.asn != "" AND
$e.principal.ip_geo_artifact.network.asn = "16509" AND
$e.principal.ip_geo_artifact.location.country_or_region != "" AND
$e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom"
這項規則可排除事件由 ASN 16509 IP 觸發,但位於英國境外的可能性。這能提升規則的整體精確度。
瞭解如何排解擴充延遲問題。
疑難排解
本節將說明預期效能,並提供自助式修正方法,協助您解決實際偵測行為與測試結果不符的常見問題。
未來活動
多事件規則會按照事件擷取時間,依序處理事件。如果您指定並啟用多事件規則,系統不會為時間戳記在未來的事件建立偵測結果,例如 event.timestamp 的日期和時間設在 ingest.timestamp 之後。
資料充實延遲時間
Google SecOps 會優先處理擷取速度,盡快顯示初始警示。不過,背景擴充程序 (例如解析 GeoIP、ASN 或 UDM 中繼資料) 則遵循最終一致性模型。
初始執行 (T₀)
即時引擎可能會在背景擴充完成前評估規則。如果邏輯仰賴擴充欄位進行偵測或排除,可能會導致下列暫時性差異:
偽陰性 (偵測延遲):這是常見的結果。如果規則需要依據擴充欄位 (例如
target.user.department == "Finance") 觸發,且該欄位為null,則規則在初始執行期間不會比對。偽陽性 (排除遺漏):如果規則使用擴充欄位篩除已知正常活動 (例如
NOT target.ip_geo_country == "US"),規則可能會觸發偽陽性,因為尚未套用「排除」資料。
調整執行作業
這些背景執行作業會在延遲一段時間 (例如 4 小時或 30 小時) 後重新評估資料。這會「校正」偵測狀態,如下所示:
延遲偵測:在 T₀ 時為「偽陰性」的事件,在擴充完成後會產生偵測結果。
更正:系統會保留所有 T₀ 偽陽性,但 UDM 檢視器會顯示完整擴充的資料,供手動分類。
執行測試差異
「執行測試」工具會對已對帳的歷來資料執行操作。執行手動測試時,資料已完全充實,因此您可以立即看到「真實」結果。也就是說,您不會看到在即時初始執行期間發生的 T₀ 偽陰性或排除型偽陽性。
錯誤修正
請參閱下表,解決即時警示和測試結果之間的差異。
| 問題 | 說明 | 可行的修正方式 |
|---|---|---|
| 排除失敗 | 即使有排除條件 (例如 != "ASN_123"),規則仍會觸發,因為該欄位在初始執行期間為空值。 |
在事件部分新增非空值檢查,確保資料在評估前已擴充,例如:$e.principal.ip_geo_artifact.network.asn != ""
|
| 正式與測試比對 | Live rules trigger alerts, but Run Test on the same data shows "No Results". |
新增 $e.field != "",檢查所有擴充欄位 (GeoIP、ASN、File Path),同步處理即時和歷來行為。 |
| 缺少中繼資料 | 偵測結果會顯示在資訊主頁中,GeoIP 或 File Path 欄位為空白。 |
這是 T0 執行的預期行為。如要修正問題,請在規則排程中加入 field != "" 檢查或設定結算延遲,讓系統有更多時間擷取資料。
|
驗證與測試
如要確認規則是否正確處理延遲擴充,請按照下列步驟操作:
找出標記:找出您認為是誤判的偵測結果。在「偵測類型」欄中,查看是否有
<span class="material-icons">lightbulb</span>圖示。如果快訊沒有這個圖示,則來自主要執行作業,這類作業最常發生擴充延遲。更新規則邏輯:針對邏輯中使用的所有擴充資料點,新增
field != ""檢查。
示例 (檔案路徑):
$e.target.process.parent_process.file.full_path != ""測試及驗證:
- 使用「執行測試」功能,確保邏輯仍與預期的歷來資料相符。
- 確認擴充欄位填入資料後,規則只會在精算執行期間觸發 (或正確排除)。
還有其他問題嗎?向社群成員和 Google SecOps 專家尋求解答。