Supervision de sécurité : analyse exhaustive d’événements avec les outils ELK

Magazine
Marque
MISC
Numéro
83
Mois de parution
janvier 2016
Domaines


Résumé
Cet article présente un POC d'outils d'analyse exhaustif basé sur la pile ELK.

Body

1. Présentation

Depuis plusieurs années, je m’intéresse à la détection d'incidents sur les systèmes d'information. En particulier aux outils « SIEM » (Security information and event management). Ceux que j'ai pu tester sont dans la plupart des cas open source (Prelude, OSSIM).

Il s'agit de très bons outils, mais j'ai vite remarqué que dans leur version libre, il fallait faire un tri sur les événements, car la base de données pouvait être rapidement surchargée…De plus, les formats utilisés par les outils (IDMEF, OSSIM Format) sont restrictifs par rapport à l'analyse d’événements.

Il y a 2 ans, j'ai découvert ELK [1] (Elasticsearch, Logstash, Kibana). Des idées d'analyses que j'avais depuis des années ont pu se concrétiser grâce à cette suite d'outils très puissants !

Depuis ses débuts, ELK a énormément évolué. Mon POC datant maintenant d'un peu plus d’un an, Elasticsearch offre aujourd'hui des possibilités plus intéressantes qui permettraient d'optimiser les différentes parties des outils présentés ici.

1.1 Idée générale

Mon idée de départ (il y a 4, 5ans) était de réaliser un équivalent de l’outil d'analyse « PICVIZ » [2] sans interface graphique et en temps réel. Quand j'ai découvert ELK, j'ai compris que c’était l'outil idéal pour réaliser ceci très simplement avec très peu de développement.

1.2 Présentation des outils

Ce POC est composé d'un regroupement d'outils [3] (scripts, filtres, IHM) qui utilisent la pile ELK, mais surtout Logstash et Elasticsearch pour effectuer de l'analyse exhaustive :

 - les scripts permettent la création de plusieurs bases de connaissances en amont : anomalies, fréquence ;

 - Logstash permet la structuration, la normalisation, l’enrichissement, la corrélation et le filtrage ;

 - Elasticsearch permet le stockage, la corrélation et l’agrégation ;

 - une IHM de gestion afin de faire évoluer votre base de référence (apprentissage des faux positifs).

J'ai privilégié l'analyse exhaustive au niveau de Logstash afin d'avoir du temps réel. Il faut éviter l'appel à des requêtes extérieures qui ralentissent la chaîne de traitement de Logstash, travailler à charger les données de référence à son lancement, et dédier une chaîne Logstash pour les recherches lentes.

En complément,on peut utiliser Kibana ou la librairie D3.js[4] pour créer une visualisation graphique des données afin de trouver des indicateurs pour de nouveaux pivots ou pour approfondir les analyses (vue orientée) :

- Kibana permet l'analyse visuelle à travers des dashboards qui sont personnalisables ;

- La librairie D3.js permet plusieurs types de visualisation dont :

  - celle en coordonnée parallèle (vue proche de « PICVIZ ») ;

  - celle de relation (bulles avec directions + contexte dans la couleur) ;

  - celle en vue interactive ("bubbles charts" ou "interactive charts" ).

Ci-dessous, le schéma global de fonctionnement du « POC » :

misc

1. Déterminer les sources d'informations (éventements) ;

2. Transporter les éventements ;

3. Structurer, Normaliser, Enrichir ;

4. Stocker ;

5. Créer une base de référence par apprentissage pour les outils d'analyse temps réel ;

6. Une fois la base de référence créée, on peut débuter l'analyse des événements en temps réel sur Logstash ;

7. Si la note de risque de l’événement dépasse le seuil fixé, alors une alerte est créée ;

8. Un mécanisme tiers peut permettre de créer ou alimenter un incident par rapport aux alertes présentes ;

9. Apprentissage des faux positifs pour améliorer les résultats.

Sens d’exécution des filtres sous Logstash :

Structuration, normalisation et enrichissement → analyse lexicale/syntaxique/sémantique/relationnelle → analyse fréquentielle → si le risque est élevé, alors alerte → création d'incident vers un gestionnaire d'incidents

2. Structurer, normaliser, enrichir l'information

Structurer, normaliser et enrichir l'information contenue dans un événement permet d'extraire l'essentiel, de lui donner un sens, des informations complémentaires, au sein d'une base homogène.

Cette partie s'effectue à travers les filtres Logstash et la mise en cache de la base de référence.

Dans certains cas, l’événement peut déjà être structuré (exemple : réception format json d'événements Windows).

Logstash permet de capter différents types d’événements à travers ses interfaces d'entrée. Dans mon exemple, j'utilise l'entrée syslog. Puis j'utilise la partie filtre afin de structurer, normaliser et enrichir.

Logstash a par défaut de nombreux filtres. J'utilise majoritairement grok, kv, mutate, translate, date, syslog_pri ainsi que des filtres personnels.

J'ai enrichi la base d'expression régulière grok de différents « patterns » comme avec l'exemple ci-dessous :

URIPATH_CUSTOM ((?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)*(?:/)+)*
URIPAGE_CUSTOM (([A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+)
URIPARAM_CUSTOM [A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]<>]*
PRE_URIPP %{URIPATH_CUSTOM:apache_uripath}(?:%{URIPAGE_CUSTOM:apache_uripage})?
URIPP %{PRE_URIPP:apache_uripath_global}(?:\?%{URIPARAM_CUSTOM:apache_uriparam})?

Par exemple, si je reçois un événement par syslog :

<149> Mar 25 10:00:00 192.168.2.1 apache2: 192.168.0.1 user [25/Mar/2015:12:00:00 +0100] GET /path/page?field=test HTTP/1.1 200 800

Celui-ci sera structuré en format « json » afin d’être ensuite stocké sous Elasticsearch. Cet enregistrement ressemble à ceci :

{
 "_index": "logstash-2015.03.25", "_type": "syslog", "_id": "xxxxxxxxxxxxxxxxxx",

Il s'agit des éléments qui définissent dans Elasticsearch l'emplacement de stockage de l’événement.

  "_source": {

Ici, on retrouve l’extraction et le message (événement d'origine).

    "message": "<149> Mar 25 12:00:00 192.168.2.1 apache2: 192.168.0.1 user [25/Mar/2015:12:00:00 +0100] GET /path/page?field=test HTTP/1.1 200 800",

Il s'agit du message/événement reçu par syslog.

"@version": "1",
 "@timestamp": "2015-03-25T11:00:00.000Z",

timestamp : correspond à l'extraction de la date syslog.

    "type": "syslog",

type : type de message, ici syslog.

    "syslog_pri": "149",

syslog_pri : la valeur priorité (« PRI ») de l'événement extrait.

    "syslog_program": "apache2",

syslog_program : extraction du programme qui a généré un événement syslog.

"tags": [
 "normalized"
 ],

Il s'agit d'un tag personnel qui m'informe que l’événement a bien été normalisé et que l’on pourra bien le traiter sur les autres filtres de Logstash.

"apache_clientip": "192.168.0.1",
 "apache_userauth": "user",
 "apache_timestamp": "25/Mar/2015:12:00:00 +0100",
 "apache_method": "GET",
 "apache_uripage": "page",
 "apache_uripath": "/path/",
 "apache_uriparam": "?field=test",
 "apache_uriparam_champs": [
 "field"
 ],
 "apache_uriparam_value_of_field": "test",
 "apache_httpversion": "1.1",
 "apache_response": "200",
 "apache_bytes": "800",

 

Ci-dessus, l'extraction du message selon la regexp grok que j'ai défini.

"syslog_severity_code": 5,
 "syslog_facility_code": 18,
 "syslog_facility": "local2",
 "syslog_severity": "notice",

Calcul de la gravité et de la catégorie de l'événement à partir de la valeur « PRI » syslog.

    "@source_host": "192.168.2.1",

Adresse du serveur à l’origine du message syslog.

    "@message": "192.168.0.1 user [25/Mar/2015:12:00:00 +0100] GET /path/page?field=test HTTP/1.1 200 800",

Événement sans la partie syslog.

  },

}

Ici, nous avons une extraction simple afin de structurer notre événement grâce à grok, qui nous permet déjà des recherches intéressantes.

Cela permet grâce à Kibana et les agrégations de Elasticsearch, en particulier les agrégations de « terms », d'obtenir une vue qui peut permettre rapidement de déceler des événements particuliers.

Cependant, ces informations extraites restent fortement liées à l’événement de type Apache. Par exemple, il est facile en connaissant Apache (champ apache_reponse) de voir ici que sa requête a « réussie ». Il en est de même pour connaître le sens de la requête effectuée. Ici on comprend que la personne demande une page web, car elle effectue un « GET ». On comprend aussi qu'il s'agit d'un utilisateur authentifié qui s'appelle « user ». Chaque service a ses propres expressions, son propre langage que l'on doit connaître pour comprendre l’événement.

C'est là que rentre en jeu la standardisation/normalisation. Elle permet de déterminer un langage commun avec un vocabulaire restreint pour n'importe quel événement.

Normaliser et classifier les événements permettent d'effectuer des recherches puissantes sur tous les événements, juste en connaissant le langage commun.

Il existe plusieurs normes dans ce domaine. Il faut bien différencier la normalisation d'une alerte ou d’un incident (exemples : IDMEF, IODEF) dans le but d'être échangé et compris par un tiers, qui pour ce faire doit respecter un standard ; et la normalisation d'événements qui a pour but de faciliter la recherche dans une masse d'informations (exemple : CEE). Il en existe beaucoup, et aucune n'est encore réellement sortie du lot… Mais avec l'arrivée d'outils comme Elasticsearch utilisant du format JSON, il apparaît de plus en plus un format ouvert pour la partie normalisation d’événements qui permet d'être très libre et donc adaptable très facilement (exemple : l'outil MOZDEF).

Ce choix offre un langage que vous pouvez personnaliser. J'ai donc opté pour ce format en utilisant certains éléments que le standard CEE peut apporter (la classification et le dictionnaire des champs) qui m'apparaissaient très intéressants pour une bonne harmonisation des événements.

Je normalise l’événement pour lui donner un sens et son état (étape de traduction : langage propre à chaque événement → langage commun), puis j’enrichis l’événement avec des informations complémentaires communes.

Par exemple, avoir des informations sur le service de l’événement comme sa surface d'exploitation possible : son utilisateur de lancement, son type de compilation, ses limitations systèmes, ses liens avec les différents projets du SI… ; des informations sur le système hôte comme sa mémoire, les autres services présents, son OS, la version … ; des informations sur l'utilisateur du service : utilisateur externe ou interne...

L'obtention des deux parties (langage commun et informations complémentaires communes) permet une recherche optimale du risque grâce à de nombreuses informations en relation avec l'exposition et les vulnérabilités possibles. Elles permettent la mise en place d'analyses visuelles performantes ainsi qu'une corrélation possible au niveau des alertes pour la création d'incidents.

Voici un extrait de langage commun que j'ai décidé d'adopter (d'origine du standard CEE) :

 - TIME → champ existant « timestamp » permettant d'avoir une information sur la date à laquelle a eu lieu l’événement.

- WHERE_NETWORK → champ de type « list » obtenu par enrichissement « traduction ou base de référence » basé sur le champ @source_host permettant d'obtenir une information d'emplacement dans le réseau : vlan,dmz…

 - ACTION → champ de type « list » obtenu par enrichissement « traduction ou base de référence » basé sur la compréhension technique de chaque événement spécifique avec un vocabulaire restreint (à définir en amont) : access, alert, allow, auth, get, close, copy, connect, encrypt, move… Il permet de comprendre l'action effectuée par le programme qui a déclenché l’événement.

 - STATUS → champ de type « string » obtenu par enrichissement « traduction ou base de référence » basé sur la compréhension technique de chaque événement spécifique avec un vocabulaire restreint (à définir en amont) : succes, error, failure ainsi que sur le champ syslog_severity si celui-ci est plus fort (exemple : succès sur traduction, mais syslog_severity == error alors status == error). Il permet de comprendre l'état obtenu par l'action effectuée.

Enrichissement commun :

SRC → champ de type « object » avec plusieurs champs de propriété obtenus par enrichissement « base de référence » basé sur les champs @source_host & syslog_program. Il permet une meilleure connaissance de l’environnement (exposition/surface d'attaque) dans lequel est l'application émettrice de l’événement afin de mieux évaluer un risque sur alerte.

Voici un  exemple d’informations communes qu'il pourrait être intéressant d'avoir :

 - SRC_pkg_name : nom du package ;

 - SRC_pkg_ver : version du package ;

 - SRC_srv_name : nom du service ;

 - SRC_srv_user_run : utilisateur qui lance le service ;

 - SRC_srv_type : vocabulaire restreint afin de connaître le rôle du service ayant créé l’événement (ex : web, fw, mail, db…) ;

 - SRC_app_hard : type de compilation mis en place sur l'application (exemple: « compilation durcie ») ;

 - SRC_app_proto : protocoles utilisés par l'application ;

 - SRC_app_port : ports utilisés par l'application ;

 - SRC_app_listen_ip[LIST] : interface en écoute (exemple : « 0.0.0.0 » ou spécifique) ;

 - SRC_os_type : système d'exploitation (Linux, Unix, Windows, Mac OS) ;

 - SRC_os_name : nom de la distribution ;

 - SRC_os_ver :  version du système d'exploitation ;

 - SRC_kernel_ver[STRING] : version du noyau système ;

 - SRC_kernel_cgroup[BOOLEAN] : si linux, information sur l'activation des « cgoups » ;

 - SRC_kernel_hardening[LIST] : information sur le durcissement du noyau système ainsi que l'activation de modules de sécurité (exemple : « ASLR, SELINUX,TOMOYO, APPARMOR, ... ») ;

 - SRC_srv_type : serveur de type physique/virtuel ;

 - SRC_srv_type_name : l'outil de virtualisation (exemple :  « xen,vmware,kvm... ») ;

 - SRC_app_projet[LIST] : projet dans lequel le service est utilisé ;

 - SRC_app_projet_exp[LIST] : exposition du projet dans lequel le service est utilisé (exemple : « interne,externe,int&ext ») ;

 - SRC_app_projet_crit[LIST] : niveau de « criticité » du projet dans lequel le service est utilisé ;

 - SRC_os_other_srv_run[LIST] : autres services lancés par le système d'exploitation ;

 - SRC_os_gw[IP] : passerelle utilisée par le service ;

 - SRC_os_dns[IP LIST] : les résolveurs de noms de domaines utilisés par le service ;

 - SRC_os_cpu : le nombre de cpu ;

 - SRC_os_ram : taille de la mémoire vive (en mégaoctet).

Il est facile de comprendre les possibilités qu'offrent la normalisation et l’enrichissement. Par exemple, je veux connaître pour un client précis le nombre de requêtes qui ont échoué sur tous les services de mon réseau. Ou bien connaître le top 10 (grâce aux agrégations) des clients qui ont effectué des requêtes qui ont échoué.

Je ne rentrerai pas plus en détail au niveau de l'enrichissement et je vous laisse vous faire votre propre idée de l'utilité qu'il pourrait avoir dans votre contexte de travail.

Voici ce que cela rajoute à notre enregistrement sur notre exemple :

{
...
 "time": "2015-03-25T11:00:00.000Z",
 "where_network": "vlan_12",
 "action": [
 "get"
 ],
 "status": "success",
 "SRC_pkg_name": "apache2",
 "SRC_pkg_ver": "2.4.10",
 "SRC_srv_name": "apache2"
 "SRC_srv_user_run": "www-data",
 "SRC_srv_type": "web",
 "SRC_app_hard": [
 "TOMOYO",
 "COMPILATION HARDENING"
 ]
 "SRC_app_port": [
 "80",
 "443"
 ],
 "SRC_app_proto": [
 "tcp"
 ],
 "SRC_app_listen_ip": [
 "192.168.2.1"
 ],
 "SRC_os_type": "linux",
 "SRC_os_name": "debian",
 "SRC_os_ver": "jessie",
 "SRC_kernel_ver": "",
 "SRC_kernel_cgroup": "cgroup",
 "SRC_kernel_hardening": [
 "TOMOYO",
 "ASLR"
 ],
 "SRC_srv_type": "virtual"
 "SRC_srv_type_name": "kvm"
 "SRC_app_projet": [
 "web_test",
 "web_dokuwiki"
 ]
 "SRC_app_projet_exp": [
 "interne",
 "interne"
 ],
 "SRC_app_projet_crit": [
 "low",
 "middle"
 ],
 "SRC_os_other_srv_run": [
 "mysqld"
 ],
 "SRC_os_gw": "192.168.2.1",
 "SRC_os_dns": [
 "192.168.2.240",
 "192.168.3.240"
 ],
 "SRC_os_cpu": "2",
 "SRC_os_ram": "2048",
 },
}

Il est très simple de réaliser des filtres Logstash qui permettent l'extraction d'objets (username, url, ip, host…) ainsi que l’enrichissement de leurs propriétés.

Exemple avec l'extraction d'utilisateur :

 1- Rechercher le nom d'un champ qui contient « user » ;

 2- Extraire la valeur et la mettre dans le champ de type list « USER » ;

 3- Vérification par regexp du contenu : « USERNAME [a-zA-Z0-9._-]+ » ;

 4- Si le champ « user » existe, alors ajouter dans le champ « action » : « auth ».

Objectifs possibles de notre extraction d'objet « user » :

 - Vue Kibana top 10 des champs : user/status & user/ip/pays & user/service ;

 - Filtre comportemental basé sur un événement encore inconnu (user → nouveau service).

Voici ce que l'on obtient sur notre exemple :

"action": [

     "get",

     "auth"

 ],

"username": "user", 

Trop d'extraction tue l'extraction, il faut se rappeler que l'on peut grâce à Elasticsearch effectuer une requête qui permet de trouver une adresse IP, un nom de fichier, un username sans même l'avoir dans un champ particulier. Il faut donc se poser la question de savoir s'il est vraiment utile de stocker une telle information dans un champ particulier. Il doit y avoir un sens, comme par exemple pour l'analyse visuelle.

3. Outil d'analyse lexicale, syntaxique, sémantique et relationnelle

L’outil d'analyse « lexicale, syntaxique, sémantique et relationnelle » comprend trois parties :

 - anomalie.py : script python qui permet la création de la base de référence et des relations ;

 - anomalie.rb : un filtre Logstash qui permet la vérification d'anomalies en temps réel dans un événement selon la base de référence créée par anomalie.py ;

 - anomalie.php : IHM qui permet la visualisation de la base de référence ainsi que sa modification (apprentissage de faux positif).

3.1 Création de la base de référence

On crée la base de référence dans Elasticsearch à partir de données conséquentes, normalisées et que l'on considère les plus « normales » possible.

On effectue une suite d'agrégation de « terms » par Elasticsearch sur les événements normalisés. On définit comme pivot de « terms »  les champs suivants :

syslog_programm|syslog_pri|@message

Soit sur l'exemple une empreinte de type :

apache2|149|192.168.0.1 user [25/Mar/2015:12:00:00 +0100] GET /path/page?field=test HTTP/1.1 200 800

Pour chaque résultat, nous allons récupérer le nom des champs contenus dans l’événement (hors langage et information communs).

Dans l'exemple et par rapport à ma configuration (grok Logtash), j'obtiens l'extraction des noms de champs suivants :

apache_bytes|apache_clientip|apache_httpversion|apache_method|apache_response|apache_uripage|apache_uripath|apache_uriparam apache_userauth

La signature finale de l'exemple donnera :

apache2|149|apache_bytes|apache_clientip|apache_httpversion|apache_method|apache_response|apache_uripage|apache_uripath|apache_uriparam|apache_userauth

Le script extrait toutes les signatures uniques des événements normalisés de la base Elasticsearch.

Un événement normalisé veut dire que son format et sa valeur sont connus et que l'on sait structurer son contenu et lui apporter une harmonisation commune.

Pour chaque signature, le script va définir des caractéristiques pour chaque champ ainsi que les relations qui en unissent certains. Dans ce but, il effectue une requête pour extraire les événements qui contiennent la signature.

Sur chaque champ extrait des résultats qui sont définis dans la signature, il va rechercher des caractéristiques dans le contenu :

 - le type : int, long, string ;

 - l'encodage : ASCII ou utf8 ;

 - la longueur et si celle-ci est pair/impair/les deux ;

 - l'expression régulière qui correspond à la valeur des champs. Un fichier contient les expressions régulières qui scannent le contenu, chaque expression qui matche est ajoutée à une liste ;

 - la fluctuation de la valeur du contenu afin de savoir s’il s’agit d’une donnée qui comporte un grand nombre de possibilités d’état ou d'une donnée relativement stable. Cette partie permet la création de la signature de relation.

Si l'on prend le champ apache_method de notre exemple ci-dessus, on obtient les caractéristiques suivantes :

FIELD_TYPE_apache_method: string

Ceci indique que le contenu du champ doit être une chaîne de caractères.

FIELD_ENCODE_apache_method: Array ( [0] => ASCII )

Ceci indique que l'encodage doit être ASCII.

FIELD_LEN_AVG_apache_method: 3

Ceci indique que la taille moyenne de la chaîne contenue dans le champ doit être de 3 octets.

FIELD_LEN_MAX_apache_method: 3

Ceci indique que la taille maximum de la chaîne contenue dans le champ doit être de 3 octets.

FIELD_LEN_MIN_apache_method: 3

Ceci indique que la taille minimum de la chaîne contenue dans le champ doit être de 3 octets.

FIELD_PI_apache_method: 1

Ceci indique que la taille de la chaîne est de valeur impaire (0 = pair & 2 = ni pair ou impair).

FIELD_UNIQ_apache_method: 0

Ceci indique qu'il s'agit d'un champ dont le contenu semble être relativement peu changeant et contient moins de 15 valeurs différentes (1 = moins de 1% de valeurs différentes, 2 = grand nombre de valeurs différentes).

FIELD_LIMIT_apache_method: Array ( [0] => GET)

Ce champ n'existe que dans le cas où FIELD_UNIQ_apache_method == 0, alors celui-ci contient les valeurs possibles du champ apache_method.

FIELD_REGEX_apache_method: Array ( [0] => ALPHA_MAJU::ALPHA_MAJandMIN )

Il s'agit d'une liste qui contient toutes les expressions régulières contenues dans le fichier de « patterns » qui matche le contenu du champ apache_method de chaque événement. Ici, ALPHA_MAJU=>>[A-Z] ALPHA_MAJandMIN=>>[A-Za-z].

FIELD_REGEX_MIN_apache_method: Array ( [0] => ALPHA_MAJU [1] => ALPHA_MAJandMIN )

Ceci indique les regexp que doit forcément contenir le champ apache_method (il s'agit de l'intersection entre toutes les signatures regexp contenues dans FIELD_REGEX_apache_method).

Il faut avoir en tête que cet exemple n'est qu'un événement particulier qui correspond à une signature, mais dans cette signature il va y avoir plusieurs événements différents. Chacun d’eux va modifier les caractéristiques. Une fois tous les événements d'une signature analysés, alors les caractéristiques sont enregistrées dans la base de référence.

On obtiendra donc des caractéristiques beaucoup plus étoffées.

Les relations qui unissent les champs d'une même signature sont définies par des empreintes accompagnées du nombre d’itérations trouvées pour cette dernière.

Dans notre exemple, nous aurions une empreinte de relations qui comprend tous les champs qui ont la caractéristique d'avoir FIELD_UNIQ_NOM_CHAMPS à 0 ou 1. Soit :

FIELD_UNIQ_apache_bytes: 2
FIELD_UNIQ_apache_clientip: 1
FIELD_UNIQ_apache_httpversion: 0
FIELD_UNIQ_apache_method: 0
FIELD_UNIQ_apache_response: 0
FIELD_UNIQ_apache_uripage: 1
FIELD_UNIQ_apache_uripath: 1
FIELD_UNIQ_apache_uriparam: 1
FIELD_UNIQ_apache_userauth: 1

Ce qui donne comme empreinte :

 "192.168.0.1<||>1.1<||>GET<||>200<||>page<||>/path/<||>?field=test<||>user" – 1 itération

Chaque événement de la signature peut créer une empreinte de relation différente ou une identique, celle-ci est comptabilisée et ajoutée lors du stockage de cette dernière dans les caractéristiques de la signature.

Le script permet aussi la mise à jour des relations basées sur les événements qui ont une note de risque nulle.

Une tache planifiée (« crontab ») peut être mise en place pour la mise à jour des relations et la création de nouvelles signatures.

3.2 Analyse en temps réel par Logstash

Le filtre Logstash se charge et récupère la base de référence créée ci-dessus. À chaque événement, il crée sa signature et la recherche dans sa base.

S’il trouve la correspondance, alors il vérifie que les caractéristiques de l’événement soient bien en adéquation avec ceux de la base de référence.

Voici un exemple d’événement :

<149> Mar 26 10:00:00 192.168.2.1 apache2: 192.168.0.2 user2 [26/Mar/2015:12:00:00 +0100] GET /../../../../../../../page?field=%00test' HTTP/1.0 404 800

Celui-ci crée la même signature que vue dans l'exemple précédent.

Chaque champ est donc vérifié par rapport aux caractéristiques définies dans la base pour cette signature. Comme expliqué plus haut, nos caractéristiques ne sont plus exactement comme vues ci-dessus, car elles ont pris en compte les caractéristiques d'un nombre important d’événements à la signature identique.

 - Type : le type de contenu est vérifié. Ici pas de problème.

 - Encodage : ici pas de caractères spéciaux, l'encodage est donc ASCII, celui-ci est bien dans les caractéristiques de la base de référence.

 - Longueur : on peut imaginer que les champs apache_uripath dépassent la taille moyenne FIELD_LEN_AVG_apache_uripath, dans ce cas deux champs risk_note et risk_desc sont créés dans l’événement. L'un contient une valeur en fonction de l'erreur et l'autre est une liste qui contient chaque erreur rencontrée afin que l’opérateur puisse mieux comprendre la note de risque attribuée. Dans notre cas, la valeur risk_note est incrémentée de 1.

 - Pair ou Impair : la base de référence contient beaucoup de champs à la valeur 2 (indéfinie) seuls les champs FIELD_PI_apache_httpversion et FIELD_PI_apache_response sont à « 1 ». L’événement respecte bien cette caractéristique.

 - REGEX : Pour de nombreux champs, pas de problème particulier. Mais pour apache_uripath et apache_uriparam, la regexp créée par l’événement n'existe pas en base de référence.

 - Les champs à peu de variation de valeur (moins de 15 – valeur 0) semblent être respectés, car ils utilisent des valeurs définies en base de référence.

 - L'empreinte de relation créée n'existe pas, il s'agit donc d'un événement encore jamais observé sur le réseau, ce qui le rend suspect et demande une analyse approfondie.

Cet événement se transformera en alerte (si la note de risque finale dépasse le seuil fixé). S'il y a une alerte, alors elle sera reportée dans un ticket d'incident qui sera créé pour l'occasion ou ajouté dans un en cours, si celui-ci a un lien. Par rapport à l' « URI » et au code retour, on peut se poser la question d'une reconnaissance. Cependant grâce à cet événement anormal, la puissance d'Elasticsearch et l'enrichissement que l'on a fait en amont, l’opérateur pourra avec des requêtes ou directement dans l’événement observer le potentiel de risque de l'individu et traiter le problème de façon efficace rapidement.

Au vu de la requête, on peut se poser deux questions :

 - Est-ce qu'il s'agit d'une reconnaissance par une source seule ? Pour le savoir, rien de plus simple, ou l'on a directement l'information dans l’événement si l'enrichissement a été bien fait, soit on crée un template Kibana qui détermine ce genre de problème, celui-ci prendra l'IP du suspect en filtre sur objsrc_ipv4 et créera des terms sur syslog_program, status, risk_note... Ceci permettra d'avoir une première idée de l’étendue des requêtes effectuées par l'individu.

 - Est-ce qu'il s'agit d'une reconnaissance d'un service par une multitude de sources ? Soit on voit dans la liste des alertes d’autres incidents connexes sur le même service (normalement dans ce cas la redirection des alertes connexes devrait enrichir le ticket d’incident en cours). Soit on réalise la même requête qu'au-dessus, mais avec un filtrage sur l'adresse de notre serveur ou bien sur un projet et le service.

Le filtre permet d'effectuer une requête ES afin de savoir s'il existe une relation et qu'elle est sa quantité. Il vaut mieux éviter pour les performances ainsi que la possibilité de « pourrir » la base pour un attaquant en créant de la relation non validée.

3.3 Contrôle et apprentissage de la base par les faux positifs

Une IHM permet de consulter la base de référence et la modifier si l'on trouve que les références ne sont pas justes. En effet, la base est créée avec des événements qui sont « normalement » propres. Or il peut toujours y avoir des anomalies, de ce fait il est important d'analyser les nouvelles signatures pour valider leurs caractéristiques.

De plus, on peut aussi obtenir des faux positifs, ceux-ci ne seront pas pris en compte pour l'apprentissage des nouvelles relations, c'est pourquoi l'IHM permet aussi avec l'identifiant de l’événement, de réactualiser la base afin d’intégrer ses nouvelles caractéristiques.

3.4 Résultats obtenus

Les tests réalisés ont montré que l'outil permettait une réduction des événements à analyser de 99 %, soit environ 30000 événements analysés en 5 minutes créent environ 250 événements d'anomalie.

Si on applique une correction des faux positifs, on peut descendre encore plus bas.

Bien sûr, ces résultats sont à prendre avec précaution, car chaque contexte va donner des résultats différents.

3.5 Améliorations à prévoir

La notation de risque est à revoir, avec la possibilité de définir dans un fichier ou une base de données la note pour chaque type d'erreur et la possibilité d'affiner par signature. Cependant, les notes devraient être identiques pour chaque signature, et simplement lors de l'arrivée sur le filtre d'incident, celui-ci prendra en compte les informations d'enrichissement qui apportent les informations d'exposition et de surface d’attaque du service/projet/host et qui permettront de mieux définir le risque général.

J'ai créé cet outil il a un peu plus d’un an (ES 1.1), à l’époque il n’était pas possible de faire une agrégation avec plus de deux champs, aujourd'hui cela a changé et pourrait permettre d'optimiser le script de création de base.

Je dois mettre en place un fichier qui contiendra les champs à exclure de la signature (champ d'information commune).

Possibilité de création de relation par rapport au champ status : à ajouter dans la signature type SG+PRI+STATUS+CHAMPS.

Gestion des éléments dans des listes... exemple : apache_uriparam_champs.

Lors de la création de la base, offrir la possibilité de ne prendre en compte que le « statut succès » pour la création de la base de relation.

4. Outil d'analyse fréquentielle

L'idée n'est pas nouvelle, il existe déjà des outils qui permettent de vérifier la taille d'un fichier de log dans le temps afin de détecter une anomalie qui peut être de différentes natures.

Ici, l'idée est de faire le même type de tests, mais en beaucoup plus précis.

Il s'agit de déterminer une fréquence approximative d’événements sur une durée déterminée de 7 jours par rapport à l'ensemble des champs ES suivants : PROGRAM & PRI & STATUS. Elasticsearch va calculer un histogramme par intervalle d'1 heure sur 7 jours. Ces informations seront ensuite mises en base de référence pour exploitation en temps réel sur Logstash.

Logstash va créer à son tour un registre incrémenté à chaque événement sur 1 heure pour chaque « PROGRAM & PRI & STATUS », celui-ci sera comparé à chaque nouvel événement à la base de référence.

Si la différence entre le réel et la base est plus grande qu'un pourcentage défini, alors une alerte sera créée.

L’outil d'analyse de « fréquence » comprend deux parties :

 - frequence.py : script python qui permet la création de la base de référence ;

 - frequence.rb : plugin logstash qui permet la vérification de la fréquence des événements en temps réel par rapport à la base de référence créée par frequence.py.

4.1 Création de la base de référence dans ES

Par exemple pour notre exemple, la base de référence qui est contenue dans Elasticsearch aura une entrée suivante :

[192.168.2.1][apache2][149][succes][5][12][1]

IP du service + nom du service + PRI syslog + état + jour (vendredi=5) + heure (12h → timestamp)+compteur de référence

Ici, le compteur de référence est à 1 pour l'exemple, mais en réalité il sera en fonction du nombre d'événements enregistrés dans Elasticsearch durant le vendredi entre 12h et 12h59 pour le service apache de l'adresse IP 192.168.2.1 sous le PRI 149, avec un état de succès.

4.2 Analyse fréquentielle en temps réel sous logstash

Le filtre récupère les informations de la base de référence. Lors d'un nouvel événement, il vérifie la présence en base du service pour l'adresse IP avec un « pri » et un « status » identique.

S’il est trouvé, alors il incrémente le compteur réel, puis le compare au compteur de référence. Trois possibilités s'offrent alors :

 - soit il est au-dessus du compteur de référence + 20 % (si compteur référence > 5) et crée une alerte par rapport au service concerné et non pas par rapport à l’événement ;

 - soit il est en dessous et la vérification proportionnelle n'est pas activée, donc il arrête le traitement pour cet événement ;

 - soit il est en dessous et la vérification proportionnelle est activée. Dans ce cas, par rapport à l'heure réelle il adaptera le compteur de référence afin qu'il soit proportionnel à l'heure. Exemple compteur référence = 10 pour 9h-10h, si à 9h30 j'ai 8 dans le compteur réel alors le programme créera une alerte, car pour lui à 9h30 le compteur de référence devrait être de 10/2 + 20%.

4.3 Résultats obtenus lors des tests

Mes résultats obtenus : 1% des événements totaux ont été remontés en alerte. Attention cela n'est pas vraiment représentatif, car le POC est spécifique à mon contexte. La mémoire peut être très sollicitée en fonction du nombre de signatures différentes que vous possédez, car celles-ci sont chargées en mémoire avec l'histogramme correspondant.

4.4 Alternatives possibles

 Utiliser les outils :

 - statsd+influxdb : il s'agit d'outils spécialisés dans la métrique. InfluxDB permet d'effectuer des calculs de dérivées et de différences. Cependant, à ma connaissance il n'offre pas de possibilités d'alerte, hormis visuel à travers Grafana par exemple.

- Elasticalert [5] : permet une analyse de fréquence qui est réalisée à partir d'Elasticsearch. Il ne s'agit pas d'une analyse temps réel (requêtes effectuées tous les X temps sur ES).

4.5 Améliorations à prévoir

Aujourd'hui, lorsqu'une entrée est détectée au-dessus du seuil fixé, il ajoute une note de risque de fréquence au premier événement trouvé. Cependant, l'évolution est de ne pas noter l’événement, car celui-ci n'est pas forcément à l'origine de la montée de fréquence, mais de créer une alerte qui permettra à un opérateur de déterminer l'origine de l'augmentation de la fréquence.

L'utilisation d'une visualisation en coordonnées parallèles peut être intéressante, d'ailleurs il pourra être intéressant lors de la création de l'alerte d'inclure les données d'une requête d’agrégation sur le problème afin de faciliter et accélérer l'analyse (exemple : requête d'agrégation « HIT » sur le champ source).

Réalisation d'une alerte en cas de sous-fréquence. Le but est de déceler les systèmes obsolètes (changement de serveur et ancien serveur toujours présent, mais plus utilisé). Il serait plus probable de le réaliser avec une post-corrélation sur ES)

Conclusion

ELK est un outil sans limites, cependant il faut adapter votre architecture en fonction du nombre d’événements à traiter.

Les différents outils méritent d'être optimisés. Cependant, ils permettent d'avoir une idée des possibilités offertes par ELK.

D'autres projets d'outils de sécurité basés sur ELK sont intéressants :

- Elasticalert : permet de faire des corrélations très intéressantes directement sur Elasticsearch, c'est très proche de ce que je réalise avec Logstash et mes filtres sur certaines choses comme la fréquence. Je suis tombé sur le projet récemment et je n'ai pas encore eu le temps de tester.

- MozDef [6]: je trouve l’outil très intéressant, mais il est encore basé sur Kibana 3, à ma connaissance il ne peut pas fonctionner avec un Elasticsearch qui utilise le plugin « shield » (pas de possibilité de configurer une authentification sur ES).

- Même si j’ai peu parlé des solutions de gestion d’incidents, j’attire votre attention sur l’outil open source « FIR » développé par le CERT SG [7]. Il est assez simple d’ajouter une couche d’API REST afin de pouvoir créer des incidents automatiquement.

Remerciements

Alexandre Deloup pour sa relecture attentionnée.

Références

[1] Site officiel ELK : https://www.elastic.co/

[2] Site officiel de PICVIZ : https://www.picviz.com/

[3] Code source des outils présentés : https://github.com/lprat/AEE/

[4] Site officiel elasticalert : http://d3js.org/

[5] Site officiel elasticalert : https://github.com/yelp/elastalert

[6] Site officiel MozDef : http://mozdef.readthedocs.org

[7] Site officiel FIR : https://github.com/certsocietegenerale/FIR




Articles qui pourraient vous intéresser...

Sécurisez votre réseau

Magazine
Marque
Linux Pratique
HS n°
Numéro
49
Mois de parution
novembre 2020
Domaines
Résumé

Maintenant que notre serveur principal est déployé et que nous y avons appliqué un premier niveau de sécurisation système, occupons-nous de sa sécurisation réseau. Nous allons détailler en quoi les attaques réseau sont primordiales dans notre modèle de menace. Comme nous le verrons, l’accès distant est le risque principal qui guette nos serveurs. Nous allons mettre en œuvre une sécurité en profondeur et les mesures de protection réseau en seront une de ses dimensions importantes.

Sécurité réseau dans un cluster Kubernetes

Magazine
Marque
MISC
Numéro
112
Mois de parution
novembre 2020
Domaines
Résumé

En introduisant le concept de micro-services, Kubernetes lance un nouveau défi aux solutions d’isolation et de filtrage réseau : comment gérer les droits d’accès réseau dans une infrastructure en constante mutation et dans laquelle une machine n’a plus un rôle prédéterminé ?

Cas pratique sur la sécurisation d'un cluster Kubernetes

Magazine
Marque
MISC
Numéro
112
Mois de parution
novembre 2020
Domaines
Résumé

Cet article présente trois exemples de problèmes de sécurité rencontrés sur des clusters Kubernetes, causés par un manque de maîtrise des applications déployées sur un cluster par ses administrateurs ou par les développeurs des applications s’y exécutant. Nous donnons ensuite des pistes afin de mieux maîtriser et sécuriser ces applications.

Introduction au dossier : Sécurisez vos serveurs et votre réseau local

Magazine
Marque
Linux Pratique
HS n°
Numéro
49
Mois de parution
novembre 2020
Domaines
Résumé

2020 aura été une année marquante pour nos vies et nos sociétés. Il aura fallu se réinventer, trouver des solutions à des situations exceptionnelles. Dans les entreprises, l'Éducation ou la Santé, la mobilisation des ressources informatiques aura été maximale. Nos infrastructures auront ployé, tangué, parfois presque craqué, mais au final, cela aura tenu.

Passez à nftables, le « nouveau » firewall de Linux

Magazine
Marque
Linux Pratique
Numéro
122
Mois de parution
novembre 2020
Domaines
Résumé

Le firewall est un élément important pour sécuriser un réseau. Il est prouvé que la sécurité par l’obscurantisme ne fonctionne pas. Ce n’est donc pas une bonne idée d’utiliser une boîte noire en priant pour que tout se passe bien. Un bon firewall est donc installé sur un système d’exploitation libre. Linux fait évoluer le sien d’iptables vers nftables. Nous montrons dans cet article comment débuter avec la nouvelle mouture.