10 ans de Suricata

MISC n° 100 | novembre 2018 | Eric Leblond
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Pas besoin d’avoir fait des lettres LA Teen pour savoir que 10 ans est une étape importante. Le projet commencé en 2008 par Victor Julien sous le nom de VIPS pour Victor’s IPS a bien changé. Son évolution fonctionnelle témoigne de la transformation des menaces et les techniques de sécurisation du développement utilisées sont la preuve des progrès réalisés dans ce domaine.

Le premier commit dans le suivi de versions de Suricata [SURI] date de juillet 2008. Ce projet open source communautaire existe et évolue depuis plus de 10 ans. L’idée de départ était de développer un moteur de détection d’intrusion réseau basé sur des signatures. Comme l’ancêtre Snort, mais avec des choix technologiques différents. Le projet Suricata peut d’ailleurs remercier les auteurs de Snort. Même si les deux logiciels ne partagent pas une ligne de code, c’est grâce à la gestion de la communauté de Snort que Suricata a vu le jour. La politique de Sourcefire (puis de Cisco après le rachat) consiste à rejeter l'essentiel des contributions de code venant de l’extérieur de la société. C’est cet enfermement qui a conduit un groupe formé autour de Victor Julien à considérer le développement d’une alternative. Le projet Suricata est ainsi né pour réaliser un IDS réseau partageant le même langage de signature que Snort, mais avec un véritable respect de la communauté. Les trois premiers choix techniques forts ont été le multi-fils (multithreading pour les intimes), un support avancé du protocole HTTP, et une reconnaissance protocolaire indépendante du port.

1. Une évolution constante

Le démarrage du développement de Suricata a été possible grâce à l’obtention de fonds publics. Une fondation à but non lucratif l’Open Information Security Foundation [OISF] a été créée pour les recevoir et elle s’occupe toujours de l’avenir de Suricata en finançant des développeurs et en organisant la promotion. Le financement a évolué et est maintenant principalement privé grâce aux sociétés membres du consortium. La fondation et les sociétés qui font partie du consortium jouent un rôle important, mais le développement de Suricata a été et reste essentiellement communautaire. Des propositions révolutionnaires venant de l’extérieur de la fondation qui seront détaillées par la suite ont profondément changé la face du logiciel. Elles l’ont fait évoluer lors de ces 10 années pour rester cohérent, attractif et apte à répondre aux menaces. Ce chapitre passe volontairement sous silence les améliorations en termes de performances, les évolutions des méthodes de captures pour se focaliser sur les changements fonctionnels.

Fig. 1 : Historique des versions majeures de Suricata.

1.1 Suricata 1.0 : welcome to the HTTP world

Si le support du multithreading est un avantage en termes de performances, la compréhension du HTTP en est le principal apport fonctionnel. Suricata 1.0 était ainsi capable de lire un jeu de règles Snort, mais disposait en plus d’une série de mots clefs pour chercher dans les champs protocolaires de HTTP.

Les signatures pouvaient pour la première fois demander une valeur d’un champ protocolaire comme l’URI sans avoir à réaliser la décomposition elle-même. Ceci diminue la complexité d’écriture d’une signature et augmente en même temps les performances, car des techniques d’optimisation sont mises en oeuvre pour accélérer la recherche dans ces champs spécifiques. Un autre point fonctionnel important est la reconnaissance protocolaire qui repose sur une analyse du début d’un échange pour en déterminer le protocole indépendamment du port. Cela a eu un impact considérable en terme de taux de détection de Suricata, car les logiciels malveillants de l’époque utilisaient souvent des ports hauts pour se connecter aux serveurs de contrôle et ensuite échanger en HTTP.

Suricata 1.0 est publié en juillet 2010 après deux ans de développement. Les points précédemment mentionnés sont là et Suricata offre également des capacités de détection multi étapes grâce aux mots clefs flowbits, flowvar et flowint. Ces mots clefs sont une première phase pour dépasser la faiblesse du langage des signatures héritée de Snort. Sa très faible expressivité (pas de branchement possible par exemple) rend en effet complexe, voire impossible, toute analyse avancée. La famille de mots clefs flowbits fournit un moyen de passer de l’information entre signatures et donc de construire un moteur à états. Il est limité certes à décrire des états au sein d’un même flux, mais cela est déjà un progrès notable. L’approche retenue est plus restreinte que celle de Snort qui utilise des règles écrites en langage C, mais la mise en oeuvre des variables flow est beaucoup plus simple.

Une fonctionnalité fait également son apparition et elle est en rupture avec la définition stricte d’un IDS. Suricata 1.0 est capable de journaliser les requêtes HTTP à la façon d’un serveur Apache (ndla : l’ancêtre de Nginx pour nos plus jeunes lecteurs). Ce n’était pas dans les spécifications initiales, mais ce n’était pas compliqué à faire et le coût en performance était moindre. Cette approche opportuniste restera typique du développement de Suricata.

1.2 Suricata 1.2 : extraction de fichiers

La version 1.2 (janvier 2012) et la version 1.3 (juillet 2012) sont dominées par l’ajout de l’extraction de fichiers. La compréhension du protocole HTTP étant suffisante pour voir ce qui transite dans les requêtes, il est apparu en fait naturel de réaliser une analyse des fichiers échangés. Ce qui fut fait avec le calcul de somme de contrôle et l’extraction des fichiers dans la version 1.2. La version 1.3 ajoutera quant à elle le mot clef filemd5 qui vérifie si le md5 d’un fichier échangé appartient à une liste stockée dans un fichier passé en argument. Il sera étendu plus tard avec les mots clefs filesha1 et filesha256.

La version 2.1 verra elle l’ajout de l’extraction pour SMTP. NFS arrivera avec la version 4.0 et Samba et FTP avec la version 4.1.

1.3 Suricata 1.3 : TLS

La version 1.3 sort en 2012 et apporte le support de TLS grâce à une contribution de Pierre Chifflier alors en poste à l’ANSSI. Il ne s’agit de déchiffrement, mais d’une analyse de la négociation TLS avec extraction d’informations comme le sujet du certificat, son signataire et son empreinte. C’est une étape importante puisque pour la première fois Suricata s’écarte totalement de la vue d’un IDS comme présentateur de données. Ici, le décodage est complexe et extrait des données qui ne sont pas visibles à l’oeil nu sur le réseau. La première approche du même type avait été la décompression des corps de messages HTTP, mais il s’agissait d’une transformation directe de la donnée.

Les fonctionnalités TLS ont été utilisées pour répondre à l’évolution des logiciels malveillants qui avaient commencé à utiliser le chiffrement Des signatures sont disponibles pour détecter des connexions vers des serveurs utilisant par exemple les configurations par défaut de OpenSSL. Une autre approche intéressante a été celle de Abuse.ch qui a utilisé le mot clef de détection d’empreinte de certificats pour publier et maintenir une série de règles référençant l’empreinte de certificats utilisés par des serveurs de contrôles de logiciels malveillants [ABUSE].

Des mots clefs dédiés à TLS font leur apparition et les événements TLS sont journalisés dans un fichier dédié. L’approche mixte de détection d’intrusion et surveillance réseau orientée sécurité commencée avec le HTTP se confirme avec cet ajout. Cette approche restera la norme : un support protocolaire complet est composé de l’identification dynamique du protocole, de la journalisation des événements, des mots clefs dédiés et de l’extraction des fichiers si le protocole s’y prête.

L’évolution du support de TLS s’est poursuivie au fil des versions. L’extraction sur disque de certificats a été ajoutée dans Suricata 1.4 et les données extraites ont été complétées progressivement (validité, serveur name indication). La version 4.1 apporte sur le support de JA3 qui est une solution d’identification des clients TLS [JA3].

1.4 Suricata 1.4 : Lua est approuvé

Il faudra attendre Suricata 1.4 en juillet 2012 pour voir un réel bond fonctionnel au niveau des signatures avec l’ajout du support de Lua. Elles peuvent désormais avoir pour filtre un script Lua qui récupère des tampons exposés par Suricata (contenu du paquet, sujet TLS...) et dont la valeur de retour détermine s’il y a correspondance ou pas. Le script Lua peut également créer et modifier des variables de flowbits et c’est donc un véritable langage de programmation combiné à une sauvegarde d’états qui est disponible. Un champ de possibilités énorme est alors ouvert. Le support lua est utilisé par exemple pour détecter de manière fiable la faille Heartbleed dès le jour de sa sortie, là où les techniques classiques n’ont qu’une détection partielle. Malheureusement, Lua n’aura pas eu le succès escompté pour une raison triviale. Le script lua utilisé par une signature doit être distribué avec la règle et l’ajout de ce type de fichier n’était pas prévu par les gestionnaires de signatures. Ces derniers ont pour rôle de télécharger les nouvelles signatures, d’appliquer les modifications (désactivation de signatures ou de catégorie de signatures) et de déclencher une mise à jour du jeu de signature de l’IDS. Malheureusement, les outils disponibles ne supportaient pas la gestion de ces fichiers accompagnant les signatures. Le support de Lua a donc été limité au niveau de son usage et de sa diffusion par manque de support au niveau de l’infrastructure. L’intérêt de Lua est toujours là et la reprise de l’activité au niveau des gestionnaires de signatures laisse à penser qu’il y a toujours de l’espoir pour un usage étendu.

Cette leçon a sans doute été retenue avec la publication par l’OISF de suricata-update, un outil de mise à jour de règles, qui est intégré à Suricata 4.1.

1.5 Suricata 2.0 : osons JSON

La version 2.0 du Suricata est publiée en 2014 et marque un tournant avec le choix de JSON comme format préféré pour les événements générés. Grâce à cette contribution de Tom Decanio, le projet quittait enfin les années 90 en abandonnant les formats de fichiers texte non structurés ou binaires comme unified2 pour un format unique et facilement exploitable.

L’injection des données créées par Suricata dans des outils comme la suite Elasticsearch ou Splunk était possible trivialement. Le tout avec une « corrélation » naturelle sur le nom des champs utilisés. Une adresse IP source étant par exemple toujours le champ src_ip. Un seul fichier contient tous les événements depuis les alertes jusqu’aux événements protocolaires de type DNS, SSH, TLS ou HTTP en passant par les statistiques de performance.

Fig. 2 : Evénement TLS au format EVE JSON.

Sur le plan de la détection d’intrusion, l’arrivée d’une alternative à unified2 a été un grand progrès. Ce format binaire dédié aux alertes ne supportait que des champs de type IP et les informations de base de la signature (message, identifiant, catégorie). Il était aussi impossible à étendre pour ajouter plus d’informations aux alertes. D’un côté pratique, les couches logicielles menant du fichier unified2 à la base de données utilisée pour présenter les événements étaient loin d’être fiables et intuitives. Dans le même temps, Suricata comprenait de plus en plus de protocoles et pouvait donc ajouter des informations contextuelles aux alertes provenant de l’analyse protocolaire. Le concept est simple : pourquoi demander à l’analyste de refaire le travail d’analyse protocolaire d’une alerte sur un flux HTTP quand Suricata a déjà réalisé une extraction structurée ? Observer les champs extraits et réaliser des analyses statistiques sur les valeurs extraites est beaucoup plus simple et efficace.

La version 2.1 de Suricata a exploité cette ouverture en ajoutant des données protocolaires dans les alertes, notamment pour HTTP. Le travail sur ce sujet a continué assez longtemps et la plupart des protocoles supportés ont été ajoutés dans l’alerte. La version 4.0 est allée un peu plus loin avec la journalisation du corps des requêtes et réponses HTTP. Ces champs étant potentiellement compressés à la volée, il était particulièrement intéressant de les retrouver décompressés et donc lisibles dans les alertes.

1.6 Suricata 3.0 xbits

Suricata 3.0 est publié en janvier 2016 et la nouveauté principale est l’ajout de xbits. Il s’agit de dépasser la limite des flowbits qui ne pouvaient pas être utilisés dans l’analyse d’attaques multi-flots. Le concept a été proposé par Michael Rash [XBITS] et est une évolution des flowbits où la signature attache une variable à une adresse IP ou par couple d’adresses (client et serveur). Des signatures peuvent alors collaborer au sein d’un moteur à état qui dépasse le simple flot.

1.7 Suricata 4.0 – In Rust, we trust

Si l‘on regarde du côté fonctionnel, la version 4.0 est dominée par l’ajout du support de NFS et de NTP. Ces protocoles ont été ajoutés en utilisant une technologie commune à savoir la combinaison de Rust et de Nom [NOM]. Laissons la partie développement sécurisée de côté pour l’instant (voir 2.5 pour plus de détails). Ce choix d’une technologie sécurisée et efficace pour le développement de parseurs a comme principal objectif d’augmenter rapidement et sans crainte de problèmes de sécurité la couverture protocolaire de Suricata. À court terme, tout nouveau protocole devra être implémenté en Rust.

Les nouveaux protocoles ajoutés sont principalement des protocoles utilisés dans les réseaux internes (la version 4.1 voit ainsi SMB et DHCP arriver). L’idée est d’augmenter la valeur de Suricata lorsqu’il est déployé pour analyser des flux internes. Ce type de déploiement est intéressant au vu d’une part de l’augmentation de l’utilisation du chiffrement sur les connexions vers Internet (et donc de la perte de visibilité sur le trafic) et d’autre part en raison de la prise en compte de la menace constituée par les mouvements latéraux lors des attaques.

1.8 Suricata 4.1

Suricata 4.1 est dominé par l’ajout du support complet des protocoles de la famille Samba : journalisation des requêtes, mots clefs dédiés et extraction des fichiers. L’impact pour le déploiement de Suricata sur les flux internes est assez considérable. La capacité de journaliser les événements sur ces protocoles et de réaliser une extraction de fichiers change la donne. Les enregistrements sont assez complets pour pouvoir mettre en place des stratégies d’analyse fines comme le montre l’exemple suivant décrivant une transaction sur un partage :

Fig. 3 : Sous Object SMB dans un événement SMB au format EVE JSON.

2. Le développement sécurisé dans Suricata

Comme le dit très bien Pierre Chifflier, membre du conseil communautaire de Suricata, un IDS doit être capable de digérer la boue d'Internet tout en ne plantant pas. La sécurité du développement est donc un des points critiques. Elle a donc été prise au sérieux dès le début et a évolué avec son temps.

2.1 Tests unitaires et fonctionnels

Le premier arsenal mis en place a été construit lors du développement, il s’agit d’une utilisation systématique des tests unitaires. On en compte plus de 3600 dans la dernière version. Ils couvrent d'une part la validité des différents modules développés, d'autre part les cas ayant déclenché des bugs. Pour ce qui est de la première partie, plusieurs phases du code sont vérifiées. La lecture et l'interprétation correcte de la configuration (ou de la signature pour un module de détection). Mais aussi, la validation du fonctionnement « complet » d'un mot clef. Dans ce dernier cas, le test consiste le plus souvent à créer un pseudo packet, potentiellement accompagné de morceaux de flux TCP. Dans le même temps, une signature stockée dans une chaîne de caractères est lue et injectée dans un moteur de détection créé pour la recevoir. Le test appelle ensuite la fonction de détection (qui a la bonne idée d'être le point d'entrée unique). Le résultat est alors comparé à celui attendu pour déterminer le succès du test.

Aussi avancés soient-ils ces tests ne peuvent recréer un contexte complexe dans un logiciel multithread aussi, Suricata est déployé en continu sur des systèmes réels et est lancé de manière automatisée sur une base de fichiers Pcap pour détecter d'éventuels problèmes. Ces fichiers ont des provenances et des objectifs variés, il s'agit parfois de simples traces contenant des protocoles analysés par Suricata. Mais les plus intéressants sont sans doute les traces correspondant à d'anciens problèmes tels que des évasions ou des crashs. Ces tests sont lancés grâce à des buildbots. Il y a une infrastructure semi-publique (les données étant confidentielles) et une infrastructure privée. Les développeurs référencés doivent soumettre leur code aux tests semi-publics avant de pouvoir demander l'inclusion de leur code dans la branche officielle. L’infrastructure privée contient un nombre important de builders. L’idée est principalement d’avoir toutes les architectures supportées testées en continu.

2.2 Analyseurs statiques

Le buildbot privé intègre également des outils open source d’analyse statique comme Drmemory et scanbuild. Une analyse est donc effectuée avant chaque envoi de commits sur le dépôt public. Suricata fait aussi partie des projets open source surveillés par Coverity et le code poussé sur le dépôt public est testé de manière régulière.

Depuis 2011, le système buildbot fait également une batterie de tests sémantiques (lancés via make check). Ce vocabulaire n'est certainement pas officiel. Il s'agit d'une approche complémentaire des analyseurs statiques visant à détecter des mauvais usages des API internes ou encore l'utilisation de fonctions interdites. Ces tests sont écrits grâce au logiciel Coccinelle [cocci], un moteur d’analyse et de transformation sémantique de code C.

Le code suivant est un des tests les plus simples qui vérifie qu'un champ dans la structure Packet n'est pas utilisé directement au lieu de passer par les macros dédiées :

@action@

typedef Packet;

Packet *p;

position p1;

@@

 

p->action@p1

@ script:python @

p1 << action.p1;

@@

print "Invalid usage of p->action, please use macro at %s:%s" % (p1[0].file, p1[0].line)

import sys

sys.exit(1)

La structure est la suivante, l’entête action définie un type C Packet et la ligne suivante annonce que l’on va avoir un pointeur sur un Packet noté p. Par noté p, on entend vraiment que toute variable de type Packet * sera vue par Coccinelle et le motif p→action sera recherché. Ainsi, le code suivant génèrera une erreur :

void PacketFree(Packet *q)

{

    q->action = DROP;

    PACKET_DESTRUCTOR(q);

    SCFree(q);

}

La vérification de la cohérence des drapeaux (ajout de 2013) est un autre exemple. Suricata avait alors connu au moins un bug lié à l'utilisation d'une famille de drapeaux dédiée à une structure (notons-la A) dans une autre structure (B). Lorsque la famille de flags de A a été modifiée, la gestion des états de B a été impactée à cause du changement de valeur d'un drapeau. Ce bug a pris plus d’une semaine à être corrigé. Aussi, une solution générique a été mise en place. La déclaration d'une structure peut s'accompagner d’un commentaire associant un champ et la famille de drapeaux qui lui est assignée. Par exemple, pour signifier que le champ flowflags de la structure Packet doit uniquement recevoir des drapeaux de la famille FLOW_PKT_ , on peut utiliser :

/* coccinelle: Packet:flowflags:FLOW_PKT_ */

Un script Python a été développé pour analyser les sources et générer un fichier de tests Coccinelle qui valide l'utilisation correcte des drapeaux.

2.3 ASAN

En 2013, les tests automatisés de lecture de pcaps ont été modifiés pour utiliser la fonctionnalité Address SANitizer (ASAN) de LLVM/Clang. L'impact de l'utilisation d’ASAN est simple, le programme s’arrête dès qu'une utilisation invalide de la mémoire est réalisée. Un message d'erreur est affiché au moment de la sortie de l'exécution. En cas de fuite de mémoire, une analyse est affichée à la sortie.

ASAN est un outil de détection d’erreur mémoire basé sur l’instrumentation du binaire et une bibliothèque. Il détecte les accès hors limites, les utilisations après libération, utilisations après retour, les doubles libérations. Le tout pour un temps d’exécution simplement multiplié par 2. Valgrind était auparavant utilisé dans ce genre de cas, mais la dégradation en performance était telle qu’il n’était pas envisageable de lancer Suricata sur du vrai trafic. La rapidité de ASAN fait qu’il a été activé sur les systèmes de tests en trafic réel dont dispose l’OISF pour détecter toutes erreurs pouvant apparaître en condition réelle.

2.4 American Fuzzy Loop

L'un des plus récents ajouts est l'utilisation d'AFL, pour American Fuzzy Loop. Des campagnes d’utilisation d’AFL ont été lancées début 2015 et des problèmes ont du être fixés.

AFL utilise une technique de fuzzing classique combinée à un binaire instrumenté pour découvrir le plus de chemins possibles lors des itérations.

Comme beaucoup de fuzzers, le principe d’AFL consiste en effet à partir d’une entrée et à la faire évoluer en lançant le binaire fournit à chaque fois pour détecter un plantage. Dans le cas de Suricata, le lancement d’une instance complète prend beaucoup trop de temps pour itérer rapidement et des modes de fonctionnement dédiés ont donc été développés pour pouvoir tester de manière isolée et efficace des sous-systèmes critiques (parseurs protocolaires par exemple). De plus, le fuzzing est fait sur un binaire compilé avec ASAN pour élargir le champ des erreurs.

2.5 Rust

L’ajout de Rust est certainement une des étapes les plus importantes dans l’évolution de Suricata. Ce changement a été proposé par Pierre Chifflier lors de Suricon 2016, qui était la deuxième conférence annuelle des utilisateurs de Suricata [RUSTICATA]. Auteur du support initial de TLS dans Suricata et fort de cette expérience, Pierre Chifflier est arrivé à la conclusion que l’écriture de parseurs fiables et sécurisés en C est pratiquement impossible. Il a donc proposé l’utilisation de Rust et de Nom, une bibliothèque d’écritures de parseurs combinatoires.

Rust bénéficie d’une gestion mémoire plus sûre que le C. Elle rend des failles de type dépassement de tampon ou utilisation après libération impossible si le développeur n’utilise pas le mot clef unsafe.

Ces mécanismes entraînent une augmentation de la complexité du développement pour une population de développeurs aguerris au C. Il y a donc beaucoup de désavantages et d’obstacles à l’adoption du langage dans un projet comme Suricata. La proposition de Pierre Chifflier limite cette complexification par l’usage de Nom, une bibliothèque de parseurs combinatoires. Le développeur va certes devoir se plier à l’écriture de code Rust, mais dans le contexte de Nom qui simplifie énormément l’écriture de parseurs. Les six protocoles ajoutés dans Suricata 4.1 témoignent du succès de l’approche.

Conclusion

Au départ un IDS classique, Suricata est maintenant un moteur dual offrant IDS et monitoring réseaux orienté sécurité. L’écosystème des utilisateurs et contributeurs est varié avec des entités telles que Google, Fireeye, le CERT Norvégien ou encore l’ANSSI et il témoigne du succès de cette approche. Cependant, le passage au chiffrement sur Internet va limiter de plus en plus la visibilité apportée pour les placements périphériques et un déploiement plus intérieur analysant les protocoles internes semble être une attitude à adopter pour rester pertinent. Enfin, l’importance de la communauté a été récemment encore renforcée avec la création du conseil communautaire qui poursuit tout au long de l’année les échanges avec la communauté qui ont lieu lors de la discussion annuelle de la roadmap.

Remerciements

Un énorme remerciement à l’OISF et à la communauté formée autour de Suricata. Et une spéciale dédicace pour les contributions externes majeures. Merci donc à Pierre Chifflier pour le support TLS et l’initiative Rust, Michael Rash pour le concept de xbits et à Tom Decanio pour la sortie JSON.

Références

[OISF] Open Information Security Foundation, https://oisf.net/

[SURI] Open Information Security Foundation, Suricata, https://suricata-ids.org/

[JA3] Salesforce, « JA3 - A method for profiling SSL/TLS Clients », https://github.com/salesforce/ja3

[cocci] Inria / Lip6, « Coccinelle, a program matching and transformation engine », http://coccinelle.lip6.fr/

[ABUSE] Abuse.ch, SSL blacklist, https://sslbl.abuse.ch/

[XBITS] Michael Rash, « Crossing the Streams in IDS Signature Languages», http://www.cipherdyne.org/blog/2013/07/crossing-the-streams-in-ids-signature-languages.html

[RUSTICATAPierre Chifflier, « Securing Security Tools », 2016, https://suricon.net/wp-content/uploads/2016/11/SuriCon2016_PierreChifflier.pdf

[NOM] Geoffroy Couprie, Nom, https://github.com/Geal/nom