בעיות מוכרות ב-YARA-L 2.0

המסמך הזה מיועד למהנדסי זיהוי שרוצים לנפות באגים בלוגיקה של כללים ולבצע אופטימיזציה של ההרצה של YARA-L 2.0. המאמר מסביר איך לטפל בהתנהגויות לא סטנדרטיות של מנועים, כמו ביטול קינון של שדות, הרחבה של מכפלה קרטזית בצבירות ומודל עקביות הדרגתי של העשרה. השיטות האלה יעזרו לכם למנוע שגיאות לוגיות שמובילות לערכי תוצאות מנופחים או להחמצת זיהויים.

ב-YARA-L 2.0 נעשה שימוש במודל ביצוע ספציפי שבו שדות חוזרים מורחבים לשורות אירועים נפרדות במהלך ההערכה. מכיוון שההמרה הזו מתבצעת ברמת המנוע, כדי להפנות לכמה שדות חוזרים או לבצע פעולות אריתמטיות על סוגי UDM לא חתומים, צריך להשתמש בפתרונות עקיפים ספציפיים לתחביר כדי להימנע משגיאות קומפילציה או מקבוצות תוצאות שגויות. במאמר הזה מפורטות המגבלות הטכניות האלה ודפוסי הלוגיקה הנדרשים כדי לפתור אותן.

לפני שמתחילים

לפני שבודקים או משנים כללי YARA-L 2.0, צריך לוודא שלחשבון יש את ההרשאות הטכניות הבאות:

התפקידים שצריך ב-IAM

  • roles/chronicle.viewer (Security Operations Viewer): כדי להציג כללים קיימים ומטא נתונים של זיהוי.
  • roles/chronicle.editor (עורך Security Operations): כדי לשנות את הלוגיקה של הכלל ולשמור את השינויים.

ההרשאות הנדרשות

  • chronicle.rules.runTest: נדרש להפעלת התכונה הפעלת בדיקה על נתונים היסטוריים.

  • chronicle.detections.get: כדי לבדוק את הפלט של אירועים לא מקוננים בלוח הבקרה של הזיהוי.

מונחים חשובים

  • מודל נתונים מאוחד (UDM): סכימת הנתונים המנורמלת שמשמשת למבנה של כל הטלמטריה של האבטחה שמוזנת לפלטפורמה.
  • ביטול הקינון: הרחבה ברמת המנוע של אירוע UDM יחיד שמכיל שדה חוזר (מערך) לכמה שורות. כל שורה מייצגת רכיב ייחודי מהמערך, מה שיכול להוביל להכפלת שורות במהלך הערכת הכלל.
  • T₀ (הפעלה ראשונית): ההפעלה הראשונה של כלל בטלמטריה נכנסת. הבעיה הזו מתרחשת במהלך שלב הסטרימינג, לרוב לפני שמושלמים תהליכי העשרה ברקע (כמו תיקונים של נתוני GeoIP או ASN).

צבירות של תוצאות עם ביטול הקינון של שדה חוזר

כשכלל מפנה לשדה חוזר במשתנה של אירוע עם כמה רכיבים, כל רכיב מתפצל לשורה נפרדת של אירוע.

לדוגמה, שתי כתובות ה-IP בשדה שחוזר על עצמו target.ip באירוע $e מפולחות לשני מופעים של $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 FAIL
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 FAIL
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

התוצאה היא סכום מקסימלי פוטנציאלי של 6, למרות ש-$e2 יכול להתאים רק ל-3 אירועים שונים.

השינוי הזה משפיע על הפונקציות sum,‏ count ו-array. במקרה של פונקציות count ו-array, אפשר להשתמש ב-count_distinct או ב-array_distinct כדי לפתור את הבעיה, אבל אין פתרון עקיף לפונקציה sum.

סוגריים בתחילת ביטוי

אי אפשר להתחיל ביטוי בסוגריים, ופעולה כזו תגרום לשגיאת ניתוח בכלי לעריכת כללים.

תחביר לא תקין

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 של שדות חוזרים. נדרש משתנה placeholder זמני.

outcome:
  $principal_user_dept = $suspicious.principal.user.department[0]

דרך לעקיפת הבעיה

מציבים את האינדקס הספציפי של המערך במשתנה placeholder בקטע events, ואז מפנים אל ה-placeholder בתוצאה.

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 שהסוג שלו הוא מספר שלם לא חתום, תקבלו שגיאה. לדוגמה:

events:
  $total_bytes = $e.network.received_bytes * 2

ברירת המחדל של קבועים של מספרים שלמים היא מספרים שלמים עם סימן, שלא תואמים לשדות UDM שמוגדרים כמספרים שלמים ללא סימן, כמו network.received_bytes.

דרך לעקיפת הבעיה

אפשר לעקוף את השגיאה הזו על ידי אילוץ הקבוע של המספר השלם להתנהג כמספר עשרוני באמצעות פעולת חילוק.

events:
  $total_bytes = $e.network.received_bytes * (2/1)

העשרה של נתוני GeoIP ומודל עקביות הדרגתי

בשלבי ההעשרה הראשוניים (Streaming ו-Latency-Sensitive), המערכת נותנת עדיפות למהירות על פני דיוק מיידי, מה שעלול להוביל לנתונים חסרים ולתוצאות חיוביות שגויות. המערכת ממשיכה להעשיר את הנתונים ברקע, אבל יכול להיות שהנתונים לא יהיו זמינים כשהכלל יופעל. זה חלק מתהליך מודל עקביות הדרגתי הרגיל.

כדי למנוע תוצאות חיוביות שגויות שנגרמות בגלל השהיה בהעשרת הנתונים, צריך לבדוק באופן מפורש שהשדה לא ריק לפני שמעריכים את הערך שלו.

לדוגמה, נניח את אירוע הכלל הבא:

$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"

הכלל הזה מבטל את האפשרות שהאירוע יופעל על ידי כתובות IP עם ASN 16509 שנמצאות מחוץ לבריטניה. כך משפרים את הדיוק הכולל של הכלל.

כך פותרים בעיות שקשורות לעיכוב בהעשרת הנתונים.

פתרון בעיות

בקטע הזה מפורטות ציפיות הביצועים ומוצעים פתרונות בשירות עצמי לבעיות נפוצות שבהן התנהגות הזיהוי בשידור חי שונה מתוצאות הבדיקה.

אירועים עם תאריך עתידי

כללים שמתייחסים לכמה אירועים נועדו לעבד אירועים בסדר כרונולוגי ביחס להוספה שלהם. אם מציינים ומפעילים כלל רב-אירועי, לא נוצרות זיהויים לאירועים עם חותמות זמן עתידיות, למשל כשהתאריך והשעה של event.timestamp מוגדרים אחרי ingest.timestamp.

עיכוב בהעשרה

ב-Google SecOps, מהירות ההטמעה היא בעדיפות גבוהה כדי לחשוף התראות ראשוניות כמה שיותר מהר. עם זאת, תהליכי העשרה ברקע, כמו פתרון מטא-נתונים של GeoIP,‏ ASN או UDM, פועלים לפי מודל עקביות הדרגתי.

הפעלה ראשונית (T₀)

יכול להיות שמנוע השידור החי יעריך כלל לפני שההעשרה ברקע תושלם. בהתאם ללוגיקה שלכם, אם היא מסתמכת על שדות מועשרים לזיהוי או להחרגות, יכול להיות שיהיו אי-התאמות זמניות מהסוגים הבאים:

  • תוצאות שליליות שגויות (פיגור בזיהוי): זו תוצאה נפוצה. אם כלל מסוים תלוי בשדה מועשר כדי להפעיל אותו (לדוגמה, target.user.department == "Finance"), והשדה הזה הוא null, הכלל לא יתאים במהלך ההפעלה הראשונית.

  • תוצאות חיוביות שגויות (פספוס החרגה): אם הכלל משתמש בשדות מועשרים כדי לסנן פעילות מוכרת (לדוגמה, NOT target.ip_geo_country == "US"), יכול להיות שהכלל יפעיל תוצאה חיובית שגויה כי נתוני ה'החרגה' עדיין לא הוחלו.

הפעלות של True-up

ההפעלות האלה ברקע מעריכות מחדש את הנתונים אחרי השהיה (לדוגמה, 45 דקות או 30 שעות). הפעולה הזו משנה את מצבי הזיהוי באופן הבא:

  • זיהויים מאוחרים: אירועים שהיו 'שליליים כוזבים' ב-T₀, ועכשיו מפיקים זיהוי אחרי שההעשרה מסתיימת.

  • תיקון: כל התוצאות החיוביות השגויות של T₀ נשארות במערכת, אבל הנתונים המועשרים במלואם גלויים בכלי לצפייה ב-UDM לצורך תעדוף ידני.

הפעלת בדיקה של פערים

הכלי Run Test פועל על נתונים היסטוריים שכבר עברו תהליך התאמה. הנתונים מועשרים באופן מלא בזמן שאתם מריצים בדיקה ידנית, ולכן אתם יכולים לראות את התוצאות של התיקון באופן מיידי. המשמעות היא שלא תראו את התוצאות השליליות הכוזבות של T₀ או את התוצאות החיוביות הכוזבות שמבוססות על החרגה שהתרחשו במהלך ההרצה הראשונית של הגרסה הפעילה.

תיקון שגיאות

הטבלה הבאה תעזור לכם לפתור בעיות שקשורות להבדלים בין התראות בזמן אמת לבין תוצאות הבדיקה.

שגיאה תיאור תיקון שניתן ליישום
החרגה נכשלה כלל מופעל למרות החרגה (לדוגמה, != "ASN_123") כי השדה היה ריק במהלך ההרצה הראשונית. מוסיפים בדיקה של not null לקטע events כדי לוודא שהנתונים מועשרים לפני ההערכה, לדוגמה:

$e.principal.ip_geo_artifact.network.asn != ""
השוואה בין משחק חי למשחק בדיקה כללים פעילים מפעילים התראות, אבל הפעלת הבדיקה על אותם נתונים מציגה "No Results". כדי לסנכרן את ההתנהגות בזמן אמת ואת ההיסטוריה, מוסיפים את $e.field != "" שבודק את כל השדות המועשרים (GeoIP,‏ ASN,‏ File Path).
מטא-נתונים חסרים הזיהויים מופיעים במרכז הבקרה עם שדות ריקים של GeoIP או File Path. זה צפוי להתרחש בהרצות של T0. כדי לפתור את הבעיה, צריך לכלול בדיקה של field != "" או להגדיל את ההיסט של ההפעלה הראשונה בלוח הזמנים של ההפעלה כדי לאפשר יותר זמן להטמעה.

אימות ובדיקה

כדי לוודא שכלל מטפל בצורה נכונה בהעשרה מושהית, מבצעים את הפעולות הבאות:

  1. זיהוי ההשהיה: מאתרים זיהוי שלדעתכם הוא תוצאה חיובית כוזבת. בעמודה סוג הזיהוי, מחפשים את הסמל <span class="material-icons">lightbulb</span>. התראות בלי הסמל הזה הן מההרצה הראשונית, שבה השהיית ההעשרה נפוצה יותר.

  2. עדכון הלוגיקה של הכלל: מוסיפים בדיקה של field != "" לכל נקודות הנתונים המועשרים שמשמשות בלוגיקה.
    דוגמה (נתיב לקובץ):
    $e.target.process.parent_process.file.full_path != ""

  3. בדיקה ואימות:

    • כדי לוודא שהלוגיקה עדיין תואמת לנתונים ההיסטוריים המיועדים, משתמשים בתכונה הפעלת בדיקה.
    • מוודאים שהכלל מופעל עכשיו רק במהלך הפעלות של עדכון נתונים (או שהוא לא מופעל במהלך הפעלות כאלה), אחרי ששדות ההעשרה מתמלאים.

פרטים נוספים מופיעים במאמרים ניהול לוח הזמנים להרצת הכללים והגדרה של לוחות זמנים מותאמים אישית לכללים.

הבעיה עדיין לא נפתרה? קבלת תשובות מחברי הקהילה וממומחי Google SecOps.