Problèmes connus de YARA-L 2.0

Compatible avec :

Ce document s'adresse aux ingénieurs en détection qui souhaitent déboguer la logique des règles et optimiser l'exécution de YARA-L 2.0. Il explique comment gérer les comportements non standards du moteur, tels que l'annulation de l'imbrication des champs, l'expansion du produit cartésien dans les agrégations et la cohérence à terme de l'enrichissement. En suivant ces méthodes, vous pouvez éviter les erreurs de logique qui entraînent des valeurs de résultat gonflées ou des détections manquées.

YARA-L 2.0 utilise un modèle d'exécution qui développe les champs répétés en lignes d'événements individuelles lors de l'évaluation. Comme cette transformation se produit au niveau du moteur, référencer plusieurs champs répétés ou effectuer des opérations arithmétiques sur des types UDM (Unified Data Model) non signés nécessite des solutions de contournement de syntaxe pour éviter les erreurs de compilateur, les erreurs d'exécution ou les ensembles de résultats incorrects. Ce document décrit ces contraintes techniques et les modèles logiques qui les résolvent.

Avant de commencer

Avant de tester ou de modifier des règles YARA-L 2.0, assurez-vous que votre compte dispose des droits techniques suivants :

Rôles IAM requis

  • roles/chronicle.viewer (Lecteur Security Operations) : pour afficher les règles existantes et les métadonnées de détection.
  • roles/chronicle.editor (éditeur d'opérations de sécurité) : pour modifier la logique des règles et enregistrer les modifications.

Autorisations requises

  • chronicle.rules.runTest : requis pour exécuter la fonctionnalité Lancer le test sur les données historiques.

  • chronicle.detections.get : pour inspecter le résultat des événements non imbriqués dans le tableau de bord de détection.

Terminologie clé

  • UDM (Unified Data Model) : schéma normalisé utilisé pour structurer toutes les données de télémétrie de sécurité ingérées sur la plate-forme.
  • Annulation de l'imbrication : expansion au niveau du moteur d'un seul événement UDM contenant un champ répété (tableau) sur plusieurs lignes. Chaque ligne représente un élément unique du tableau, ce qui peut entraîner une multiplication des lignes lors de l'évaluation de la règle.
  • T₀ (exécution initiale) : première exécution d'une règle sur la télémétrie entrante. Cela se produit pendant la phase de "streaming", souvent avant que les processus d'enrichissement en arrière-plan (comme les ajustements GeoIP ou ASN) ne soient finalisés.

Agrégations de résultats avec annulation de l'imbrication des champs répétés

Lorsqu'une règle fait référence à un champ répété dans une variable d'événement comportant plusieurs éléments, chaque élément est divisé en une ligne d'événement distincte.

Par exemple, les deux adresses IP du champ répété target.ip de l'événement $e sont divisées en deux instances de $e, chacune avec une valeur target.ip différente.

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
}

Enregistrements d'événements : avant et après l'annulation de l'imbrication

Les tableaux de cette section montrent comment un événement unique contenant un tableau d'adresses IP est transformé en deux enregistrements distincts.

Avant d'annuler l'imbrication

Le tableau suivant présente l'enregistrement d'événement avant d'annuler l'imbrication du champ répété :

metadata.id principal.application target.ip
aaaaaaaaa Google SecOps [192.0.2.20, 192.0.2.28]

Après l'annulation de l'imbrication

Le tableau suivant présente l'enregistrement d'événement après l'annulation de l'imbrication du champ répété :

metadata.id principal.application target.ip
aaaaaaaaa Google SecOps 192.0.2.20
aaaaaaaaa Google SecOps 192.0.2.28

Champs répétés imbriqués (produit cartésien)

Lorsqu'une règle fait référence à un champ répété imbriqué dans un autre, comme security_results.action, l'annulation de l'imbrication se produit simultanément aux deux niveaux (parent et enfant). Cela donne un produit cartésien de tous les éléments.

Dans l'exemple suivant, un événement $e avec deux valeurs répétées sur security_results et deux valeurs répétées sur security_results.actions est dissocié en quatre instances.

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
}

Enregistrement d'événement avant l'annulation de l'imbrication

L'enregistrement d'origine stocke les actions dans une structure de tableau imbriquée.

metadata.id principal.application security_results
aaaaaaaaa Google SecOps [ { actions: [ ALLOW, FAIL ] }, { actions: [ CHALLENGE, BLOCK ] } ]

Enregistrements d'événements après l'annulation de l'imbrication

Après le développement, chaque action unique devient une ligne distincte, ce qui peut entraîner des décomptes inattendus dans les agrégations non distinctes.

metadata.id principal.application security_results.actions
aaaaaaaaa Google SecOps AUTORISER
aaaaaaaaa Google SecOps ÉCHEC
aaaaaaaaa Google SecOps DÉFI
aaaaaaaaa Google SecOps BLOQUER

Impact sur les champs non liés

Ce comportement de suppression de l'imbrication dans l'évaluation des règles peut produire des agrégations de résultats inattendues lorsque la règle fait référence à un ou plusieurs champs répétés avec un champ parent qui est également un champ répété. Les agrégations non distinctes telles que sum(), array() et count() ne peuvent pas prendre en compte les valeurs en double dans d'autres champs du même événement, produites par le comportement de suppression de l'imbrication.

Dans l'exemple suivant, l'événement $e comporte un seul nom d'hôte (google.com), mais le résultat (hostnames) agrège quatre instances non imbriquées du même événement $e, chacune avec une valeur principal.hostname en double. Ce résultat génère quatre noms d'hôte (au lieu d'un) en raison de l'annulation de l'imbrication des valeurs répétées sur 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
}

Enregistrement d'événement avant l'annulation de l'imbrication avec des champs non liés

Le nom d'hôte est une valeur unique, mais il se trouve à côté des résultats de sécurité répétés.

metadata.id principal.application principal.hostname security_results
aaaaaaaaa Google SecOps google.com [ { action: [ ALLOW, FAIL ] }, { action: [ CHALLENGE, BLOCK ] } ]

Enregistrement d'événement après dissociation avec des champs non liés

Le nom d'hôte est désormais dupliqué sur quatre lignes, ce qui amène la fonction array() à le récupérer quatre fois.

metadata.id principal.application principal.hostname security_results.action
aaaaaaaaa Google SecOps google.com AUTORISER
aaaaaaaaa Google SecOps google.com ÉCHEC
aaaaaaaaa Google SecOps google.com DÉFI
aaaaaaaaa Google SecOps google.com BLOQUER

Solution pour le comportement d'annulation de l'imbrication

Pour vous assurer que les valeurs de vos résultats sont exactes lorsque l'annulation de l'imbrication se produit, utilisez la version distincte de l'agrégation sélectionnée. Les fonctions suivantes ignorent les lignes en double créées par l'annulation de l'imbrication :

  • max()
  • min()
  • array_distinct()
  • count_distinct()

Agrégations de résultats avec plusieurs variables d'événement

Si une règle contient plusieurs variables d'événement, l'agrégation comporte un élément distinct pour chaque combinaison d'événements incluse dans la détection. Par exemple, si l'exemple de règle suivant est exécuté sur les événements listés :

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"

La somme est calculée sur chaque combinaison d'événements, ce qui vous permet d'utiliser les deux variables d'événement dans les calculs de la valeur du résultat. Les éléments suivants sont utilisés dans le calcul :

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

La somme maximale potentielle est donc de 6, même si $e2 ne peut correspondre qu'à trois événements distincts.

Cela affecte les sommes, les nombres et les tableaux. Pour les types "count" et "array", l'utilisation de count_distinct ou array_distinct peut résoudre le problème, mais il n'existe pas de solution de contournement pour le type "sum".

Parenthèses au début d'une expression

Il n'est pas possible de commencer une expression par des parenthèses. Cela déclenche une erreur d'analyse dans l'éditeur de règles.

Syntaxe non valide

parsing: error with token: ")"
invalid operator in events predicate

L'exemple suivant génère ce type d'erreur :

($event.metadata.ingested_timestamp.seconds -
$event.metadata.event_timestamp.seconds) / 3600 > 1

Variantes syntaxiques valides

Les variantes de syntaxe suivantes renvoient le même résultat, mais avec une syntaxe valide :

$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

Le tableau d'index dans le résultat nécessite une agrégation

L'indexation directe d'un tableau dans la section outcome pour les champs répétés n'est pas autorisée. Elle nécessite une variable d'espace réservé temporaire.

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

Solution

Capturez l'index de tableau spécifique dans une variable d'espace réservé dans la section events, puis référencez cet espace réservé dans votre résultat.

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

outcome:
  $principal_user_department = $principal_user_dept

Condition OR avec non-existence

Si vous appliquez une condition OR entre deux variables d'événement distinctes et que la règle correspond à une non-existence, la règle est compilée, mais peut produire des détections de faux positifs.

Par exemple, la syntaxe de règle suivante peut correspondre à des événements contenant $event_a.field = "something", même si ce n'est pas censé être le cas :

events:
     not ($event_a.field = "something" **or** $event_b.field = "something")
condition:
     $event_a and #event_b >= 0

Solution

Séparez les vérifications de non-existence en blocs individuels pour chaque variable afin de préserver l'intégrité logique.

events:
  not ($event_a.field = "something")
  not ($event_b.field = "something")

condition:
  $event_a and #event_b >= 0

Arithmétique avec des champs d'événement non signés

Si vous utilisez un entier littéral dans une opération arithmétique (+, -, *) ou modulo (%) avec un champ UDM d'entier non signé, vous obtenez une erreur. Les littéraux d'entiers standards sont par défaut des entiers signés de 64 bits (INT64), qui sont incompatibles avec les champs UDM définis comme des entiers non signés de 64 bits (UINT64), tels que network.received_bytes, network.sent_bytes et principal.file.size.

Cette contrainte de typage stricte s'applique aux sections events et outcome, y compris dans les agrégations sum() ou max() et les expressions de diviseur. Le vérificateur de syntaxe YARA-L traite les entiers signés et non signés comme un type int générique. Par conséquent, une règle qui mélange des champs UDM UINT64 et des littéraux d'entier INT64 peut réussir la validation de la syntaxe au moment de l'enregistrement, mais échouer lors de l'exécution de la règle ou lorsque vous cliquez sur Exécuter le test. En cas d'échec, elle génère une erreur générique "Rule Error" (cannot complete [arithmetic/mod] operation between unsigned and signed integer).

Par exemple, la comparaison events suivante passe la validation de la syntaxe lors de l'enregistrement, mais échoue lors de l'exécution, car $e.network.received_bytes (UINT64) est multiplié par le littéral entier signé 2 (INT64) :

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

Solutions

Vous pouvez résoudre cette incompatibilité de type de l'une des manières suivantes :

  • Casting d'entier explicite (cast.as_int() ou cast.as_uint()) : convertissez le champ UDM non signé en entier signé à l'aide de cast.as_int(), ou convertissez le littéral entier en entier non signé à l'aide de cast.as_uint(). Utilisez cette méthode lorsque vous avez besoin d'un résultat entier ou lorsque vous utilisez l'opérateur modulo (%), qui n'accepte que les opérandes entiers.
  • Contrainte de virgule flottante : forcez la constante entière à se comporter comme un float à l'aide d'un littéral à virgule flottante, tel que 2.0 ou 100.0. Dans les expressions simples sans autres opérateurs de division sur la même ligne, vous pouvez également utiliser une opération de division, telle que (2/1). Dans les expressions qui contiennent déjà des opérateurs de division (/), utilisez des littéraux à virgule flottante ou cast.as_int() au lieu de la contrainte de division (N/1). Plusieurs caractères / sur la même ligne peuvent être interprétés comme un littéral d'expression régulière, ce qui entraîne une erreur d'analyse lors de la compilation ou interrompt la coloration syntaxique dans l'éditeur de règles.

Solution de contournement dans la section events

Utilisez cast.as_int(), un littéral à virgule flottante ou une conversion de division pour corriger l'erreur dans la section events :

  • Utilisez cast.as_int() :

    events:
      $total_bytes = $e.network.total_bytes
      $total_bytes = cast.as_int($e.network.received_bytes) * 2
    
  • Utilisez un littéral à virgule flottante :

    events:
      $total_bytes = $e.network.total_bytes
      $total_bytes = $e.network.received_bytes * 2.0
    
  • Utilisez la coercition de division :

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

Solution de contournement dans la section outcome

L'expression outcome suivante est enregistrée sans erreur de syntaxe, mais échoue au moment de l'exécution, car sum($e.network.sent_bytes) et sum($e.network.received_bytes) renvoient des valeurs UINT64 qui sont combinées avec les littéraux INT64 signés 100 et 1 :

outcome:
  // Saves, but fails at runtime:
  $upload_ratio_pct = sum($e.network.sent_bytes) * 100 / (sum($e.network.received_bytes) + 1)

Réécrivez l'expression à l'aide de littéraux à virgule flottante ou de cast.as_int() afin qu'elle s'enregistre et s'exécute sans erreur :

  • Utilisez des littéraux à virgule flottante :

    outcome:
      $upload_ratio_pct = sum($e.network.sent_bytes) * 100.0 / (sum($e.network.received_bytes) + 1.0)
    
  • Utilisez 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)
    

Enrichissement GeoIP et cohérence à terme

Le système privilégie la vitesse à la précision immédiate lors des étapes d'enrichissement initiales (streaming et sensible à la latence), ce qui peut entraîner des données manquantes et de potentiels faux positifs. Le système continue d'enrichir les données en arrière-plan, mais il est possible qu'elles ne soient pas disponibles lors de l'exécution de la règle. Cela fait partie du processus normal de cohérence à terme.

Pour éviter les faux positifs causés par un retard d'enrichissement, vérifiez explicitement que le champ n'est pas vide avant d'évaluer sa valeur.

Prenons l'exemple de l'événement de règle suivant :

$e.principal.ip_geo_artifact.network.asn = "16509" AND
$e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom"

La règle repose sur le fait que l'événement doit comporter $e.principal.ip_geo_artifact.network.asn = "16509" ET $e.principal.ip_geo_artifact.location.country_or_region = "United Kingdom", qui sont tous deux des champs enrichis. Si l'enrichissement n'est pas terminé à temps, la règle génère un faux positif.

Pour éviter cela, une meilleure vérification de cette règle serait :

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

Cette règle élimine la possibilité que l'événement soit déclenché par des adresses IP avec l'ASN 16509, mais situées en dehors du Royaume-Uni. Cela améliore la précision globale de la règle.

Découvrez comment résoudre le problème de délai d'enrichissement.

Dépannage

Cette section décrit les performances attendues et fournit des solutions en libre-service pour les problèmes courants où le comportement de détection en direct diffère des résultats des tests.

Événements futurs

Les règles à événements multiples sont conçues pour traiter les événements dans l'ordre chronologique de leur ingestion. Si vous spécifiez et activez une règle multi-événement, elle ne crée pas de détections pour les événements dont le code temporel est futur, par exemple lorsque la date et l'heure de event.timestamp sont postérieures à celles de ingest.timestamp.

Délai d'enrichissement

Google SecOps donne la priorité à la vitesse d'ingestion pour exposer les alertes initiales le plus rapidement possible. Toutefois, les processus d'enrichissement en arrière-plan, tels que la résolution des métadonnées GeoIP, ASN ou UDM, suivent un modèle de cohérence à terme.

Exécution initiale (T₀)

Le moteur en direct peut évaluer une règle avant que l'enrichissement en arrière-plan ne soit terminé. Selon que votre logique s'appuie sur des champs enrichis pour les détections ou les exclusions, cela peut entraîner les écarts temporaires suivants :

  • Faux négatifs (retard de détection) : il s'agit d'un résultat courant. Si une règle dépend d'un champ enrichi pour se déclencher (par exemple, target.user.department == "Finance") et que ce champ est null, la règle ne correspond pas lors de l'exécution initiale.

  • Faux positifs (exclusion manquée) : si votre règle utilise des champs enrichis pour filtrer une activité connue (par exemple, NOT target.ip_geo_country == "US"), elle peut déclencher un faux positif, car les données d'exclusion n'ont pas encore été appliquées.

Exécutions de régularisation

Ces exécutions en arrière-plan réévaluent les données après un certain délai (par exemple, 4 heures ou 30 heures). Cela permet de "corriger" les états de détection comme suit :

  • Détections tardives : les événements qui étaient des "faux négatifs" à T₀ produisent désormais une détection une fois l'enrichissement finalisé.

  • Correction : les faux positifs T₀ restent dans le système, mais les données entièrement enrichies sont visibles dans l'outil de visualisation UDM pour un tri manuel.

Exécuter un test de divergence

L'outil Exécuter le test fonctionne sur des données historiques qui ont déjà été rapprochées. Étant donné que les données sont entièrement enrichies au moment où vous exécutez un test manuel, vous pouvez voir immédiatement les résultats de la régularisation. Cela signifie que vous ne verrez pas les faux négatifs T₀ ni les faux positifs basés sur l'exclusion qui se sont produits lors de l'exécution initiale en direct.

Correction des erreurs

Utilisez le tableau suivant pour résoudre les écarts entre les alertes en direct et les résultats des tests.

Problème Description Correction exploitable
Échec de l'exclusion Une règle se déclenche malgré une exclusion (par exemple, != "ASN_123"), car le champ était nul lors de l'exécution initiale. Ajoutez une vérification "not null" à la section des événements pour vous assurer que les données sont enrichies avant l'évaluation, par exemple :

$e.principal.ip_geo_artifact.network.asn != ""
Comparaison entre les correspondances réelles et les correspondances de test Les règles actives déclenchent des alertes, mais l'exécution d'un test sur les mêmes données affiche "No Results. Ajoutez $e.field != "", qui vérifie tous les champs enrichis (GeoIP, ASN, File Path) pour synchroniser le comportement en temps réel et l'historique.
Métadonnées manquantes Les détections s'affichent dans le tableau de bord avec des champs GeoIP ou File Path vides. Ce comportement est normal pour les exécutions T0. Pour résoudre ce problème, incluez une vérification field != "" ou configurez un délai de règlement dans votre calendrier de règles pour laisser plus de temps à l'ingestion.

Validation et test

Pour vérifier qu'une règle gère correctement l'enrichissement différé, procédez comme suit :

  1. Identifier le décalage : recherchez une détection que vous pensez être un faux positif. Dans la colonne Type de détection, recherchez l'icône <span class="material-icons">lightbulb</span>. Les alertes sans cette icône proviennent de l'exécution principale, où le délai d'enrichissement est le plus courant.

  2. Mettez à jour la logique de la règle : ajoutez une vérification field != "" pour tous les points de données enrichis utilisés dans votre logique.
    Exemple (chemin d'accès au fichier) :
    $e.target.process.parent_process.file.full_path != ""

  3. Tester et vérifier :

    • Utilisez la fonctionnalité Exécuter le test pour vous assurer que votre logique correspond toujours aux données historiques prévues.
    • Vérifiez que la règle ne se déclenche (ou n'exclut correctement) que lors des exécutions de régularisation une fois que les champs d'enrichissement sont renseignés.

Pour en savoir plus, consultez Comprendre la planification de l'exécution des règles et Configurer des planifications personnalisées pour les règles.

Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.