Splunk – Filtrer ses logs avec un Lookup… efficacement !

Splunk Logo

Bonjour à tous, aujourd’hui je voudrais qu’on se penche sur un truc assez simple d’apparence qu’on a tous voulus faire avec Splunk : Filtrer ses logs avec un Lookup. Le use case que j’ai eu aujourd’hui au taf est assez simple. On avait une liste de domaine malveillant dans un petit CSV et on voulait croiser ça avec nos logs proxy (plusieurs dizaines de Go par jours) sur une semaine.

Filtrer ses logs avec un Lookup… sur des petits volumes de log !

Du coup le collègue qui a essayé en premier est parti avec la première méthode qu’on trouve sur Google (normal jusque-là). Faire une sous-recherche (subsearch) qui prend le lookup, « table » la colonne avec le nom qui match celui du champ dans les logs et cherches.

index=proxy [| inputlookup malwaredomainslist.csv | table site ]

Bilan : 1h après on avait pas fait 25% des logs et la recherche a même finis par planter en « Dag Execution Exception: Search has been cancelled. Search auto-canceled« . A ce stade il m’appelle, et je gratte un peu dans mes souvenirs. Il me semblait qu’on pouvait forger le texte de la recherche depuis une subsearch. Et je retombe sur la doc de return.

index=proxy [| inputlookup malwaredomainslist.csv | return 100 site ]

Même résultat, mais ca remis sur la piste de ce que je cherchais. Je retente avec format, qui me donne presque le même résultat que return ici.

index=proxy [| inputlookup malwaredomainslist.csv | format "" "" "" "" " OR " ""]

Si vous tester vous verrez que dans les deux cas les subsearch renvoies une chaine du type (aux parenthèses près) :

site="a.tld" OR site="b.tld" OR site="c.tld"

Bloom filter, tsidx et extraction de champs

A ce stade, il faut comprendre pourquoi la recherche initiale elle « pique sa mère » au niveau des performances. Je précise qu’ici je n’utilise pas de datamodel. Et il faut commencer par se rappeler que les extractions de champs dans Splunk sont réalisées au moment de la recherche (search time – contrairement à de l’ELK ou du QRadar).

Du coup, là ce qu’on demande à Splunk c’est un algo du type :

foreach $log in $index {
    $fields = regex($log,$lagrosseregexpourleslogsproxy)
    if $field.site="a.tld" OR $field.site="b.tld" OR $field.site="c.tld" {
       results += $log
    }
}

Et ça sur 500Go de log et quelques milliards de lignes, à peu près. On comprend mieux pourquoi ça prend du temps là non ? On ne veut pas faire ça, on veut tirer parti de la techno Splunk pour que ça aille vite et notamment des Bloom Filters. Pour vulgariser un bloom filter c’est une table de hashage probabiliste qui vous dit très rapidement si un élément « à des chances » d’être dans la table ou pas. Le côté probabiliste est fait de tel manière que les faux positifs sont possibles mais pas faux négatif.

Pour revenir à notre index Splunk, ça signifie qu’on peut demander à Splunk si du texte est présent dans un bucket. Je simplifie, le tsdix c’est un gros bloomfilter (dopé à l’EPO par Splunk) sur les buckets de votre index. Je rappel ici que Splunk range les logs dans un index par paquet : des buckets (pour notre exemple un bucket par paquet 10Go de log), ordonné dans le temps.

Donc notre algo d’avant, on voudrait plutôt que ce soit :

foreach $bucket  in $index {
    foreach $keyword in $searchterms{
        if bloom_find($keyword, $bucket) {
            results += check_bucket_for_terms($bucket,$keyword)
        } 
    }
}

Ici au lieu de se taper les 500Go de logs à grand coup de Regex, Splunk va avoir 50 buckets de 10Go à contrôler avec ses bloom filters (tsdix) et peut-être en contrôler 3, avec 2 qui contiendront effectivement les termes qui nous intéresses. La techno bloom nous garantit que le termes n’est pas dans les 47 autres buckets.

Bon, et ça marche de Filtrer ses logs avec un Lookup ?

Et pour éviter que Splunk ne lance les extractions de champs, il suffit ne pas spécifier le « champs= » dans la recherche – juste le texte que vous cherchez dans les logs : « A la Google ». Littéralement « index=proxy a.tld » c’est ici plus efficace que « index=proxy site=a.tld » (note: valable sur des gros volumes de logs et sans parler des datamodels). C’est super contre intuitif quand on a une formation et beaucoup de pratique en informatique classique (i.e. SQL et le big data) mais vos recherches vont déboiter à partir de maintenant. Je vous laisse tester.

Du coup dans la subsearch, il suffit de virer la partie qui ne nous intéresse pas avec un simple replace :

index=proxy [| inputlookup malwaredomainslist.csv | rename domain as "site" | format "" "" "" "" " OR " "" | eval search=replace(search,"site=","")]

Alors je ne vous fait pas languir plus : 1,106 s pour chercher dans mes 500go de logs la dizaine de site que qu’on cherchait. Voilà voilà voilà…

Pour ceux qui veulent aller plus loin sur le sujet je vous conseille de lire cette présentation de Splunk qui explique en détails comment ça fonctionne sous le capot.

https://conf.splunk.com/files/2016/slides/behind-the-magnifying-glass-how-search-works.pdf

Conclusion

Alors, celui là ça fait un moment que je voulais l’écrire mais je n’avais jamais trouvé le cas d’usage qui allait bien. Jusqu’à aujourd’hui (merci @PYL du coup). J’espère que ça vous aidera à mieux chercher dans vos logs déjà. Ensuite, ce qui est bien à filtrer ses logs avec un lookup, c’est que le lookup peut-être alimenté automatiquement (qui a dit Threat Intelligence ? 🙂 ) et avoir une alerte qui défonce pas les perfs de votre SIEM et qui s’applique sur des logs volumineux.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.