Les chercheurs en sécurité découvrent régulièrement de nouvelles vulnérabilités qui compromettent la sécurité de nombreux systèmes informatiques. Le mois de décembre 2021 a été l'occasion d'un bel exemple, avec la faille de la librairie log4j qui permet l'exécution à distance de code arbitraire sur les systèmes vulnérables. Constatant qu'il parait impossible de se prémunir de manière définitive contre ce type de problème, nous nous sommes demandé si les modules Yoctopuce pourraient aider à détecter des intrusions avant qu'il ne soit trop tard...
L'idée est de faire un canari, un petit logiciel qui permette de détecter les signes d'une intrusion réseau. De même que les canaris utilisés dans les mines au XIXe siècle permettaient aux mineurs de fuir les émanations de gaz dangereuses, le but de notre canari est de détecter les intrusions en cours pour limiter les dégats et protéger les données critiques tant que c'est encore possible.
Principe
Lorsqu'un intrus parvient à pénétrer sur un réseau privé, il va devoir chercher sur les ordinateurs du réseau des portes d'entrée lui permettant d'augmenter son emprise et d'accéder à des données critiques monayables. Ces portes d'entrées sont les divers services réseaux ouverts par chaque machine, qui recèlent potentiellement des vulnérabilités exploitables.
La phase exploratoire peut être conduite très discrètement, mais elle n'est pas sans risque pour l'intrus, car pour essayer d'utiliser une porte d'entrée, il devra forcément la pousser. Concrètement, cela veut dire qu'il devra ouvrir une connexion vers chaque service qu'il essaie d'attaquer. En soit, c'est une opération très anodine: n'importe qui peut essayer de se connecter à un service réseau, et il n'est pas interdit de se tromper. Donc l'exploration peut rester silencieuse.
Mais l'intrus a un handicap sur les utilisateurs légitimes: il ne sait pas où sont les vrais services. Si l'on rajoute donc sur différentes machines du réseau des faux services, ou si l'on rajoute des machines qui ressemblent à des serveurs mais n'en sont pas vraiment, l'intrus risque d'essayer de se connecter à un service qu'aucun utilisateur légitime n'aurait utilisé. Et c'est là que l'attend le canari.
Utilisation de canaris pour détecter les intrusions sur le réseau
Un canari simplifié à l'extrême
Un canari peut donc se réduire à un logiciel qui se contente d'ouvrir un certain nombre de ports réseau susceptibles d'être explorés par des intrus, et de lancer une alarme si quelqu'un s'y connecte.
Pour ouvrir les ports réseau, il faut créer autant de socket réseau, les attacher aux ports voulus avec la fonction bind et y écouter avec la fonction listen. Dès qu'une connexion entrante se présente, on l'accept pour récupèrer l'adresse IP d'origine, puis on déclenche l'alarme. C'est un peu de plomberie à base de sockets, mais ce n'est pas bien compliqué. Vous trouverez les quelques dizaines de lignes de code qui effectuent ces opérations dans ce fichier.
Pour donner l'alarme, le Yocto-MaxiBuzzer est idéal: il se branche par USB à n'importe quelle machine, et il ne risque pas de passer inaperçu vu sa forte puissance. Les quelques lignes suivantes permettent de lancer une sirène, qui continuera à hurler de manière autonome indéfiniment jusqu'à-ce qu'un utilisateur soit venu physiquement tirer la prise du module Yoctopuce:
buz->addVolMoveToPlaySeq(50, 0); // Set volume to 50%
buz->addFreqMoveToPlaySeq(500, 0); // Start at 500 Hz
buz->addFreqMoveToPlaySeq(5000, 1000); // Ramp up to 5 kHz in 1000 ms
buz->addFreqMoveToPlaySeq(500, 1000); // Ramp down to 500 Hz in 1000 ms
buz->startPlaySeq(); // Run in loop
On aurait pu écrire le code réseau de ce canari de manière encore plus compacte en python ou en node.js, mais nous avons choisi de le faire en C/C++ pour obtenir un exécutable monolithique, sans dépendance envers un interpréteur, et surtout ne permettant pas à un intrus qui tomberait dessus de savoir ce que fait le canari lorsqu'il détecte une intrusion. Cet outil vous est proposé en code source, combinant des fichiers C++ et C, sur GitHub. Il peut être compile sous Windows, Linux et macOS, et il inclut la gestion d'arguments en ligne de commande permettant facilement de l'installer comme un service lancé au démarrage sous Windows ou Linux. Il peut même être compilé et lancé sur un RaspberryPi.
Ce code est volontairement ultra simple. Il existe des logiciels de ce type plus sophistiqués qui essaient de répondre à l'intrus, afin de l'amener à poursuivre son intrusion plus loin sur le faux serveur, mais ça n'est pas sans risque non plus, car la complexité rajoutée peut se révéler source de vulnérabilités que l'intrus pourrait exploiter, pour cacher son intrusion ou même pour pénétrer sur le système. Cela sort de la philosophie de base du canari de notre point de vue.
De même, la plupart des solutions de canari que nous avons trouvé sur le web utilisent un système basé Cloud pour signaler les intrusions, plutôt qu'une alarme sonore. La solution Cloud a l'avantage de permettre une notification à distance, mais la complexité rajoutée offre à nouveau une opportunité de bloquer l'alarme: si l'intrus a pénétré sur le réseau grâce à une vulnérabilité sur le routeur, il y a des risques qu'il soit aussi en mesure de bloquer sur le routeur les accès réseau sortant de la machine qu'il attaque pour éviter qu'elle ne puisse donner l'alarme.
C'est pourquoi nous vous proposons ci-dessous une autre solution plus originale et bien plus robuste pour centraliser les alarmes et implémenter une notification à distance.
Centralisation des alarmes des canaris
Pour faciliter la surveillance des canaris, on aimerait les raccorder à un contrôleur centralisé. Mais ce centre de contrôle et d'alarme doit à tout prix rester hors de portée de l'intrus, sans quoi la sécurité du système risque de tomber à l'eau. Or la meilleure manière d'assurer sa sécurité est qu'il n'ait aucune connexion réseau: pas de câble réseau, pas d'interface Wifi, pas de bluetooth, rien. Ce n'est qu'ainsi qu'on peut s'assurer qu'il sera invisible et inattaquable depuis une autre machine.
La remontée des alarmes depuis les canaris doit donc utiliser un autre canal. Une solution simple et robuste consiste à utiliser un bus série RS485. Pas besoin d'infrastructure particulière, il suffit de remplacer le Yocto-MaxiBuzzer sur chaque canari par un Yocto-RS485-V2, et de les chaîner les un aux autres par un câble à trois fils relié aussi au contôleur. On obtient ainsi un canal de communication dédié, qui n'expose aucun service du contrôleur à un éventuel intrus.
Armée de canaris fédérés par un bus RS485
Cette nouvelle version "soldat" du canari fonctionne comme la précédente, mais plutôt que d'actionner leur propre sirène, les canaris vont simplement envoyer un message d'alarme. A l'initialisation, on va récupérer le nom logique qui aura été donné au Yocto-RS485-V2, et qui identifiera de manière unique ce canari auprès du contrôleur:
string linehdr = rs485->get_module()->get_logicalName()+";";
En cas de détection d'intrusion, on envoie un messag au contrôleur indiquant la machine depuis laquelle la connexion a été initiée, et le port qui a été ciblé.
+ ";" + std::to_string(targetPort);
rs485->writeLine(line);
Notez qu'on a choisi ici un protocole textuel, donc il faudra configurer le Yocto-RS485-V2 en mode Line-based ASCII protocol. Vérifiez aussi que tous Yocto-RS485-V2 utilisent la même vitesse de transmission et le même encodage, par exemple 19200, 8N1.
En bonus, nous avons ajouté à cette nouvelle version du canari la capacité de mesurer la quantité de données lues et écrites sur les disques par période de 15 secondes. Cette information est transmise à titre informatif quatre fois par minute au contôleur. Non seulement cela permet au contrôleur de s'assurer que le canari est toujours vivant, mais cela peut permettre de détecter une activité anormale au cas où un intrus aurait commencer à siphonner les données de la machine ou à les encrypter.
Le code source complet du canari soldat est disponible sur GitHub. Le code spécifique à Windows, Linux et macOS pour mesurer la quantité de données lues et écrites sur les disques se trouve dans les fichiers spécifiques winStartup.cpp, linStartup.c et osxStartup.c. Le cas échéant, prenez soin de vérifier que les disques pris en compte incluent bien votre disque de données, et pas seulement le disque système :-)
Le centre de commandement des canaris
Le programme qui va centraliser les alarmes des canaris et agir en conséquence pour protéger les données critiques va lui-aussi utiliser un Yocto-RS485-V2 pour écouter les messages sur le bus. Une manière efficace de le faire est de configurer un callback de changement de valeur sur la fonction serialPort, et d'utiliser ensuite la fonction readMessages pour lire tous les messages reçus depuis la dernière vérification.
Comme le contrôleur ne va tourner que sur une machine dédiée et isolée du réseau, il n'est pas utile de s'imposer la programmation en C/C++. On peut utiliser par exemple C#, qui permet plus facilement de faire une jolie interface donnant une bonne vue d'ensemble de l'état des canaris. Le code qui reçoit les alarmes peut donc ressembler à ceci:
for (int i = 0; i < messages.Count; i++) {
string[] fields = messages[i].Split(';');
if (fields.Length >= 2 && fields[1] == "alarm") {
// If a Yocto-MaxiBuzzer is connected, sound a loud alarm
YBuzzer buz = YBuzzer.FirstBuzzer();
if (!(buz is null)) {
buz.resetPlaySeq();
buz.addVolMoveToPlaySeq(50, 0); // Set volume to 50%
buz.addFreqMoveToPlaySeq(500, 0); // Start at 500 Hz
buz.addFreqMoveToPlaySeq(5000, 1000); // Ramp up to 5 kHz
buz.addFreqMoveToPlaySeq(500, 1000); // Ramp down to 500
buz.startPlaySeq(); // Run in loop
}
// Add more tricks here...
}
}
Maintenant que les alarmes sont centralisées sur un seul contrôleur, on peut s'offrir le luxe de rajouter d'autres actions d'alerte. Par exemple, vous pouvez connecter un YoctoHub-GSM-4G par USB à votre contrôleur de canari et l'utiliser pour envoyer une alerte par SMS:
YMessageBox mbox = YMessageBox.FirstMessageBox();
if (!(mbox is null)) {
// Send an SMS, but not more than once every 15 minutes
if (YAPI.GetTickCount() - lastSMSsent > 15 * 60 * 1000) {
// FIXME: Put your mobile phone number below
mbox.sendTextMessage("+xxxxxxxxxx", messages[i]);
lastSMSsent = YAPI.GetTickCount();
}
// One more trick here, see below...
}
On peut aussi utiliser le YoctoHub-GSM-4G pour qu'il contacte un serveur HTTP externe sur le Web pour propager l'alarme. Cet appel sera invisible à l'intrus, puisqu'il passe par une ligne 4G indépendante. Il n'expose pas non plus le contrôleur de canari, car la requête est émise par le YoctoHub lui-même et non par le contrôleur de canari, qui ne dispose toujours pas de connexion réseau propre.
string funcid = mbox.get_serialNumber() + ".network"
YNetwork net = YNetwork.FindNetwork(funcid);
if (net.isOnline() &&
net.get_readiness() == YNetwork.READINESS_WWW_OK) {
string args = "time=" + YAPI.GetTickCount() +
"&csv=" + messages[i];
net.set_callbackUrl("http://xxxxxx/xxxxx/xxxxx.php?" + args);
net.triggerCallback();
}
Il y a encore bien d'autres options possibles grâce aux modules Yoctopuce. Par exemple, vous pouvez demander à un utilisateur de quittancer les alarmes dans les 3 minutes sur le contrôleur, et si la quittance ne vient pas, prendre automatiquement une mesure de protection radicale en coupant l'alimentation de tous les switchs réseau à l'aide d'un Yocto-MaxiPowerRelay. C'est assez disruptif, mais cela vous évitera qu'un intrus profite de la nuit pour avancer dans son exploration de votre réseau, et cela vous permettra de réfléchir à tête reposée aux mesures à prendre. De plus, même avec le réseau hors service, votre contrôleur de canari continuera à recevoir les mesures d'accès disques, ce qui vous aidera à repérer les machines où un logiciel malveillant est peut-être encore en train de fouiner dans vos disques.
Nous vous avons mis sur GitHub un code d'exemple de contrôleur de canari basé sur ces principes. Nous l'avons dérivé de Yocto-Visualization pour permettre l'affichage des statistiques d'accès disque sous forme de graphique, et nous y avons simplement ajouté la gestion des alarmes arrivant sur le serialPort comme décrit plus haut (voir le fichier sensorsManager.cs). Mais attention, n'utilisez pas ce code en l'état, il a été bricolé en peu de temps en soutien à cet article et n'a pas la robustesse d'un outil de production.
Conclusion
Vous savez désormais comment un petit logiciel très simple et un petit module USB à moins de 100$ peuvent se transformer en puissant outil de détection des intrusions. Attention toutefois à faire la mise en service par une personne qualifiée, qui pourra adapter ces petits programmes d'exemple à votre environnement, notemment adapter la liste des ports à surveiller, installer l'outil là où c'est judicieux et si nécesssaire compléter l'outil. Par exemple, notre code n'intercepte que les ports TCP, mais il pourrait être souhaitable de rajouter un traitement similaire pour les ports UDP.
En ce qui concerne le contrôleur centralisé, à vous de faire l'application qui convient à vos besoins. Vous fournir une véritable solution clé-en-main sort du champs de ce que nous pouvons vous offrir dans un article de blog...