Problèmes connus de YARA-L 2.0
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()oucast.as_uint()) : convertissez le champ UDM non signé en entier signé à l'aide decast.as_int(), ou convertissez le littéral entier en entier non signé à l'aide decast.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 que2.0ou100.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 oucast.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) * 2Utilisez un littéral à virgule flottante :
events: $total_bytes = $e.network.total_bytes $total_bytes = $e.network.received_bytes * 2.0Utilisez 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 estnull, 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 :
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.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 != ""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.