Mise en œuvre d’OSSEC sous Debian : théorie

Je travaille en ce moment sur OSSEC et comme je n’ai pas publié d’article depuis un moment déjà, je me suis dit que c’était l’occasion ou jamais ! Sauf qu’avant de mettre les mains dans les lignes de shell il nous faut poser quelques bases théoriques sur ce qu’est OSSEC, à quoi ça sert et comment ça marche. Et nous allons nous intéresser plus particulièrement à la manière dont OSSEC traite les logs qu’il reçoit pour en extraire des modèles et en déduire des événements anormaux.

Mais qu’est-ce qu’OSSEC ?

OSSEC, pour Open Source HIDS Security est ce qu’on appelle un HIDS : Host-based Intrusion Detection System.

C’est un ensemble d’outils qui, en fonctionnant ensemble, vont permettre de faire de la détection d’intrusion dans vos systèmes d’information. Parmi ces outils, on trouve un analyseur de logs en temps réel, la surveillance d’intégrité de fichiers système (est-ce que le hash MD5 de /etc/shadow a changé ?), de la détection de rootkits ou encore la surveillance en temps réel des processus en cours d’exécution.

C’est une solution libre et ouverte dont les sources sont maintenues par Trend Micro et disponibles sur Github. OSSEC peut être mis en place selon plusieurs architectures : standalone, clients / serveur (l’agent est disponible pour un grand nombre de plate-formes) ou même serveur seul (agentless).

Une fois qu’un de ces outils détecte un changement ou une activité suspects, OSSEC va pouvoir alerter les administrateurs par l’envoi de messages ou d’en informer un éventuel SIEM par syslog. Et là où OSSEC est très intéressant, c’est qu’il va aussi être capable de répondre à ces attaques grâce aux active-responses. Exemple : une tentative de force-brute sur un serveur SSH ; au bout de trois tentatives suspectes l’adresse IP d’où provient l’attaque est automatiquement bloquée via l’ajout d’une entrée au pare-feu.

Vous avez dit LIDS ?

On peut même pousser le vice un peu plus loin car certains utilisateurs d’OSSEC n’activent que la partie analyse de logs, faisant de cet outil un LIDS (Log-based Intrusion Detection System). La détection d’intrusion par analyse de logs regroupe les processus et les techniques utilisées pour détecter des attaques sur un environnement particulier en utilisant les logs qu’il génère comme source primaire d’information. Dès lors le LIDS remplit pleinement sont rôle en détectant un mauvaise usage des machines, une violation de vos politiques de sécurité ou encore toute forme d’activité suspecte ou inappropriée. Génial non ?

Découverte d’OSSEC

Après l’installation, que nous verrons dans un futur billet, nous observons qu’OSSEC est entièrement installé dans /var/ossec. C’est pour des raisons de sécurité car il s’exécute en environnement chrooté ! Plusieurs fichiers importants sont à connaître :

  • Le fichier de configuration principal n’est autre que /var/ossec/etc/ossec.conf
  • Tous les décodeurs sont stockés dans /var/ossec/etc/decoders.xml
  • Les binaires sont déposés dans /var/ossec/bin
  • Les règles de détection dans /var/ossec/rules/*.xml
  • Et les alertes enregistrées dans /var/ossec/logs/alerts.log

OSSEC fonctionne avec plusieurs daemons, tous contrôlés par le binaire ossec-control, et chaque daemon a une tâche bien particulière et dispose des privilèges les plus bas possible pour faire ce qu’il a à faire.

Flux des logs dans OSSEC

Avec un beau dessin, selon l’architecture client / serveur, ça se passe comme ça :

Schéma simple du flux de logs OSSEC

ossec-logcollectord, qui est lancé par l’agent, est chargé de surveiller les fichiers de logs que nous lui demandons (par exemple les logs d’un serveur web Nginx, ou encore /var/log/auth.log) et de transmettre toutes les nouvelles lignes au serveur auquel il est affecté. Côté serveur, c’est ossec-remoted qui est chargé du dialogue client ↔ serveur. Il reçoit les logs, et les transmet à ossec-analysisd. Ce daemon est quant à lui chargé de décoder chaque ligne de log qu’il reçoit afin d’en extraire les informations, puis de les comparer aux règles (rules) qu’il connaît afin d’en déduire ou non un comportement suspect.

L’envoi d’alerte est effectué par ossec-maild et une éventuelle action de réponse (active-response) est exécutée par ossec-execd. Cette réponse n’est autre que l’exécution d’un script. L’exemple type étant l’ajout d’une règle iptables pour une période donnée afin de bloquer une adresse IP qui tenterait une attaque par force brute sur un serveur SSH.

Par défaut les agents OSSEC communiquent avec leur serveur via un canal sécurisé sur le port UDP 1514 (compression zlib, chiffrement avec clés partagées et blowfish).

Focus sur l’analyse des logs

Le flux

Pour mieux comprendre le prochain article, nous allons maintenant nous intéresser plus précisément au daemon ossec-analysisd et comment il analyse les logs qui lui sont envoyés.

En fonction du volume d’agents que vous aurez, ossec-analysisd et ossec-remoted auront tendance à être les plus gros consommateurs de CPU. Pas de panique, c’est normal. Cette analyse, donc, est découpée en trois étapes :

Schéma du flux des logs dans ossec-analysisd

Le pré-décodage n’est autre que l’extraction d’informations de base de notre ligne de log comme la date, le nom de la machine ou encore le nom du programme qui a généré cette ligne. Le décodage repose sur le même principe mais concerne les champs particuliers qui intéresseront potentiellement l’administrateur : ip source, nom d’utilisateur, etc. Ces phases de pré-décodage et décodage permettent à OSSEC de formater les informations sous forme de champs. Enfin, l’analyse finale consiste à comparer les champs, les informations extraits aux règles pré-définies par l’administrateur afin de déterminer si la ligne de log est suspecte ou non.

Allez, avec un exemple ça ira mieux !

Prenons la ligne de log suivante générée par une tentative de connexion à un serveur SSH. Elle est extraite du fichier /var/log/auth.log :

Apr 30 17:27:03 labo01 sshd[7431]: Invalid user toto from 172.16.199.1

Utilisons l’utilitaire ossec-logtest pour voir ce qu’il en est :

GIF animé de démonstration d'ossec-logtest

Le pré-décodage

On observe que lors de la phase de pré-decodage, OSSEC a extrait les informations suivantes :

**Phase 1: Completed pre-decoding.
       full event: 'Apr 30 17:27:03 labo01 sshd[7431]: Invalid user toto from 172.16.199.1'
       hostname: 'labo01'
       program_name: 'sshd'
       log: 'Invalid user toto from 172.16.199.1

Ces informations (hostname, program_name) sont dès lors disponibles dans OSSEC et dans toute la chaîne de traitement de la ligne de log pour être enfin passées en arguments à une éventuelle active-response.

Le décodage

Vient ensuite la phase de décodage qui repose sur le même principe que le précédent, sauf qu’ici OSSEC va extraire d’autres informations relatives au type de logs qui sont analysés. En effet les différents champs seront plus ou moins pertinent que l’on soit sur des logs web ou des logs de messagerie par exemple. Et dans le cas d’une tentative de connexion à un service quel qu’il soit l’adresse IP source est bien évidemment importante :

**Phase 2: Completed decoding.
       decoder: 'sshd'
       srcip: '172.16.199.1'

Côté configuration, les décodeurs ressemblent à ceci :

<decoder name="sshd">                                                           
  <program_name>^sshd</program_name>                                            
</decoder>
[...]
<decoder name="ssh-invalid-user">                                               
  <parent>sshd</parent>                                                         
  <prematch>^Invalid user|^Illegal user</prematch>                              
  <regex offset="after_prematch"> from (\S+)$</regex>                           
  <order>srcip</order>                                                          
</decoder>

OSSEC est livré avec des centaines de décodeurs par défaut, le fork de Wazuh en apporte aussi beaucoup. Ils sont tous écrits dans /var/ossec/etc/decoders.xml. N’éditez pas ce fichier si vous souhaitez adapter les règles par défaut ou en ajouter de nouvelles mais utilisez plutôt local_decoders.xml qui ne sera pas réécrit lors de la mise à jour du paquet.

Le test sur base de signatures

Maintenant qu’OSSEC a décodé la ligne de log afin d’en extraire des informations intéressantes, il va comparer celles-ci aux règles dont il dispose. C’est ici que tout se joue !

Les règles OSSEC sont stockées dans /var/ossec/rules sous forme de fichiers XML. Chaque fichier contient un ensemble de règles et ce fichier est nommé selon le logiciel dont il va surveiller les logs. Les règles concernant OpenSSH sont par exemple écrites dans sshd_rules.xml. OSSEC est livré avec des centaines de règles pré-configurées.

Chaque règle doit avoir au moins un ID qui permette de l’identifier de manière unique. Et tout comme les décodeurs, si vous avez besoin d’ajouter ou d’éditer une règle, faites-le via le fichier local_rules.xml.

Grâce aux décodeurs, la syntaxe des rules est indépendante du format original de la ligne de log. N’oubliez pas que nous travaillons sur les champs extraits par les décodeurs, c’est là qu’ils prennent tout leur intérêt !

Reprenons l’exemple précédent, avec OpenSSH. La première règle qu’OSSEC va mettre en correspondance est la suivante :

  <rule id="5700" level="0" noalert="1">                                        
    <decoded_as>sshd</decoded_as>                                               
    <description>SSHD messages grouped.</description>                           
  </rule>

C’est une règle qui n’effectue pas d’action particulière mais qui permet de regrouper toutes les règles liées à OpenSSH. Une fois qu’OSSEC établit la correspondance, il continue de comparer les champs extraits avec toutes les règles enfant de celle ci-dessus. Et dans notre cas on obtient notamment :

  <rule id="5710" level="5">                                                    
    <if_sid>5700</if_sid>                                                       
    <match>illegal user|invalid user</match>                                    
    <description>Attempt to login using a non-existent user</description>       
    <group>invalid_login,authentication_failed,</group>                         
  </rule>

La deuxième ligne de cette règle est importante. Elle indique que la règle 5710 est enfant de la règle 5700 située plus haut. Ainsi, OSSEC sait qu’il peut continuer à comparer les champs extraits de la ligne de log avec les règles enfants de 5710, qui nous amène à une autre règle :

  <rule id="5712" level="10" frequency="6" timeframe="120" ignore="60">         
    <if_matched_sid>5710</if_matched_sid>                                       
    <description>SSHD brute force trying to get access to </description>        
    <description>the system.</description>                                      
    <same_source_ip />                                                          
    <group>authentication_failures,</group>                                     
  </rule>

En langage humain, cette règle qui porte l’ID 5712 de niveau 10 s’exécute si la règle parente n°5710 est déclenchée plus de 6 fois en 120 secondes et que l’IP source extraite est la même. En gros, qu’une tentative de brute-force est réalisée sur votre serveur SSH.

Ce système d’arborescence des règles est au cœur de la performance d’OSSEC car il n’utilise que celles qui correspondent réellement à la ligne de log en cours d’analyse.

Schéma de la structure des règles OSSEC

La même commande ossec-logtest avec un peu de débug :

OSSEC indique toutes les règles qu'il teste

OSSEC indique toutes les règles qu’il teste

La réponse via les active-response

À partir de la dernière règle ci-dessus (5712), OSSEC dispose de suffisamment d’éléments pour déclencher une active-response. Ce mécanisme va nous permettre de créer un lien entre les règles et l’exécution d’un script. On va pouvoir en quelque sorte « abonner » des active-response à des ID de règles, des groupes ou encore des niveaux d’alerte et leur demander d’exécuter des scripts en conséquence.

Les active-response sont configurées dans /var/ossec/etc/ossec.conf, par exemple :

<!-- 
  On commence par déclarer une commande, un script à exécuter.
  Ceux-ci doivent être placés dans /var/ossec/active-response/bin -->
  <command>                                                                     
    <name>firewall-drop</name>                                                  
    <executable>firewall-drop.sh</executable>                                   
    <expect>srcip</expect>                                                      
    <timeout_allowed>yes</timeout_allowed>                                      
  </command>
 
<!-- Puis on "lie" ce script à une active response. -->
  <active-response>                                                             
    <command>firewall-drop</command>                                            
    <location>local</location>                                                  
    <level>6</level>                                                            
    <timeout>600</timeout>                                                      
  </active-response>

Ici la commande s’intitule firewall-drop. Elle correspond à l’exécution du script /var/ossec/active-response/bin/firewall-drop.sh avec le paramètre d’entrée le champs qui contient l’adresse IP source extraite de la ligne de log.

L’active-response qui la déclenche est « abonnée » à toutes les règles de niveau 6 ou supérieur, et donc à la règle 5712 évoquée précédemment.

Le mot de la fin

Nous allons nous arrêter ici pour aujourd’hui et les exemples ci-dessus vont prendre tout leur sens dans le prochain article où nous mettrons en œuvre OSSEC pour de bon. Nous lui ferons avaler quelques logs et voir comment il réagit avec une machine exposée sur Internet. J’espère que ça vous a plu, à très vite !

Cet article vous a plu ? Partagez-le sur les réseaux sociaux !

Twitter Facebook Google Plus email