Créer des requêtes en plusieurs étapes dans YARA-L

Compatible avec :

Ce document explique comment les requêtes à plusieurs étapes dans YARA-L vous permettent d'intégrer la sortie d'une étape de requête directement dans l'entrée d'une étape ultérieure. Ce processus vous permet de mieux contrôler la transformation des données qu'une requête monolithique unique.

Intégrer les requêtes en plusieurs étapes aux fonctionnalités existantes

Les requêtes multi-étapes fonctionnent en parallèle avec les fonctionnalités existantes suivantes de Google Security Operations :

  • Règles de détection composite : les requêtes en plusieurs étapes complètent les règles de détection composite. Contrairement aux règles composites, les requêtes multi-étapes qui utilisent la recherche peuvent renvoyer des résultats en temps réel.

  • Plages de dates et règles multi-événements : vous pouvez utiliser des requêtes à plusieurs étapes pour détecter des anomalies en comparant différentes périodes dans vos données. Par exemple, vous pouvez utiliser vos étapes de requête initiales pour établir une référence sur une période prolongée, puis utiliser une étape ultérieure pour évaluer l'activité récente par rapport à cette référence. Vous pouvez également utiliser des règles multi-événements pour créer un type de comparaison similaire.

Les requêtes à plusieurs étapes en YARA-L sont compatibles avec les tableaux de bord et la recherche.

Les jointures permettent de corréler des données provenant de plusieurs sources afin de fournir plus de contexte pour une enquête. En associant des événements, des entités et d'autres données connexes, vous pouvez examiner des scénarios d'attaque complexes. Pour en savoir plus, consultez Utiliser des jointures dans la recherche.

Définir la syntaxe YARA-L à plusieurs étapes

Lorsque vous configurez une requête à plusieurs étapes, tenez compte des points suivants :

  • Étape de limite : les requêtes à plusieurs étapes doivent contenir entre une et quatre étapes nommées, en plus de l'étape racine.
  • Syntaxe de l'ordre : définissez toujours la syntaxe de l'étape nommée avant de définir la syntaxe de l'étape racine.

Créer une requête YARA-L en plusieurs étapes

Pour créer une requête YARA-L à plusieurs étapes, procédez comme suit.

Structure et syntaxe des étapes

Accédez à Investigation > Recherche. Suivez ces exigences structurelles lorsque vous définissez les étapes de votre requête :

Syntaxe : utilisez la syntaxe suivante pour nommer chaque étape et la séparer des autres :

stage <stage name> { }

  • Accolades : placez toute la syntaxe de l'étape entre accolades {}.

  • Order (Ordre) : définissez la syntaxe de toutes les étapes nommées avant de définir l'étape racine.

  • Références : chaque étape peut faire référence à des étapes définies plus tôt dans la requête.

  • Étape racine : une requête doit comporter une étape racine, qui est traitée après toutes les étapes nommées.

L'exemple de phase suivant, daily_stats, collecte les statistiques réseau quotidiennes :

stage daily_stats {
  metadata.event_type = "NETWORK_CONNECTION"
  $source = principal.hostname
  $target = target.ip
  $source != ""
  $target != ""
  $total_bytes = cast.as_int(network.sent_bytes + network.received_bytes)
  match:
    $source, $target by day
  outcome:
    $exchanged_bytes = sum($total_bytes)
}

Sortie de la phase d'accès

La sortie d'une étape nommée est accessible aux étapes suivantes à l'aide des champs d'étape. Les champs d'étape correspondent aux variables match et outcome de l'étape. Ils peuvent être utilisés de la même manière que les champs du modèle de données unifié (UDM).

Utilisez la syntaxe suivante pour accéder à un champ d'étape :

$<stage name>.<variable name>

Codes temporels de la période d'accès (facultatif)

Si une étape nommée utilise une fenêtre glissante, par saut ou cumulée, accédez au début et à la fin de la fenêtre pour chaque ligne de sortie à l'aide des champs réservés suivants :

  • $<stage name>.window_start

  • $<stage name>.window_end

window_start et window_end sont des champs entiers exprimés en secondes depuis l'epoch Unix. La taille des fenêtres peut varier selon l'étape.

Limites

Les requêtes à plusieurs étapes présentent les contraintes fonctionnelles et structurelles suivantes :

Limites structurelles et de phase

  • Étape racine : une seule étape racine est autorisée par requête.

  • Étapes nommées : vous pouvez créer jusqu'à quatre étapes nommées.

  • Référencement des étapes : une étape ne peut faire référence qu'à des étapes définies logiquement avant elle dans la même requête.

  • Jointures : un maximum de quatre jointures de tables non liées aux données est autorisé pour toutes les étapes.

  • Exigence de résultat : chaque étape nommée (à l'exception de l'étape racine) doit inclure une section match ou une section outcome. La section outcome ne nécessite pas d'agrégation.

Limites de fenêtre et de compatibilité

  • Compatibilité des fonctionnalités : les requêtes à plusieurs étapes sont compatibles avec Recherche et Tableaux de bord, mais pas avec Règles.

  • Types de fenêtres : évitez de mélanger différents types de fenêtres dans une même requête.

  • Dépendance de fenêtre : une étape utilisant une fenêtre glissante ou un saut ne peut pas dépendre d'une autre étape qui utilise également une fenêtre glissante ou un saut.

  • Taille de la fenêtre glissante : bien que la taille des fenêtres glissantes puisse varier d'une étape à l'autre, la différence de taille doit être inférieure à 720x.

Exemple : Différence d'agrégation des étapes

La configuration de fenêtre suivante n'est pas autorisée :

stage monthly_stats {
  metadata.event_type = "NETWORK_CONNECTION"
    $source = principal.hostname
    $target = target.ip
    $source != ""
    $target != ""
    $total_bytes = cast.as_int(network.sent_bytes + network.received_bytes)

  match:
    $source, $target by month

  outcome:
    $exchanged_bytes = sum($total_bytes)
}

$source = $monthly_stats.source
$target = $monthly_stats.target

match:
    $source, $target by minute

Si l'étape monthly_stats agrège les données par mois et que l'étape racine agrège la sortie de monthly_stats par minute, chaque ligne de monthly_stats correspond à 43 200 lignes dans l'étape racine (car il y a 43 200 minutes dans un mois).

Limites concernant les étapes et les requêtes

Chaque étape individuelle d'une requête à plusieurs étapes est soumise aux contraintes suivantes :

  • La plupart des limites qui s'appliquent à une requête à une seule étape s'appliquent également à chaque étape individuelle :

  • Les requêtes à plusieurs étapes sont soumises aux mêmes limites que les requêtes statistiques :

    • Requêtes de statistiques : 120 QPH (API et UI)

    • Vues de recherche depuis Google SecOps : 100 vues par minute

    • Les jointures à plusieurs étapes sont compatibles avec l'interface utilisateur et l'API EventService.UDMSearch, mais pas avec l'API SearchService.UDMSearch. Les requêtes à plusieurs étapes sans jointures sont également acceptées dans l'interface utilisateur.

Limites d'événement et globales

Nombre maximal d'événements :

Le nombre d'événements que les requêtes à plusieurs étapes peuvent traiter simultanément est strictement limité :

  • Événements UDM : deux événements UDM maximum sont autorisés.

  • Événements du graphique de contexte d'entité (ECG) : un événement ECG maximum est autorisé.

Limites globales des requêtes :

Ces limites sont des contraintes à l'échelle de la plate-forme qui contrôlent la période et la quantité de données qu'une requête à plusieurs étapes peut renvoyer.

  • Pour une période de requête, la période maximale pour une requête standard est de 30 jours.

  • La taille maximale de l'ensemble de résultats est de 10 000.

Exemples de requêtes en plusieurs étapes

Les exemples de cette section vous aideront à illustrer comment créer une requête YARA-L complète en plusieurs étapes.

Exemple : Rechercher les connexions réseau inhabituellement actives (heures)

Cet exemple YARA-L en plusieurs étapes identifie les paires d'adresses IP présentant une activité réseau supérieure à la normale, en ciblant les paires qui maintiennent une activité élevée pendant plus de trois heures. La requête inclut deux composants obligatoires : l'étape nommée hourly_stats et l'étape root.

L'étape hourly_stats recherche les paires principal.ip et target.ip avec des niveaux d'activité réseau élevés.

Cette étape renvoie une seule valeur horaire pour les champs suivants :

  • Statistiques pour l'adresse IP source (chaîne) : $hourly_stats.src_ip

  • Statistiques pour l'adresse IP de destination (chaîne) : $hourly_stats.dst_ip

  • Statistiques sur le nombre d'événements (entier) : $hourly_stats.count

  • Écart-type des octets reçus (float) : $hourly_stats.std_recd_bytes

  • Nombre moyen d'octets reçus (float) : $hourly_stats.avg_recd_bytes

  • Heure de début du bucket horaire en secondes depuis l'epoch Unix (entier) : $hourly_stats.window_start

  • Heure de fin du bucket horaire en secondes depuis l'époque Unix (entier) : $hourly_stats.window_end

L'étape racine traite la sortie de l'étape hourly_stats. Il calcule les statistiques pour les paires principal.ip et target.ip dont l'activité dépasse le seuil spécifié par $hourly_stats. Il filtre ensuite les paires présentant plus de trois heures d'activité élevée.


stage hourly_stats {
  metadata.event_type = "NETWORK_CONNECTION"
  $src_ip = principal.ip
  $dst_ip = target.ip
  $src_ip != ""
  $dst_ip != ""

  match:
    $src_ip, $dst_ip by hour

  outcome:
    $count = count(metadata.id)
    $avg_recd_bytes = avg(network.received_bytes)
    $std_recd_bytes = stddev(network.received_bytes)

  condition:
    $avg_recd_bytes > 100 and $std_recd_bytes > 50
}

$src_ip = $hourly_stats.src_ip
$dst_ip = $hourly_stats.dst_ip
$time_bucket_count = strings.concat(timestamp.get_timestamp($hourly_stats.window_start), "|", $hourly_stats.count)

match:
 $src_ip, $dst_ip

outcome:
 $list = array_distinct($time_bucket_count)
 $count = count_distinct($hourly_stats.window_start)

condition:
 $count > 3

Si vous modifiez la condition de correspondance dans l'étape racine comme suit, vous pouvez introduire une agrégation par jour pour la requête à plusieurs étapes.

match:
 $src_ip, $dst_ip by day

Exemple : Rechercher les connexions réseau inhabituellement actives (à l'aide du score Z)

Cette requête en plusieurs étapes compare l'activité réseau moyenne quotidienne à l'activité du jour à l'aide d'un calcul de score Z (qui mesure le nombre d'écarts-types par rapport à la moyenne). Cette requête recherche efficacement une activité réseau inhabituellement élevée entre les ressources internes et les systèmes externes.

Prérequis : La période de la requête doit être supérieure ou égale à deux jours et inclure le jour actuel pour que le score Z calculé soit efficace.

Cette requête à plusieurs étapes inclut les étapes daily_stats et root, qui fonctionnent ensemble pour calculer le score Z de l'activité réseau :

  • L'étape daily_stats effectue l'agrégation quotidienne initiale. Elle calcule le nombre total d'octets échangés chaque jour pour chaque paire d'adresses IP (source et target), puis renvoie les champs d'étape suivants (correspondant aux colonnes des lignes de sortie) :

    • $daily_stats.source : singulier, chaîne
    • $daily_stats.target : singulier, chaîne
    • $daily_stats.exchanged_bytes : singulier, entier
    • $daily_stats.window_start : singulier, entier
    • $daily_stats.window_end : singulier, entier
  • L'étape racine agrège la sortie de l'étape daily_stats pour chaque paire d'adresses IP. Il calcule la moyenne et l'écart-type des octets échangés quotidiennement sur l'ensemble de la plage de recherche, ainsi que les octets échangés aujourd'hui. Il utilise ces trois valeurs calculées pour déterminer le score Z.

  • La sortie liste les scores Z pour toutes les paires d'adresses IP du jour, triées par ordre décroissant.

// Calculate the total bytes exchanged per day by source and target

stage daily_stats {
  metadata.event_type = "NETWORK_CONNECTION"
  $source = principal.hostname
  $target = target.ip
  $source != ""
  $target != ""
  $total_bytes = cast.as_int(network.sent_bytes + network.received_bytes)
  match:
    $source, $target by day
  outcome:
    $exchanged_bytes = sum($total_bytes)
}

// Calculate the average per day over the time window and compare with the bytes
   exchanged today

$source = $daily_stats.source
$target = $daily_stats.target
$date = timestamp.get_date($daily_stats.window_start)

match:
  $source, $target

outcome:
  $today_bytes = sum(if($date = timestamp.get_date(timestamp.current_seconds()), $daily_stats.exchanged_bytes, 0))
  $average_bytes = window.avg($daily_stats.exchanged_bytes)
  $stddev_bytes = window.stddev($daily_stats.exchanged_bytes)
  $zscore = ($today_bytes - $average_bytes) / $stddev_bytes

order:
  $zscore desc

Exporter des variables non agrégées à partir d'étapes

Les étapes nommées peuvent inclure une section outcome non agrégée. Cela signifie que les variables définies dans cette section outcome sont générées directement à partir de l'étape, ce qui permet aux étapes suivantes d'y accéder en tant que champs d'étape sans nécessiter d'agrégation groupée.

Exemple : Exporter une variable non agrégée

Cet exemple montre comment exporter des variables non agrégées. Notez la logique suivante :

  • top_5_bytes_sent recherche les cinq événements ayant la plus forte activité réseau.

  • L'étape top_5_bytes_sent génère les champs d'étape suivants correspondant aux colonnes des lignes de sortie :

    • $top_5_bytes_sent.bytes_sent : singulier, entier
    • $top_5_bytes_sent.timestamp_seconds : singulier, entier
  • L'étape root calcule les codes temporels les plus récents et les plus anciens pour les cinq événements ayant la plus forte activité réseau.

stage top_5_bytes_sent {
  metadata.event_type = "NETWORK_CONNECTION"
  network.sent_bytes > 0

  outcome:
    $bytes_sent = cast.as_int(network.sent_bytes)
    $timestamp_seconds = metadata.event_timestamp.seconds

  order:
    $bytes_sent desc 
  
  limit:
    5
}

outcome:
  $latest_timestamp = timestamp.get_timestamp(max($top_5_bytes_sent.timestamp_seconds))
  $earliest_timestamp = timestamp.get_timestamp(min($top_5_bytes_sent.timestamp_seconds))

Implémenter le fenêtrage dans les requêtes à plusieurs étapes

Les requêtes à plusieurs étapes sont compatibles avec tous les types de fenêtrage (par saut, glissant et cumulatif) dans les étapes nommées. Si une étape nommée inclut une fenêtre, le début et la fin de la fenêtre pour chaque ligne de sortie sont accessibles à l'aide des champs réservés suivants :

  • $<stage name>.window_start
  • $<stage name>.window_end

Exemple : fenêtre de saut

L'exemple suivant montre comment utiliser les fenêtres de saut dans une requête à plusieurs étapes :

  • L'étape hourly_stats recherche les paires d'adresses IP qui présentent une activité réseau élevée au cours de la même heure.

  • hourly_stats génère les champs d'étape suivants correspondant aux colonnes des lignes de sortie :

    • $hourly_stats.src_ip : singulier, chaîne
    • $hourly_stats.dst_ip : singulier, chaîne
    • $hourly_stats.count : singulier, entier
    • $hourly_stats.std_recd_bytes : singulier, float
    • $hourly_stats.avg_recd_bytes : singulier, float
    • $hourly_stats.window_start : singulier, entier
    • $hourly_stats.window_end : singulier, entier
  • L'étape racine filtre les paires d'adresses IP présentant plus de trois heures d'activité élevée. Les heures peuvent se chevaucher en raison de l'utilisation d'une fenêtre récurrente dans l'étape hourly_stats.

stage hourly_stats {
  metadata.event_type = "NETWORK_CONNECTION"
  $src_ip = principal.ip
  $dst_ip = target.ip
  $src_ip != ""
  $dst_ip != ""

  match:
    $src_ip, $dst_ip over 1h

  outcome:
    $count = count(metadata.id)
    $avg_recd_bytes = avg(network.received_bytes)
    $std_recd_bytes = stddev(network.received_bytes)

  condition:
    $avg_recd_bytes > 100 and $std_recd_bytes > 50
}

$src_ip = $hourly_stats.src_ip
$dst_ip = $hourly_stats.dst_ip
$time_bucket_count = strings.concat(timestamp.get_timestamp($hourly_stats.window_start), "|", $hourly_stats.count)

match:
 $src_ip, $dst_ip

outcome:
 $list = array_distinct($time_bucket_count)
 $count = count_distinct($hourly_stats.window_start)

condition:
 $count > 3

Problèmes connus

Nous vous recommandons de consulter les limites et les solutions de contournement recommandées suivantes lorsque vous implémentez des requêtes à plusieurs étapes :

  • Toutes les requêtes à plusieurs étapes se comportent comme des requêtes de recherche de statistiques (la sortie se compose de statistiques agrégées plutôt que de lignes d'événements ou de tableaux de données non agrégées).

  • Les performances des jointures avec UDM et les événements d'entité d'un côté peuvent être faibles en raison de la taille de cet ensemble de données. Nous vous recommandons vivement de filtrer autant que possible les événements UDM et d'entité du côté de la jointure (par exemple, en filtrant sur le type d'événement).

Pour obtenir des conseils généraux sur les pratiques recommandées, consultez Bonnes pratiques concernant Yara-L. Pour obtenir des informations spécifiques aux jointures, consultez Bonnes pratiques.

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