Problèmes connus liés à 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 syntaxiques pour éviter les erreurs de compilation, 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é Exécuter 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 l'annulation de 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 l'expansion, 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 produit 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

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

Cela affecte les sommes, les nombres et les tableaux. Pour le nombre et le tableau, l'utilisation de count_distinct ou array_distinct peut résoudre le problème, mais il n'existe pas de solution de contournement pour la somme.

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 ayant $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 maintenir 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, la règle génère une erreur générique "Erreur de règle" (cannot complete [arithmetic/mod] operation between unsigned and signed integer).

Par exemple, la comparaison events suivante réussit 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 ce problème de non-concordance 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 des 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 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 coercition 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)
    

Variables de recherche et de correspondance de chaîne non sensibles à la casse

Dans la recherche Google Security Operations, les requêtes ne sont pas sensibles à la casse par défaut. Lorsque vous exécutez une requête statistique (à l'aide de match: et outcome:) avec la recherche non sensible à la casse activée, les variables de chaîne de la section match: sont automatiquement converties en minuscules (strings.to_lower()) pour le regroupement lors de la compilation de la requête.

Par conséquent, les colonnes match: de vos résultats de statistiques contiennent des valeurs en minuscules plutôt que la casse d'origine. Ce comportement peut être destructeur pour les champs de chaîne sensibles à la casse, tels que les lignes de commande encodées en Base64, les jetons, les URL ou les chemins sensibles à la casse, ce qui empêche un décodage ou une évaluation corrects.

Pour contourner ce comportement, effectuez l'une des opérations suivantes :

  • Champs bruts du projet dans les résultats : les champs projetés dans la section outcome: à l'aide d'agrégations telles que array_distinct(...), max(...) ou min(...) ne sont pas convertis en minuscules et conservent leur casse d'origine. Si vous devez afficher la casse d'origine des chaînes groupées, projetez le champ brut dans la section outcome: à l'aide de array_distinct(...).
  • Utiliser la recherche sensible à la casse : vous pouvez également passer du mode Recherche au mode Sensible à la casse. Notez que cela rend également le regroupement match: sensible à la casse.

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 : tous 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. Comme 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 direct 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 afin de laisser plus de temps pour 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.