Problèmes connus liés à 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 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()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 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 que2.0ou100.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 oucast.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) * 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)
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 quearray_distinct(...),max(...)oumin(...)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 sectionoutcome:à l'aide dearray_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 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 : 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 :
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.