Documente Academic
Documente Profesional
Documente Cultură
Mémoire présenté
à la Faculté des études supérieures de l’Université Laval
dans le cadre du programme de maı̂trise en informatique
pour l’obtention du grade de Maı̂tre ès sciences
juin 2005
c
°Mathieu Couture, 2005
Résumé
Je désire d’abord remercier ma mère, qui a su me donner le goût des études, ainsi
que mon père, qui m’a appris l’importance de l’intégrité et de la transparence. Je les
remercie tous les deux pour le support et les encouragements qu’ils m’ont donné tout
au long de mon cheminement, autant personnel qu’académique.
Je remercie aussi mon directeur de recherches, le professeur Béchir Ktari, pour avoir
accepté de me diriger dans un domaine aussi stimulant, et pour la confiance qu’il m’a
montrée tout au long de ces deux années de travail. J’adresse aussi mes remerciements
à Frédéric Massicotte, ami et collaborateur au Centre de Recherches sur les Communi-
cations, qui a su à plusieurs occasions me donner des critiques constructives sur mon
travail. Je le remercie aussi pour son sens aigu du bien-être d’autrui, qu’il a su en partie
me communiquer. Je tiens aussi à exprimer ma reconnaissance envers les professeurs
Josée Desharnais et Mohamed Mejri qui ont accepté d’évaluer ce mémoire. Je remer-
cie le personnel administratif du département d’informatique, et plus particulièrement
Lynda Goulet, pour sa bonne humeur et les nombreux services qu’elle m’a rendus.
Merci à Catherine et aux colocs du 22A, pour leur présence chaleureuse et les nom-
breuses soirées que nous avons passées ensemble. Merci à Alexandre Lacasse, pour son
agréable compagnie au cours des deux voyages que nous avons faits en France, de même
que pour ses commentaires encourageants et constructifs. Finalement, je remercie toutes
celles et ceux de mes amies et amis qui m’ont encouragé et supporté dans mes projets.
À ma mère, Nicole,
avec qui tout a commencé.
Résumé ii
Avant-propos iii
Introduction 1
2 Logiques temporelles 55
2.1 Logiques temporelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.1.1 Logique temporelle linéaire . . . . . . . . . . . . . . . . . . . . . . 58
2.1.2 Logique temporelle linéaire passée . . . . . . . . . . . . . . . . . . 60
2.1.3 Logique temporelle linéaire avec passé oubliable . . . . . . . . . . 63
2.1.4 µ-calcul linéaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.2 Logiques temporisées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.2.1 Logique temporelle temporisée . . . . . . . . . . . . . . . . . . . . 67
2.2.2 Logique temporelle métrique . . . . . . . . . . . . . . . . . . . . . 70
2.2.3 TRIO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
2.3 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Conclusion 142
Bibliographie 144
Liste des tableaux
3.1 Architecture du système développée (à droite), et celle de Snort (à gauche). 76
3.2 Balayage SYN. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.3 Poignée de main TCP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.4 Ports TCP fermés. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.5 Fermeture d’une session TCP. . . . . . . . . . . . . . . . . . . . . . . . . 81
3.6 Détection d’un système d’exploitation FreeBSD 3.0 à 4.3. . . . . . . . . . 83
3.7 Règle Snort numéro 343. . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
3.8 Réduction des faux-positifs à l’aide de la détection de vulnérabilités. . . . 84
3.9 Adresses du routeur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Table des figures xi
Contexte
Une autre façon de classer les systèmes de détection d’intrusions est selon le niveau
auquel ils interviennent : hôte ou réseau. Au niveau hôte, les fichiers d’audit analysés
Introduction 2
Plusieurs paradigmes, auxquels nous reviendrons, ont été proposés pour les lan-
gages de signatures des systèmes de détection d’intrusions à base de scénarios. Parmi
ceux-ci, se trouvent ceux basés sur des logiques temporelles. Les logiques temporelles,
en méthodes formelles, ont été utilisées pour s’attaquer à trois catégories de problèmes :
Le présent travail s’inscrit donc dans un contexte de validation. Dans notre cas, le
programme à valider sera le réseau, et son exécution sera matérialisée par les différents
fichiers d’audit mis à notre disposition. Bien que nous nous intéressions particulièrement
aux traces de trafic, la méthode que nous proposons s’adapte aussi bien aux autres
fichiers d’audit. Il convient de noter, avant d’aller plus loin, que la détection d’intrusions
a ceci de particulier que les exécutions auxquelles on s’intéresse sont de longueur infinie.
Les méthodes proposées pour des exécutions finies de programmes ne sauraient donc
s’appliquer ici.
Objectifs et méthodologie
Nous avons donc dû nous familiariser avec les logiques temporelles, et voir comment
celles-ci pouvaient être utilisées pour répondre à nos besoins. Un de ces besoins était
d’acquérir passivement de l’information sur un réseau informatique, de façon à faire une
détection d’intrusions plus accrue. En effet, un reproche ayant souvent été fait aux sys-
tèmes de détection d’intrusions est de ne pas tenir compte du contexte avant de signaler
des alarmes. Cette sauvegarde du contexte ne saurait cependant être faite sans un coût
additionnel, le pire cas, tout à fait déraisonnable, constituant la sauvegarde complète de
la trace de trafic. Au mieux, on ne sauvegarde que l’information nécessaire, pas un bit
de plus. La capacité à traiter un fichier d’audit enregistrement par enregistrement sans
revenir en arrière est ce que nous appellerons traitement en ligne. Les trois objectifs
principaux de notre travail sont donc :
1. Paradigme déclaratif,
2. acquisition passive d’information,
3. et traitement en ligne.
Nous proposons deux approches pour s’attaquer à ces problèmes. La première est
une approche hybride où la séparation entre l’information acquise et les scénarios à
reconnaı̂tre est claire, tant au niveau du langage que du logiciel développé, et la seconde
approche permet d’uniformiser l’acquisition d’information et la définition de scénarios
à reconnaı̂tre. Dans le premier cas, le langage utilisé pour décrire les scénarios est basé
sur une logique temporelle avec opérateurs futurs, et dans le second cas, il est basé sur
une logique temporelle avec opérateurs passés.
Organisation
biner et d’effectuer les deux tâches dans un paradigme uniforme. Au chapitre 4, nous
présentons un autre langage, développé en vue de combler les lacunes identifiées avec le
premier. Entre autres, ce langage a la propriété d’être purement déclaratif. Pour cha-
cun de ces deux langages, nous présentons les algorithmes de vérification à utiliser et
donnons les preuves de complétude et de cohérence.
Un système de détection d’intrusions (ou IDS pour Intrusion Detection System) est
une composante logicielle responsable d’identifier une utilisation non-désirable d’une
ressource informatique. On peut classer les IDS selon plusieurs critères. Les critères
les plus généralement rencontrés sont le type de sonde (réseau ou hôte) et le type
d’algorithme (statistique ou à base de signatures).
Les systèmes basés sur des méthodes statistiques, aussi appelés détecteurs d’anoma-
lies, présentent l’avantage de pouvoir découvrir des attaques encore non-répertoriées.
Ils fonctionnent généralement en deux phases : une phase d’apprentissage et une phase
de détection proprement dite. Pendant la phase d’apprentissage, le système dresse un
profil de ce qui sera par la suite considéré comme un comportement normal. La phase
de reconnaissance, quand à elle, consistera à détecter des déviations du comportement
normal. Normalement, la phase d’apprentissage continue pendant la phase de recon-
naissance. Cette stratégie permet de faire face à la nature changeante du comportement
des utilisateurs et ainsi de s’adapter au changement. L’hypothèse derrière cette façon
de faire est que le comportement normal d’un utilisateur change de façon graduelle
et qu’une utilisation non-désirée du réseau entraı̂nera un changement brusque dans le
comportement. Cette hypothèse entraı̂ne à la fois des faux-positifs (fausse alarme) et
des faux-négatifs (attaque non-détectée). Les faux positifs surviennent lorsque les uti-
lisateurs changent leur comportement de façon brusque, par exemple en installant un
nouveau logiciel. Les faux négatifs, quand à eux, surviennent lorsqu’un utilisateur mal-
veillant au courant de la stratégie de détection change son comportement petit à petit
de façon à exploiter la capacité d’adaptation du système.
Les IDS fonctionnant à base de signatures, quand à eux, engendrent moins de faux-
positifs car on leur spécifie exactement ce qu’ils doivent détecter. Cependant, comme
Chapitre 1. Systèmes de détection d’intrusions 6
Les IDS fonctionnant à base de patrons de bits comportent cependant des faiblesses
au niveau de l’expressivité des signatures qui peuvent mener autant à des faux-positifs
qu’à des faux-négatifs. C’est pourquoi la communauté scientifique persiste à mettre
tant d’efforts dans le développement d’IDS pouvant détecter des patrons d’événements
séparés dans le temps. Les signatures de ces systèmes permettent de prendre en compte
le contexte avant de signaler une alarme. Par exemple, dans le cas d’un IDS réseau, ils
permettent de spécifier que pour être valide, une attaque doit survenir dans le contexte
d’une session active. Différents modèles théoriques ont été utilisés dans le cadre du
développement de ces systèmes, et nous allons consacrer le reste de ce chapitre à leur
étude.
Dans [2], Cédric Michel et Ludovic Mé proposent une taxonomie des langages utilisés
en détection d’intrusions divisant ceux-ci en six catégories : langages d’événements,
d’exploits, de rapports, de détection, de corrélation et de rapports. Dans ce chapitre,
nous nous attaquons au cas particulier des langages de détection, que nous subdivisons
en cinq catégories.
Nous commencerons notre étude dans la section 1.1 par les langages les plus simples :
les langages spécifiques au domaine. Ces langages ont l’avantage de présenter une grande
simplicité, mais offrent souvent une faible expressivité et peu de souplesse. Par la suite,
dans la section 1.2, nous jetterons un oeil aux langages impératifs ayant été développés
spécialement pour la détection d’intrusions. Ce sont ceux qui se rapprochent le plus des
langages de programmation habituels. Ils présentent l’avantage d’offrir des primitives
associées au traitement d’événements et des systèmes de types appropriés à la détection
d’intrusions. Les signatures développées avec ces langages sont cependant souvent diffi-
ciles à maintenir. Les langages basés sur les systèmes de transition, que nous traiterons
Chapitre 1. Systèmes de détection d’intrusions 7
dans la section 1.3, ont été mis au point afin de pouvoir spécifier d’une façon déclarative
les scénarios d’attaques. Ces langages comportent cependant une partie impérative dont
l’objectif est souvent d’emmagasiner une partie du contexte. Les systèmes experts, dont
nous parlerons dans la section 1.4, sont spécialisés dans l’accumulation et, surtout, l’in-
férence d’information concernant le contexte. Afin d’éviter les problèmes de mémoire,
cette information doit cependant être gérée explicitement, et le niveau d’abstraction
désiré n’est pas toujours atteint. Les systèmes basés sur des logiques temporelles, dont
nous parlerons finalement dans la section 1.5, sont ceux qui se rapprochent le plus de
systèmes à base de signatures purement déclaratives.
Un langage spécifique au domaine, tel que défini dans [3], est un langage de pro-
grammation spécialement conçu pour capturer la sémantique propre à un domaine d’ap-
plication particulier. Des exemples de langages spécifiques au domaine sont HTML et
SQL, respectivement conçus pour l’édition d’hypertextes et la consultation de bases de
données. Dans le présent chapitre, chacun des systèmes de détection d’intrusions que
nous présentons vient avec son propre langage permettant de le configurer et/ou de
spécifier des signatures particulières d’attaque. Ces langages, spécialement conçus pour
la détection d’intrusions ou pour la surveillance de systèmes en général, tombent donc
tous dans la catégorie des langages spécifiques au domaine. Cependant, dans la plupart
des cas, ces langages sont conçus avec un certain souci d’abstraction permettant des
les utiliser pour différents types de fichiers d’audit. Par exemple, le langage STATL
peut être utilisé autant pour une détection d’intrusions au niveau hôte (dans le cas de
WinSTAT et USTAT), qu’au niveau réseau (dans le cas de NetSTAT).
Les trois systèmes que nous présentons dans cette section se distinguent de par le
fait que les langages qu’ils utilisent sont liés d’encore plus près au type de détection
particulier qu’ils permettent d’effectuer. Ils ne sont pas définis sur une sémantique abs-
traite de fichiers d’audit, mais sur des sémantiques très concrètes reliées aux fichiers
d’audits de processus (dans le cas de Panoptis), au filtrage de paquets (dans le cas de
Snort), et à l’identification passive de failles de sécurité (dans le cas de NeVO). Nous
présentons dès maintenant ces trois systèmes.
Chapitre 1. Systèmes de détection d’intrusions 8
#
# Configuration file for host pooh
#
# $ Id : poo.dsl 1.6 2000/05/30 12 :26 :58 dds Exp $
#
HZ = 100 # "Floating point" value divisor
bigend = FALSE # Set to TRUE for big endian (e.g. Sun), FALSE for
# little endian (e.g. VAX, Intel x86)
map = TRUE # Set to TRUE to map uid/tty numbers to names
EPSILON = 150 # New maxima difference threshold (%)
report = TRUE # Set to TRUE to report new/updated entries
unlink = FALSE # Set to TRUE to start fresh
# Reporting procedure
output = ’| /usr/bin/tee /dev/console | /bin/mail root’
# Databases and parameters to check
dbcheck(tty, minbmin, maxbmin, maxio, maxcount) # Terminals
dbcheck(comm, ALL) # Commands
dbcheck(uid, ALL) # Users
dbcheck(uidtty, maxcount) # Users on a terminal
dbcheck(uidcomm, minbmin, maxbmin, maxutime, # Users of a command
maxstime, maxmem, maxrw, maxcount, maxasu)
# Map users and terminals into groups
usermap(caduser, john, marry, jill)
usermap(admin, root, bin, uucp, mail, news)
termmap(room202, tty31, tty32, tty33, tty34, tty35)
termmap(ptys, ttyp01, ttyp02, ttyp03, ttyp04, ttyp05, ttyp06)
1.1.1 Panoptis
Panoptis [3] se distingue des autres systèmes de détection d’intrusions présentés dans
ce chapitre par le fait qu’il s’agit du seul système basé sur une détection d’anomalies
que nous avons choisi de présenter. Comme nos travaux se situent plutôt dans le cadre
de reconnaissance de scénarios, nous avons mis le focus sur ces types de systèmes, mais
nous avons tout de même jugé utile, par souci de complétude, d’inclure au moins un
détecteur d’anomalies.
Comme nous l’avons déjà dit, Panoptis est configurable à l’aide d’un langage spéci-
fique au domaine. Ce domaine est celui des processus d’un système Unix, et Panoptis
prend donc en entrée des fichiers d’audits de processus. Les auteurs affirment que le
langage a quand même été conçu de façon assez générale pour pouvoir fonctionner sur
n’importe quel système fournissant des fichiers d’audits de processus, dont Windows NT.
Panoptis maintient des tables contenant les profils d’activités pour différents utilisa-
teurs, terminaux, processus, ou groupages et/ou couplage de ces entités. Par exemple,
il peut maintenir une table concernant l’utilisation d’un processus donné, pour trois
utilisateurs spécifiques utilisant un même terminal. Les informations maintenues pour
Chapitre 1. Systèmes de détection d’intrusions 9
#
# Panoptis crontab file for host pooh
#
# The format of this file is :
# Hour Minute Day-of-month Month Day-of-week Command
* 5,25,45 * * * panoptis pooh-quick.cfg pooh.20min 20m
8-18 05 * * * panoptis pooh-hour.cfg pooh.workhour 1h
19-7 05 * * * panoptis pooh-hour.cfg pooh.late 1h
4 50 * * 1-5 panoptis pooh-day.cfg pooh.workday 24h
4 50 * * 6,0 panoptis pooh-day.cfg pooh.weekend 24h
2 20 * * 0 panoptis pooh-full.cfg pooh.weekly 7d
/usr/adm/pacct ? /usr/adm/pacct
chacune des entités surveillées sont paramétrables à l’aide d’un fichier de configuration.
Un exemple de fichier de configuration utilisé par Panoptis se trouve à la figure 1.1. Ce
fichier se divise en quatre parties. En premier lieu, on trouve un ensemble de définitions
de variables qui servent à définir le fonctionnement général du programme. Ensuite, on
indique la méthode de rapport à utiliser. La troisième partie est celle où on spécifie
ce que l’on veut surveiller. Pour chacune des cinq tables tty, comm, uid, uidtty et
uidcomm, on spécifie les champs que l’on veut surveiller. Ces champs sont prédéfinis et
font partie de la définition du langage. Le mot-clé ALL signifie que l’on veut surveiller
tous les champs. Finalement, on trouve les options de groupage qui permettent de re-
grouper certains utilisateurs ou terminaux en un seul. Par exemple, à la figure 1.1, les
utilisateurs john, marry et jill sont regroupés sous l’utilisateur abstrait caduser.
Une fois ces options choisies, il reste à configurer la fréquence à laquelle les fichiers
d’audits sont lus par le système. Panoptis peut soit les lire en continu, soit être exécuté
à des intervalles prédéfinis. Un exemple de fichier de planification de tâches se trouve
à la figure 1.2. On voit que des intervalles différents, de même que des fichiers de
configuration différents, peuvent être utilisés selon les périodes de la journée ou de la
semaine. Finalement, à chaque fois que Panoptis est exécuté sur un fichier d’audit,
celui-ci compare les valeurs calculées à celles se trouvant dans les tables et rapporte les
entrées qui présentent des différences significatives.
que l’utilisateur dds a utilisé le compilateur gcc pour la première fois, alors que la
seconde signifie que l’utilisateur root, à l’aide de la commande ls, a listé le contenu de
répertoires contenant deux fois plus de fichiers qu’à l’habitude.
1.1.2 Snort
Modules d’affichage
Module de détection
Préprocesseurs
Module de décodage
Libpcap
Réseau
Par exemple, si une politique de sécurité spécifie que personne ne doit se connecter
au service Telnet en utilisant l’utilisateur root, on voudrait pouvoir spécifier une règle
qui surveille les paquets contenant la chaı̂ne de caractères user : root. Cependant, il
est possible d’échapper à cette règle en utilisant le caractère de retour en arrière <BS>.
En tapant user : roo<BS>ot, un utilisateur peut alors se connecter sous le compte
root sans se faire repérer. Des astuces similaires s’appliquent au trafic HTTP. Pour ces
raisons, Snort comporte des préprocesseurs responsables de régulariser le trafic Telnet
et HTTP. Il est cependant possible d’échapper au système de détection d’intrusions
autrement qu’en utilisant des astuces de formatage au niveau application. Une astuce
bien connue consiste à fragmenter le paquet offensif en plusieurs petits paquets, de
façon à ce qu’aucun des paquets résultants ne soit alarmant. Une autre façon de faire
est d’envoyer les paquets dans un ordre ne correspondant pas à celui dans lequel ils
seront interprétés par la machine qui les recevra. Les préprocesseurs frag2 et stream4
ont donc été ajoutés afin de remettre ensemble les paquets fragmentés et de présenter
les paquets au système de détection d’intrusions dans le même ordre que celui dans
lequel le système les recevant les interprètera. Il s’agit encore une fois d’un travail de
formatage visant à éviter certaines attaques d’évasion.
En plus de cette fonctionnalité, les préprocesseurs sont aussi utilisés à deux autres
fins : détecter les scénarios d’attaques utilisant plusieurs paquets, ainsi que permettre
l’acquisition passive d’information. Un exemple simple de catégorie d’attaques nécessi-
tant la détection de plusieurs paquets est le balayage (scan). Qu’il s’agisse d’un balayage
TCP, UDP, ICMP ou autre, les attaques de balayage ont pour objectif de permettre
Chapitre 1. Systèmes de détection d’intrusions 13
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg :"SCAN nmap TCP" ;
stateless ; flags :A,12 ; ack :0 ; reference :arachnids,28 ;
classtype :attempted-recon ; sid :628 ; rev :3 ;)
alert tcp any 143 -> any any (msg :"IMAP login" ; content :"OK LOGIN" ;
flow :established,to_client ; flowbits :set,logged_in ; flowbits :noalert)
alert tcp any any -> any 143 (msg :"IMAP list overflow attempt" ;
flow :established,to_server ; flowbits :isset,logged_in ; content :"LIST" ;)
teless), il faut alors signaler un balayage TCP fait avec nmap [7] (msg :"SCAN nmap
TCP"). Le reste de la signature constitue la documentation de l’attaque.
Cette revue de Snort serait incomplète si aucune allusion n’était faite à la nature
particulière des modules de détection react et flowbits. Le module react fait de Snort
non plus un simple système de détection d’intrusions, mais un système de prévention
d’intrusions. Il permet de réagir en interrompant les sessions TCP jugées offensives. Il
permet, de plus, d’envoyer un message d’avertissement à l’usager expliquant pourquoi la
communication a été interrompue. À la figure 1.6, on voit comment ce module peut être
utilisé pour empêcher les enfants d’aller sur des sites pornographiques. Lorsqu’un usager
tente de consulter un site web contenant le mot sex, la session TCP correspondante
est interrompue et le message not for children est affiché dans son navigateur. Cet
exemple est tiré de [1].
Le module flowbits, quant à lui, a été mis au point afin de répondre au besoin de
plus en plus grandissant de permettre à Snort de tenir compte de l’état des sessions au
niveau application. Il permet de définir des variables booléennes (drapeaux) servant à
suivre l’état de la session en changeant la valeur de ces variables (set, unset, reset,
toggle) dans certaines règles, et en les lisant dans d’autres (isset, isnotset). Comme
ce module a été mis au point pour suivre l’état des sessions au dessus de TCP, ces
variables sont instanciées et initialisées à chaque nouvelle session TCP. Il est à noter
que les règles servant à mettre à jour les valeurs des drapeaux contiennent généralement
le mot-clé noalert, signifiant que même si la règle est activée, aucune alerte ne doit
être générée. À la figure 1.7, on peut voir comment l’utilisation du module de détection
flowbits permet de diminuer le nombre de faux positifs lors de la détection d’une attaque
de débordement de tampon d’un serveur IMAP, un protocole s’exécutant généralement
Chapitre 1. Systèmes de détection d’intrusions 15
sur le port TCP numéro 143. Cette attaque, exploitant une vulnérabilité de certaines
versions de imapd, s’effectue en utilisant incorrectement la commande LIST, et ne peut
réussir que si l’usager est correctement connecté (au niveau application). La première
signature de la figure 1.7 sert donc à mémoriser le fait que le mot de passe de l’usager a
été accepté. Ce sera le cas si on voit passer la chaı̂ne de caractères OK LOGIN. Dans ce
cas, on active drapeau logged_in et on spécifie que cette règle ne doit pas déclencher
d’alerte en utilisant le mot-clé noalert. L’utilisation du préprocesseur flow nous permet,
d’une part, de vérifier que le paquet survient bel et bien dans le contexte d’une session
TCP active, et d’autre part, qu’il provient bien du serveur. La seconde signature permet
la détection de l’attaque proprement dite. Il s’agit d’une version légèrement modifiée
de la signature numéro 2118. Une alerte sera lancée si on voit passer le mot LIST en
direction du serveur dans le cadre d’une session TCP active appartenant à un usager
ayant réussi à se connecter.
1.1.3 NeVO
NeVO, mis au point par la compagnie Tenable, est un identificateur passif de failles
de sécurité, ce qui signifie qu’il effectue son travail sans avoir à injecter de trafic sur le
réseau. Il se présente comme un complément ou une alternative à Nessus [11, 12, 13], qui
est un identificateur actif de failles de sécurité. Bien que l’identification active puisse
en général fournir des rapports plus exhaustifs que l’identification passive, elle peut
souvent s’avérer très dérangeante pour le réseau en cours d’audit, voire même mener à
son instabilité. De plus, l’identification passive permet d’avoir accès à une information
continuellement mise à jour. Finalement, l’identification passive permet non-seulement
de trouver les failles sur les serveurs, mais aussi sur les clients. Pour toutes ces raisons,
l’identification passive s’impose comme un complément essentiel à l’identification active.
id=700018
nooutput
hs_sport=21
name=Anonymous FTP (login : ftp)
pmatch=^USER ftp
match=^331
NEXT #-------------------------------
id=700019
dependency=700018
timed-dependency=5
hs_sport=21
name=Anonymous FTP enabled
description=The remote FTP server has anonymous access enabled.
risk=LOW
pmatch=^PASS
match=^230
tème de détection d’intrusions, être archivée à chaque fois qu’elle est détectée. Au
contraire, l’identificateur doit plutôt tenir à jour un modèle du réseau et le fournir
sur demande. Cette demande sera typiquement faite au moment de la consultation
du journal d’attaques du système de détection d’intrusions. L’information n’est
donc pas envoyée directement à l’utilisateur, mais conservée jusqu’à ce qu’elle soit
demandée.
La distinction entre les langages impératifs et les langages déclaratifs, dans la litté-
rature, n’est pas aussi nette que l’on pourrait le croire. Généralement, on se contente
de définir informellement un langage déclaratif comme étant un langage permettant de
définir ce que l’on veut identifier, plutôt que comment l’identifier. Les autres langages
tombent tous sous la catégorie des langages impératifs. Sur la base de cette définition,
le langage de signatures de Snort, par exemple, est clairement déclaratif.
À l’exception des langages présentés dans la présente section, les langages que nous
présentons dans ce chapitre ont tous la prétention d’être déclaratifs. Plus loin dans
Chapitre 1. Systèmes de détection d’intrusions 19
1.2.1 ASAX
ASAX [14, 15, 16] a été développé dans le but d’analyser des traces d’audit. La syn-
taxe de RUSSEL, le langage utilisé pour écrire les signatures, se trouve à la table 1.2.
L’exécution d’un programme RUSSEL implique l’analyse séquentielle d’un fichier de
trace d’audit. Une trace d’audit est définie comme étant une séquence finie d’enregis-
trements, qui eux sont définis comme étant une fonction partielle associant des valeurs
à un ensemble d’étiquettes, aussi appelées champs. Ces enregistrements sont passés à
ASAX dans un format normalisé (Normalized Audit Data Format), qui permet de faire
abstraction du type d’audit.
Une autre caractéristique d’un contexte d’exécution est l’ensemble des valeurs des
différentes variables. Ces variables peuvent être de trois types : locales à une règle, glo-
bales, ou correspondre aux champs d’un enregistrement. Comme l’ordre d’exécution des
règles n’est pas spécifié, l’utilisation de variables globales peut amener un certain non-
déterminisme. Aussi, pour palier aux problèmes découlant du fait que certains champs
peuvent être absents de certains enregistrements, on a ajouté un mot-clé present, per-
mettant de tester la présence d’un champ. Lorsqu’un champ est absent, il est tout de
Chapitre 1. Systèmes de détection d’intrusions 20
R : := rule Q ( . . . ; G ; . . . ) ; . . . ; H ; . . . ; A
G : := P:T
H : := V:T
A : := V := E | Y ( . . . , E , . . . )
| trigger off M Q ( . . . , E , . . . ) | begin . . . ; A ;. . . end
| do . . . ; C → A ; . . . od | if . . . ; C → A ; . . . fi
C : := true | F present | not C | C B C | E S E
E : := L | V | F | P | -E | E O E | X(. . . ,E,. . . )
B : := and | or
O : := + | - | * | div | mod
S : := > | < | = | 6= | ≤ | ≥
M : := for current | for next | at completion
T : := integer | byte | string
même possible de lire sa valeur. La valeur lue est alors celle qui était présente la der-
nière fois que le champ était présent, et les résultats obtenus sont susceptibles d’être
très différents de ceux escomptés.
À la figure 1.9 se trouve un exemple de signature écrit en RUSSEL pour ASAX. Cet
exemple a été copié tel quel de [14]. Les variables evt et res sont des champs qui sont
supposés faire partie de chaque enregistrement. Ensemble, les deux règles Failed_login
et Count_rule1 servent à surveiller un utilisateur qui échouerait à se connecter trop
souvent. On remarque que la règle Failed_login se réactive inconditionnellement (ligne
8). La règle Count_rule1 démontre la sémantique particulière d’un bloc if . . . fi. Ces
blocs renferment une série de conditions, chacune suivie d’un bloc d’actions. Dès qu’une
condition est vérifiée, le bloc correspondant est exécuté et le bloc if . . . fi est terminé.
Chapitre 1. Systèmes de détection d’intrusions 21
L’instruction skip est l’instruction qui ne fait rien. Donc, si le délai est écoulé (ligne 21),
la règle n’est pas réactivée. Sinon, la condition true est vérifiée et la règle est réactivée
pour le prochain enregistrement.
Interpréteur de scripts
Module d ’événements
Libpcap
Réseau
1.2.2 BRO
Malgré le fait que les deux architectures puissent se ressembler au premier abord,
il y a tout de même deux différences majeures entre les deux systèmes : l’abstraction
créée par le module d’événements et la nature impérative du langage de signatures.
Alors que les objets fournis par les préprocesseurs de Snort au module de détection
sont des paquets, ceux fournis par le module d’événements de Bro à l’interpréteur de
scripts sont d’un type plus abstrait : celui d’événement. Ceci implique qu’un processus
de filtrage est fait par le module d’événements, et que seulement les informations jugées
pertinentes sont passées à l’interpréteur de scripts. Un des objectifs de cette étape
de filtrage est de permettre au système de fonctionner en temps réel, malgré la grande
affluence de trafic pouvant circuler sur le réseau. Aussi, les politiques de sécurité vérifiées
avec Bro sont normalement plus haut niveau que celles vérifiées par Snort, voire même la
plupart des autres systèmes de détection d’intrusions. En fait, il s’agit d’un des objectifs
poursuivis par les auteurs : permettre une séparation claire entre les mécanismes et les
politiques de sécurité. Les mécanismes, dans ce cas, sont les différents moyens de générer
un événement donné, alors que les politiques de sécurité sont les actions à prendre
lorsque les événements sont identifiés. Les mécanismes sont donc gérés au niveau du
Chapitre 1. Systèmes de détection d’intrusions 23
moteur d’événements, programmé en C++, alors que les politiques de sécurité sont
mises en oeuvre par l’interpréteur de scripts. Ces dernières sont écrites dans un langage
de signatures propre à Bro.
Les travaux entourant Bro sont grandement motivés par la pratique et l’article [17]
traite de plusieurs problèmes concrets devant être pris en compte lors de la concep-
tion d’un système de détection d’intrusions en temps réel. L’auteur parle entre autres
des différentes façons d’attaquer le système lui-même et des moyens de s’en défendre,
des problèmes reliés à la surveillance de réseaux haut-débit (principalement en ce qui
concerne la perte de paquets), des différentes façons de gérer les chronomètres, des
avantages et des inconvénients de disposer d’un langage compilé, des problèmes reliés
au redémarrage du système (souvent nécessaire pour faire un nettoyage de la mémoire),
etc. Quiconque s’engage dans des travaux reliés à la détection d’intrusions en temps
réel devrait au moins prendre quelques minutes pour jeter un oeil à cet article.
vient avec un langage impératif interprété qui permet d’exprimer des scripts vérifiant
des politiques de sécurité à un plus haut niveau que la plupart des autres systèmes de
détection d’intrusions. Son architecture en couches, semblable à celle de Snort, a été
conçu avec un souci d’extensibilité et permet d’ajouter des modules d’événements et
de définir nos propres politiques de sécurité assez facilement. Le langage utilisé pour
exprimer les politiques de sécurité reste cependant impératif, ce qui implique que l’on
indique plutôt comment vérifier les politiques, que les politiques elle-mêmes. Il faut
tout de même donner à Bro le mérite d’avoir eu l’idée de séparer les mécanismes des
politiques au niveau même de l’architecture du logiciel et d’avoir prévu deux modules
différents pour gérer ces deux aspects.
Chacun des systèmes que nous présentons dans cette section a été conçu dans l’idée
de permettre la représentation des signatures d’attaque de façon déclarative. Les au-
teurs de ces travaux, surtout en ce qui concerne STATL et IDIOT, considèrent que
Chapitre 1. Systèmes de détection d’intrusions 26
les systèmes de transition constituent un moyen de représenter ce qui doit être détecté
plutôt que comment le détecter. Le dictionnaire en ligne Foldoc2 propose, pour le terme
langage déclaratif, la définition suivante :
Declarative languages contrast with imperative languages which specify explicit ma-
nipulation of the computer’s internal state ; or procedural languages which specify an
explicit sequence of steps to follow.
The most common examples of declarative languages are logic programming lan-
guages such as Prolog and functional languages like Haskell.
Tous les langages étudiés dans le reste de ce chapitre disposent d’un algorithme fixe
de vérification, comme mentionné dans le premier paragraphe de la définition. Cepen-
dant, comme nous le verrons, plusieurs sont aussi pourvus de constructions syntaxiques
qui permettent de modifier explicitement le contenu de la mémoire. En se référant au
second paragraphe de la définition, on réalise alors que l’on se rapproche de la frontière
entre langage déclaratif et langage impératif.
Maintenant que nous avons une définition plus claire de la différence entre langage
déclaratif et impératif, nous sommes prêts à étudier les systèmes de détection d’intru-
sions basés sur les systèmes de transition, qui sont à la frontière entre les deux types de
langages.
1.3.1 STAT
STAT (State Transition Analysis Technique) [18] utilise une approche qui se veut
déclarative. Le formalisme utilisé pour spécifier les signatures se base sur les automates
généralisés. STAT fournit un langage général, STATL (STAT Language), permettant de
spécifier des automates. La sémantique attribuée à ces systèmes dépend de l’extension
utilisée. Les différentes extensions permettent de faire différents types de détection d’in-
trusions. Par exemple, les extensions USTAT [19] et WinSTAT permettent de spécifier
des signatures au niveau hôte pour les systèmes d’exploitation Unix et Windows, alors
2
www.foldoc.org
Chapitre 1. Systèmes de détection d’intrusions 27
que l’extension NetSTAT [20] permet de spécifier des signatures au niveau réseau.
Les systèmes de transition définis à l’aide du langage STATL sont constitués d’états
et de transitions. Les transitions, qui peuvent être activées par les actions effectuées sur
le système, sont de trois types : consuming, non-consuming, et unwinding. Les transi-
tions de type consuming correspondent exactement aux transitions régulières des auto-
mates finis : lorsque l’action correspondant à l’étiquette de la transition est effectuée,
le système change d’état. Les transitions non-consuming créent une copie du système
avant de changer d’état. Les transitions consuming permettent donc de spécifier des pro-
priétés de la prochaine action telle que, alors les transitions non-consuming permettent
de spécifier l’existence d’une prochaine action telle que. Finalement, les actions unwin-
ding permettent d’éliminer des systèmes. Plus spécifiquement, ils permettent d’éliminer
toutes les copies ayant été faites du système depuis qu’il a traversé un état donné. Ceci
permet de représenter le fait qu’une action peut tout annuler.
À la figure 1.12, on peut voir un exemple, tiré de [18], de scénario exprimable à l’aide
du langage STATL. Ce scénario permet de détecter une session TCP à demi ouverte. Une
session TCP est dite à demi ouverte si le serveur a accepté une demande de connexion
(le SYN et le SYN-ACK ont eu lieu) et est en attente de la confirmation (le dernier
ACK) de la part de l’hôte ayant demandé la connexion. Si, pour un serveur donné,
trop de connexions sont simultanément à demi ouvertes, celui-ci peut cesser d’accepter
de nouvelles connexions. Il est possible d’exploiter ce fait pour effectuer une attaque
de déni de service, connue sous le nom de SYN-Flood. Le scénario de la figure 1.12
comporte donc trois états : l’état initial (s0), l’état en attente de confirmation (s1), et
l’état final s2. Le scénario est paramétré par une variable, timeout, indiquant combien
de temps on doit attendre la confirmation. La première transition du scénario, SYN,
est activée lorsqu’un paquet de type SYN (ayant le drapeau SYN activé et le drapeau
ACK désactivé) est identifié. Cette transition est de type non-consuming, signifiant que
lorsqu’elle est activée, une copie du scénario est effectuée avant de passer à l’état s1.
Aussi, lors de l’activation de cette transition, les variables locales du scénario servant
à mémoriser les adresses IP et les ports TCP de la victime et de l’attaquant sont
initialisées selon les valeurs contenues dans le paquet. On voit bien, avec la transition
SYN, les deux parties constituantes d’une transition : la condition d’activation et les
actions à poser lors de l’activation. En arrivant dans l’état s1, le chronomètre t0 est
démarré avec comme délai la valeur de la variable timeout fournie en paramètre. Si,
avant l’écoulement du délai, un paquet ACK ou un paquet RST sont vus pour les
adresses IP et les ports TCP correspondant au SYN (transitions ACK et RST), tout est
annulé et le scénario retourne à l’état original car ces transitions sont de type unwinding.
Si, au contraire, aucun de ces paquets n’est vu avant que le délai ne s’écoule, la transition
Timed_out est activée et le scénario passe (sans duplication, puisque la transition est de
Chapitre 1. Systèmes de détection d’intrusions 28
use netstat ;
scenario halfopentcp(int timeout)
{
IPAddress victim_addr ;
Port victim_port ;
IPAddress attacker_addr ;
Port attacker_port ;
timer t0 ;
initial state s0 {}
transition SYN (s0 -> s1) nonconsuming
{
[IP ip [TCP tcp]] :
(tcp.tcp_header.flags & TH_SYN) && !(tcp.tcp_header.flags & TH_ACK)
{
victim_addr=ip.header.dst ;
victim_port=tcp.header.dst ;
attacker_addr=ip.header.src ;
attacker_port=tcp.header.src ;
}
}
state s1
{
{ timer_start(t0, timeout) ; }
}
transition ACK (s1 -> s0) unwinding
{
[IP ip [TCP tcp]] :
(ip.header.dst==victim_addr) && (tcp.header.dst==victim_port) &&
(ip.header.src==attacker_addr) && (tcp.header.src==attacker_port) &&
!(tcp.header.flags & TH_SYN) && (tcp.header.flags & TH_ACK)
}
transition RST (s1 -> s0) unwinding
{
[IP ip [TCP tcp]] :
(ip.header.src==victim_addr) && (tcp.header.src==victim_port) &&
(ip.header.dst==attacker_addr) && (tcp.header.dst==attacker_port) &&
(tcp.header.flags & TH_RST)
}
transition Timed_out (s1 -> s2) consuming
{
timer t0 ;
}
state s2
{
{
HALFOPENTCP e ;
e = new HALFOPENTCP(attacker_addr, attacker_port, victim_addr,
victim_port, start) ;
enqueue_event(e, HALFOPENTCP, start) ;
}
}
}
type consuming) à l’état s2. En arrivant dans cet état, un nouvel événement est créé,
signalant ainsi le fait qu’une session à demi ouverte a été identifiée. Cet événement peut
alors servir de condition d’activation aux transitions d’autres scénarios, servant ainsi à
la détection, par exemple, d’une attaque SYN-Flood. À la figure 1.13, on peut voir une
représentation graphique de ce scénario, obtenue à l’aide de l’outil STATed, un éditeur
de scénarios téléchargeable avec STAT. Les conditions d’activation et les actions à poser
peuvent être spécifiées en cliquant sur les états et les transitions.
En bref, le système STAT, avec le langage STATL, constitue une avancée en direction
d’un langage déclaratif. Cependant, avec ses trois types de transition, ses variables
partagées et ses actions à exécuter à l’activation des transitions, le langage STATL
comporte encore une bonne part d’impérativité. Un aspect très intéressant de l’extension
NetSTAT est la prise en compte de la topologie pour spécifier, améliorer et automatiser
une partie de la détection d’intrusions.
1.3.2 IDIOT
IDIOT [21, 22, 23], tout comme STAT, utilise une approche qui se veut déclarative.
Le formalisme employé pour spécifier les signatures se base sur les réseaux de Petri
colorés. Cette approche présente deux avantages. Premièrement, les réseaux de Petri
permettent de bien représenter le parallélisme et le synchronisme, deux phénomènes
bien présents en détection d’intrusions. Avec STAT, le parallélisme est sous-entendu par
l’exécution parallèle des systèmes de transition, alors que le synchronisme est représenté
par l’utilisation des variables partagées. Le deuxième avantage de l’approche choisie
réside dans le choix de réseaux colorés. Les réseaux colorés permettent, comme avec
STAT, de définir des patrons d’événements reliés les uns aux autres. Par exemple, ils
Chapitre 1. Systèmes de détection d’intrusions 31
permettent de spécifier que deux paquets différents doivent avoir les mêmes adresses IP
sources et destination.
La documentation sur IDIOT est plutôt rare, et il fait partie des systèmes qu’il ne
nous a pas semblé possible de télécharger. Ce que nous avons à notre disposition est
un nombre assez restreint d’exemples tirés d’articles. L’exemple de la figure 1.15 est
tiré de [23]. Il montre comment on peut utiliser, dans le système IDIOT, les réseaux de
Petri colorés pour spécifier l’établissement d’une session TCP. Aux lignes 6,7, et 8 se
trouvent les déclarations des états. Comme l’établissement d’une session TCP se fait
en trois étapes, on a besoin de quatre états pour la spécifier. Le mot-clé nodup, à la
ligne 7, signifie que lorsqu’une transition sortante est activée, l’état n’est pas dupliqué.
On obtient donc ici un résultat semblable aux transitions consuming de STAT, sauf
que la spécification se fait au niveau des états plutôt qu’au niveau des transitions. La
ligne 9 spécifie quelle est l’action à effectuer lorsque l’état final a été atteint. Les lignes
10 à 18 spécifient un invariant qui doit être vérifié par le système. La notion d’invariant,
en IDIOT, s’apparente à la notion de transition unwinding en STATL. La spécification
d’un invariant prend la forme d’un réseau de Petri, avec ses propres états et ses propres
transitions. À chaque fois qu’un jeton sort de l’état initial, une copie en est placée dans
l’état initial de l’invariant. Dans l’exemple qui nous occupe, l’invariant nous permet de
spécifier qu’aucun paquet RST ne doit être envoyé pendant l’établissement d’une session
TCP. Les lignes 19 à 26, 27 à 35, et 36 à 45 spécifient chacune des transitions. Les deux
premières lignes indiquent les états entrants et sortants de la transition, alors que le
reste spécifie les conditions devant être respectées par le jeton pour que la transition
soit activée.
1.3.3 BSML
Le langage BSML (Behavioral Specification Monitoring Language) [24] tend lui aussi
vers une approche déclarative. Le formalisme utilisé pour représenter les comportements
Chapitre 1. Systèmes de détection d’intrusions 33
¯ ¯ ¯ ¯ ¯
p := e(x1 , . . . , xn )|cond ¯ !p ¯ p1 ; p2 ¯ p1 ||p2 ¯ p∗ ¯ p within [t1 , t2 ]
à identifier est celui des expressions régulières pour les événements (ERE). Les diffé-
rences entre les expressions régulières conventionnelles et les ERE sont la présence de
prédicats du premier ordre pour relier les événements entre eux et la possibilité de
spécifier des contraintes de temps-réel.
La syntaxe des ERE utilisées dans le langage BSML se trouve à la table 1.3. Un
patron d’événements est soit un événement primitif, paramétré par (x1 , . . . , xn ) et res-
pectant la condition cond, soit la non-occurrence d’un patron donné (!p), soit le patron
p1 suivi immédiatement du patron p2 (p1 ; p2 ), soit le patron p1 ou bien le patron p2
(p1 ||p2 ), soit la répétition un nombre indéterminé de fois du patron p (p∗ ), soit le pa-
tron p devant survenir dans l’intervalle de temps [t1 , t2 ] (p within [t1 , t2 ]). L’expression
p..q est utilisée pour abréger p; (!(p||q))∗ ; q. Aussi, on utilise les expressions p over t et
p within t pour abréger respectivement p within [t, ∞] et p within [0, t].
Une spécification BSML ne comporte cependant pas que des ERE. Une spécification
BSML est un ensemble de déclarations de variables ainsi que de couples p → act, où act
est une action à poser chaque fois que le patron p est reconnu. Cette action peut être
un simple archivage ou encore la manipulation (incrémentation, décrémentation) d’un
compteur. Plusieurs compteurs peuvent aussi être regroupés dans une table. La notion
de compteur en BSML est assez originale car elle permet de donner une valeur plus
élevée aux événements plus récents. Les conditions d’activation des actions associées
aux compteurs ne sont alors plus nécessairement définies sur le nombre d’événements
identifiés, mais sur le poids pondéré de ces événements.
L’exemple de la figure 1.16 est tiré de [24]. Cette signature sert à détecter une inon-
dation TCP SYN. Aux lignes 1 à 3, on définit un prédicat sur deux paquets TCP servant
à vérifier si ceux-ci appartiennent à la même session (dans des directions opposées, i.e.
avec les adresses IP et les ports inversés). À la ligne 5, on définit l’événement tcp_syn(p)
comme étant la réception d’un paquet (rx(p)) tel que le drapeau tcp_syn est activé,
mais pas le drapeau tcp_ack. À la ligne 6, on définit l’événement tcp_syn_ack(p,q)
comme étant l’envoi, en direction opposée, d’un paquet ayant les drapeaux tcp_syn et
tcp_ack activés. Finalement, à la ligne 7, on définit l’événement tcp_ack(q, r) comme
étant la réception, en direction opposée au syn_ack, d’un paquet ayant seulement le
drapeau tcp_ack d’activé. Aux lignes 9 à 13, on déclare la table neptune qui servira
Chapitre 1. Systèmes de détection d’intrusions 34
01.same_session(p, q) =
02. p.daddr=q.saddr && p.tcp_dport=q.tcp_sport &&
03. p.saddr=q.daddr && p.tcp_sport=q.tcp_dport
04.
05.event tcp_syn(p) = rx(p)| p.tcp_syn && !p.tcp_ack
06.event tcp_synack(p, q) = tx(q)| same_session(p,q) && q.tcp_syn && q.tcp_ack
07.event tcp_ack(q, r) = rx(r)| same_session(q,r) && !r.tcp_syn && r.tcp_ack
08.
09.Table neptune(
10. (unsigned int, unsigned short) /*key : (IP address,port)*/
11. 1000, 120, /*size 1000, window 120 seconds */
12. 4, 1, neptuneBegin, neptuneEnd /* thresholds and associated functions*/
13.)
14.
15.(tcp_syn(p1)..tcp_synack(p1,p2)) ;(( !tcp_ack(p2,p3))* over 60) ->
16. neptune.inc((p1.daddr, p1.tcp_dport))
Le langage BSML est un langage compilé, et les ERE sont transformées en machines
à états étendues. La différence entre une machine à état étendue et une machine à états
conventionnelle est la même qu’entre des réseaux de Petri conventionnels et des réseaux
de Petri colorés : les états de la machine contiennent des variables qui permettent de
relier entre eux les symboles lus par la machine. Bien entendu, les symboles de l’alphabet
(événements primitifs) sont donc eux aussi caractérisés par des variables.
En résumé, BSML est un langage simple et concis basé sur un formalisme cou-
ramment utilisé par les informaticiens : celui des expressions régulières. Ce langage est
compilé vers un autre formalisme qui le rend comparable à STATL et à IDIOT : ce-
lui des machines à état étendues. Il dispose de primitives permettant d’exprimer des
contraintes temps-réel posées sur les événements et de relier ceux-ci entre eux par la
valeur de leurs attributs. Parmi les trois langages étudiés dans cette section, il est celui
qui se rapproche le plus d’un langage déclaratif car les actions posées lors de la recon-
naissance de patrons n’influencent pas la reconnaissance ultérieure d’autres patrons.
Cependant, les actions posées par les compteurs indiquent qu’ils jouent eux aussi un
rôle important dans l’expressivité du langage et que les expressions régulières à elles
seules ne sont pas suffisantes pour exprimer les propriétés voulues. Il s’agit cependant
d’un bon compromis entre expressivité et complexité algorithmique.
Les systèmes experts en détection d’intrusions sont intéressants pour deux raisons :
leur capacité d’accumuler et d’inférer de la connaissance permet une détection plus pré-
Chapitre 1. Systèmes de détection d’intrusions 36
cise, et leur capacité à prendre des décisions permet de réagir aux attaques identifiées de
façon automatisée. Dans cette section, nous donnerons deux exemples de systèmes ex-
perts employés en détection d’intrusions : P-BEST et Lambda. P-BEST est un système
expert ayant été employé pendant plusieurs années dans le domaine de la recherche
et de la détection d’intrusions, alors que LAMBDA a d’abord été conçu comme un
langage ayant pour objectif de permettre aux experts du domaine de transcrire leurs
connaissances dans un langage simple.
1.4.1 P-BEST
P-BEST (Production-Based Expert System Toolset) [26, 27, 28] est en fait un en-
semble d’outils permettant de développer un système expert. Il ne s’agit pas d’un sys-
tèmes expert au sens propre du terme car, en théorie, un systèmes expert comporte
trois composantes principales : un moteur d’inférence, un moteur de décision et une
base de connaissance (qui elle même se divise en faits et en règles d’inférences). P-
BEST ne comporte que les deux premières composantes, qui d’un point de vue pratique
ne forment qu’une seule et même composante. Par abus de langage, nous dirons tout
de même que P-BEST est un système expert.
D’un point de vue algorithmique, P-BEST fonctionne en chaı̂nage avant. Ceci signifie
que, à chaque ajout d’un nouveau fait, l’ensemble des règles d’inférence est parcouru et
que toute l’information déductible de ce fait est ajoutée à la base de connaissance. La
base de faits contient donc à la fois les faits de base et les faits calculés. Cette philosophie
s’oppose au chaı̂nage arrière, qui consiste à emmagasiner seulement les faits de base, et à
inférer sur demande. En détection d’intrusions, où chaque événement signalé correspond
à un fait, et où les conclusions critiques doivent être tirées aussitôt que possible, le
chaı̂nage avant s’impose donc comme la voie à suivre. De plus, le chaı̂nage avant permet
à P-BEST de conserver un minimum d’information en ne conservant dans sa base de
faits que ceux qui ont été déduits et en éliminant ceux correspondant aux événements.
P-BEST fournit un langage compilé en C qui offre la possibilité de faire appel à des
routines programmées dans ce langage, conservant ainsi une certaine simplicité tout en
permettant un maximum de souplesse. Un exemple de règle d’inférence utilisée par P-
BEST se trouve à la figure 1.17. Aux ligne 1 à 3, on voit comment le langage fourni par
P-BEST permet de déclarer des types d’événements. Les paramètres #10 ;* indiquent
le niveau de priorité de la règle et le fait que la même règle peut être appliquée plusieurs
fois à un même événement. Les niveaux de priorité permettent de spécifier que certaines
règles doivent être exécutées avant d’autres. L’opérateur *, indiquant que la règle peut
être rappelée plusieurs fois, peut causer des boucles infinies à l’exécution du système
Chapitre 1. Systèmes de détection d’intrusions 37
s’il n’est pas utilisé correctement. À chaque fois que cet opérateur est utilisé, on doit
s’assurer que l’événement sera détruit par au moins une des règles. L’opérateur -, utilisé
à la ligne 9, permet justement d’effectuer cette tâche. Il permet de retirer des faits de la
base de connaissances ; en particulier, il permet aussi de retirer des événements. Chaque
événement doit normalement être soigneusement effacé de la base de connaissance afin
d’éviter un engorgement de la mémoire utilisée. On doit aussi s’assurer que la règle
supprimant l’événement ait la priorité la plus basse, afin d’éviter que celui-ci ne soit
supprimé avant que toutes les règles en ayant besoin aient pu l’utiliser. Finalement,
l’opérateur + doit être vu comme un quantificateur existentiel, l’opérateur ! permet de
faire un appel à une routine externe, et l’opérateur ==> sépare la prémisse des conclu-
sions. En conclusion, la règle de la figure 1.17 est donc activée par les événements de
type login ayant un code de retour égal à la constante BAD_PASSWORD. Lorsqu’un tel
événement survient, celui-ci est détruit (ligne 9) après avoir créé un nouvel événement
de type bad_login (ligne 8), et un message, indiquant qu’une tentative de connexion a
échoué (ligne 10), est affiché à l’aide de la fonction externe printf.
D’autres opérateurs sont aussi disponibles dans le langage de P-BEST. Par exemple,
il existe des opérateurs permettant d’activer ou de désactiver dynamiquement des règles
d’inférence. P-BEST peut donc prendre des décisions à propos de son propre fonction-
nement. Le principal intérêt de cette fonctionnalité est de permettre une forme d’opti-
misation en désactivant des règles jugées temporairement non pertinentes. Il est aussi
possible, afin de simplifier l’écriture de certaines règles, de modifier les champs de cer-
tains faits. Ceci permet d’émuler la notion de variable, facilitant ainsi, par exemple, la
définition de compteurs. Finalement, le langage fournit des opérateurs permettant de
marquer ou de démarquer des événements, permettant ainsi à certaines règles de laisser
des traces qui peuvent être utilisées par d’autres règles.
¯ ¯ ¯ ¯ ¯ ¯
e := a ¯ 0 ¯ e1 ; e2 ¯ e1 |e2 ¯ e ¯ e1 ?e2 ¯ e1 &e2
un langage compilé qui permet une interopérabilité simple et efficace avec d’autres lan-
gages, et par conséquent d’autres systèmes. D’un point de vue abstrait, les fonctionna-
lités de base d’un système expert, soient l’accumulation et l’inférence de connaissance,
de même que l’aide à la prise de décision, sont tout à fait souhaitables en détection
d’intrusions et P-BEST les implémente très bien. Cependant, le langage de P-BEST
n’est peut-être pas des plus appropriés pour la gestion de flots d’événements. En effet,
l’association fait-événement respecte bien le paradigme des systèmes experts, mais cer-
tains aspects du langage et de l’implémentation dépassent le cadre des systèmes experts
et demandent une connaissance des algorithmes utilisés en arrière-plan. Par exemple,
le chaı̂nage avant force l’utilisateur à s’assurer de donner les bons niveaux priorité aux
différentes règles. De plus, la suppression des événements qu’il n’est plus nécessaire de
conserver en mémoire doit être faite explicitement, ce qui complique (inutilement) la
rédaction de règles. Finalement, les notions de règles répétables, de marquage d’événe-
ments, et d’activation/désactivation de règles donnent à P-BEST des possibilités pra-
tiquement algorithmiques qui dépassent le cadre strict des systèmes experts. P-BEST
nous convainc donc que les caractéristiques principales d’un système expert sont sou-
haitables en détection d’intrusions, mais l’implémentation suggère que le paradigme pur
des systèmes experts semble n’être pas parfaitement adapté à la tâche.
1.4.2 LAMBDA
LAMBDA est un langage abstrait permettant de décrire des scénarios d’attaque sans
se soucier des détails de détection. La description qui en est donnée dans [29] est détachée
des détails d’implémentation, et repose sur l’hypothèse que d’autres outils d’audit (dont
des systèmes de détection d’intrusions) fournissent un flux d’événements bas niveau à
analyser. LAMBDA n’est pas décrit par ses auteurs comme un langage de spécification
de système expert, mais comporte tout de même les trois caractéristiques que nous
avons identifiées comme étant désirables d’un système expert, soient l’accumulation
et l’inférence de connaissance, de même que la possibilité de prendre des décisions et
de réagir automatiquement. De plus, comme LAMBDA est spécialement conçu pour la
description d’attaques, il comporte des primitives spécialement reliées à ce domaine. Par
exemple, il permet de définir un scénario d’attaque de deux points de vue différents :
Chapitre 1. Systèmes de détection d’intrusions 39
celui de l’attaquant et celui du système de détection. Bien que les deux points de vue
soient en général très semblables, il arrive cependant que certaines actions effectuées
par l’attaquant puissent ne pas être détectables par le système attaqué. Par exemple, il
peut s’agir de certains calculs effectués à l’interne.
Le langage LAMBDA offre une solution hybride pour exprimer les scénarios d’at-
taque, et repose sur trois langages différents, dépendamment du point de vue auquel on
se place. Pour représenter la connaissance acquise, on utilise la logique du deuxième
ordre. On utilise des prédicats du deuxième ordre pour modéliser, par exemple, la
connaissance de l’attaquant : le prédicat knows(A, φ) exprime le fait que l’attaquant
A acquiert la connaissance φ. Pour représenter les contraintes d’ordonnancement de-
vant être respectées par les différentes étapes d’un scénario d’attaque, on utilise le
calcul d’événements [30], dont la syntaxe est donnée à la table 1.4. Un événement peut
être soit un événement primitif (a), soit l’événement nul (0), soit une séquence d’évé-
nements (e1 ; e2 ), soit l’exécution parallèle de deux événements (e1 |e2 ), soit l’absence
d’événements (e), soit le choix non-déterministe d’événements (e1 ?e2 ), soit l’exécution
synchronisée d’événements (e1 &e2 ). L’action nulle, combinée au choix non-déterministe,
def
permet de représenter un événement facultatif [e] = e ? 0. Il est à noter que l’événe-
ment primitif a est défini sur un intervalle de temps [t1 , t2 ], et non comme étant un
événement ponctuel. Pour les événements ponctuels survenant à l’instant t, l’intervalle
est [t, t]. Finalement, pour exprimer les contraintes autres que l’ordonnancement de-
vant être respectées par les différents événements du scénario, on utilise simplement la
logique du premier ordre.
Le lien avec la base de connaissances se fait via les clauses pre et post. La clause pre
exprime les conditions devant être satisfaites par le système avant l’exécution du scénario
afin que celui-ci puisse réussir. La clause post exprime l’état du système une fois que
l’exécution du scénario a réussi. Les clauses scenario et detection donnent les détails
de l’attaque du point de vue de l’attaquant et de l’attaqué. La clause verification,
quand à elle, indique comment valider l’effet de l’attaque. Les auteurs parlent aussi
d’ajouter une clause reaction, permettant d’indiquer comment réagir à l’attaque pour
se protéger. Finalement, les clauses where donnent les relations entre les diverses étapes
des scénarios.
Nous n’avons pas trouvé, dans la littérature, d’exemple de scénario d’attaque ex-
primé avec LAMBDA utilisant le calcul des événements. Tous les exemples sur lesquels
nous avons pu mettre la main n’utilisaient, dans la partie scenario, que des actions
simples. C’est le cas, entre autres, du scénario d’accès illégal à un fichier, présenté à la
figure 1.18. Ce scénario se déroule en huit étapes. Premièrement (1), l’attaquant crée
un fichier auquel il aura alors les droits d’accès. Ensuite (2), l’attaquant ayant un accès
physique à une imprimante bloque celle-ci, par exemple en retirant le bac de papier. Il
lance alors la commande d’impression du fichier créé (3), et comme il a les droits d’accès
Chapitre 1. Systèmes de détection d’intrusions 41
pour ce fichier, la commande est placée dans la file d’impression. L’impression ne peut
avoir lieu immédiatement, car l’imprimante est bloquée. Les prochaines étapes sont la
suppression du fichier pour lequel une commande d’impression a été lancée (4), et la
création d’un lien logique vers le fichier cible ayant le même nom que le fichier supprimé
(5). Il ne reste ensuite qu’à débloquer l’imprimante (6), pour que l’impression du fichier
puisse avoir lieu (7), donnant ainsi accès à l’attaquant au fichier auquel il n’avait pas
droit (8).
En résumé, LAMBDA est un langage qui offre les mêmes avantages qu’un para-
digme basé sur un système expert : soient l’accumulation et l’inférence d’information,
de même qu’un support à la décision. De plus, l’approche hybride utilisant le calcul
d’événements permet d’exprimer des scénarios d’attaque selon une syntaxe claire et
concise qui semble au moins aussi satisfaisante sinon plus que celles employées avec
les systèmes de transition. Des aspects intéressants de LAMBDA sont la séparation du
point de vue de l’attaquant et du point de vue de l’attaqué, de même que les notions de
vérification et de réaction. Aussi, la description logique des effets et des prémisses d’une
attaque permet de faire un calcul de scénario d’attaque afin d’élaborer de nouveaux
scénarios.
Les trois derniers travaux que nous présentons, LogWeaver, Monid, et Chronicles,
sont ceux qui se rapprochent le plus de ceux que nous avons effectués de par le fait que
les langages de spécification utilisés ont été développés à partir de logiques temporelles.
LogWeaver utilise une logique temporelle future du premier ordre, alors que Monid
utilise une logique temporelle passée et future du premier ordre avec points fixes nommée
Eagle et que Chronicles se base sur une logique réifiée. Dans les trois cas, les algorithmes
utilisés ont été conçus de façon à pouvoir travailler en ligne (online). Grossièrement,
un algorithme est dit en ligne s’il peut traiter séquentiellement sur un fichier d’audit
à partir du début sans avoir à mémoriser tout le fichier. Un exemple d’algorithme qui
n’est pas en ligne est celui décrit dans [31], où le traitement s’effectue à partir de la fin
du fichier. Un tel algorithme est appelé, dans la littérature anglaise, offline.
Chapitre 1. Systèmes de détection d’intrusions 42
F ::= A | ¬F | F1 ∧ F2 | F1 ∨ F2 | ¦F
1.5.1 LogWeaver
L’article sur LogWeaver [32] est divisé en deux parties. Premièrement, les auteurs
montrent comment on peut utiliser une logique temporelle classique pour effectuer de
la détection d’intrusions sur des fichiers d’audits. Ils montrent ensuite les faiblesses de
cette logique et en proposent une autre moins orthodoxe, mais qu’ils considèrent comme
plus appropriée. Nous focuserons ici sur la première logique, car elle est plus proche de
celles que nous avons déjà vues et elle nous permet de voir comment on peut effectuer
de la vérification sur une logique du premier ordre.
Le modèle sur lequel travaille LogWeaver est une séquence finie ou infinie d’enre-
gistrements, appelé indistinctement log, ou trace. Un enregistrement est défini comme
étant une fonction d’un ensemble d’étiquettes vers un ensemble de valeurs, lesquelles
sont considérées comme étant des chaı̂nes de caractères.
La syntaxe des formules utilisées dans la première logique abordée est présentée à la
table 1.6. À première vue, il s’agit d’un sous-ensemble de LTL, que nous étudierons plus
en détails à la section 2.1.1. Les opérateurs U (jusqu’à ce que) et ° (immédiatement
après) ont été omis car, selon les auteurs, ces derniers ne se révèlent pas d’une grande
utilité pour la détection d’intrusions. Le seul opérateur temporel ayant été conservé est
donc l’opérateur ¦, signifiant nécessairement. Il est à noter que l’opérateur ¦ utilisé ici
exprime un futur strict. La principale différence entre cette logique et LTL se trouve au
niveau du type de la relation de satisfaction et de la sémantique attribuée aux formules
atomiques A, aussi appelées patrons d’événements. Un exemple de patron d’événements
est {id=X, action="creat", object="/usr/spool/mail/root$"}. Ce patron filtre
tous les événements dont la valeur du champ id est égale à celle de la variable X, et
dont les valeurs des champs action et object sont respectivement égales à "creat" et
"/usr/spool/mail/root$".
Étant donné une trace σ et une formule F , l’ensemble des éléments satisfaisant F
est alors défini non-pas seulement comme étant un sous-ensemble d’enregistrements de
σ, mais comme un ensemble de couples (s, ρ), où s est un enregistrement de σ et ρ
est un environnement. Un environnement est une fonction attribuant des valeurs aux
Chapitre 1. Systèmes de détection d’intrusions 43
variables se trouvant dans les patrons d’événements d’une formule. C’est exactement
cette notion d’environnement qui justifie l’emploi d’une logique du premier ordre. Ce
sont les environnements qui permettent de relier les divers enregistrements entre eux en
unifiant les valeurs de certains de leurs champs.
L’algorithme de vérification utilisé pour cette logique est semblable à celui utilisé
pour RUSSEL (section 1.2.1). On travaille cependant avec deux ensembles plutôt que
trois, l’ensemble Cmp n’ayant aucun sens ici. Pour vérifier une formule F , on initialise
donc l’ensemble Cur à {F } et l’ensemble N xt à ∅. Ensuite, pour chaque enregistrement,
on traite chacune des formules de l’ensemble Cur. Ce traitement peut soit amener à
ajouter d’autres formules dans l’ensemble Cur (dans le cas de formules de la forme
F1 ∧ F2 , F1 ∨ F2 , ou ¬F ), soit à ajouter des formules dans l’ensemble N xt (dans le
cas des formules de la forme ¦F ). Il est à noter que dans le dernier cas, on utilise
l’équivalence ¦F ⇔ °F ∨ ° ¦ F , signifiant que formule F sera satisfaite dans le futur
si et seulement si elle sera satisfaite à l’état suivant ou si, à partir de l’état suivant, elle
sera satisfaite dans le futur. En plus de manipuler ces ensembles, il faut aussi ajouter
les structures de données nécessaires au chaı̂nage des formules et à la manipulation des
environnements. Le lecteur intéressé aux détails de cet algorithme est référé à [32].
Pour résoudre ces problèmes, les auteurs suggèrent d’utiliser une autre logique, moins
classique, inspirée de ETL [33]. Nous ne nous étendrons pas ici sur les détails de ETL,
mais nous nous contenterons de dire que cette dernière a la particularité d’offrir la
possibilité d’inclure des automates dans la définition des formules. Une partie de la
sémantique des formules est donc définie par la notion d’acceptation d’une chaı̂ne par
un automate. Cette solution hybride présente des avantages au niveau de l’expressi-
vité, mais les spécifications obtenues, comme le remarquent les auteurs, commencent à
ressembler étrangement à celles de STATL.
1.5.2 Monid
La syntaxe des spécifications Eagle se trouve à la table 1.7. Il s’agit d’une syntaxe
plutôt concrète, puisqu’on y spécifie plus que la syntaxe des formules. Une spécification
S consiste en une partie de déclarations D et une partie où on définit des observateurs O.
D est constitué de zéro ou plus définitions de règles R, et O de zéro ou plus moniteurs M .
Les règles et les moniteurs sont nommés N . Le nommage des règles permet la définition
de règles récursives. Le domaine lexical T représente les types, qui sont utilisés lors de
la définition des règles. Finalement, xi représente une variable, et exp est une expression
qui s’évalue soit à vrai soit à faux à chacun des états de la trace.
J
Les opérateurs °, , désignent respectivement l’événement suivant et l’événement
précédent, alors que les opérateurs de points fixes min et max permettent de définir des
Chapitre 1. Systèmes de détection d’intrusions 45
S ::= DO
D ::= R∗
O ::= M∗
R ::= {max|min} N (T1 x1 , . . . , Tn xn ) = F
M ::= mon N = F
T ::= Form | primitive type
F ::= exp | true | false | ¬F | F1 ∧ F2 | F1 ∨ F2 | F1 → F2
J
| °F | F | F1 · F2 | N (F1 , . . . , Fn ) | xi
la fin de la trace, on calcule une fonction booléenne value(F ) qui s’évalue à vrai si et
seulement si le dernier état de σ satisfait F . Donc, pour une trace σ de longueur n et une
formule F , on a σ, 1 |= F ssi value(eval(. . . eval(eval(F, σ(1)), σ(2)) . . . , σ(n))) = vrai.
On remarque ici une différence importante entre les hypothèses faites sur le modèle
utilisé par LogWeaver et celui de Eagle : les traces vérifiées par Eagle sont de longueur
finie, alors que celles sur lesquelles travaille LogWeaver sont définies comme étant finies
ou infinies. Il est à noter que cette différence reste tout de même très mince car, d’une
part, les résultats théoriques sur l’algorithme employé par LogWeaver demandent une
trace finie, et d’autre part, les auteurs de [35] citent une méthode, appelée specifying-
bad prefixes [36], permettant d’exprimer la formule à vérifier de telle sorte que celle-ci
devient tôt ou tard vraie ou fausse, peu importe le reste de la trace. L’intérêt de cette
technique réside dans le fait qu’elle évite d’avoir à vérifier la satisfiabilité éventuelle
d’une formule, problème qui est indécidable dans le cas de Eagle dû à l’utilisation de
prédicats du premier ordre.
En résumé, les travaux effectués dans le cadre du développement de Eagle sont plus
près de la pratique que ceux effectués pour LogWeaver. Les propriétés intéressantes pour
la détection d’intrusions telles que le comptage ou les propriétés de parité s’expriment
beaucoup plus naturellement dans Eagle que dans le sous-ensemble de LTL considéré
par les auteurs de LogWeaver ou encore la seconde logique proposée, inspirée de ETL.
Les algorithmes de vérification utilisés par Eagle sont simples, mais semblent mieux
adaptés au cas où la trace d’événements considérée est de longueur finie. Finalement,
on trouve dans [35] toute une série d’exemples de scénarios d’attaque allant des plus
classiques à d’autres plus originaux pouvant s’exprimer dans Eagle.
1.5.3 Chronicles
Les deux systèmes que nous venons de présenter ont été construits sur des para-
digmes provenant de la théorie de la vérification formelle de modèles. Ils utilisent des
logiques qui ont été historiquement développées dans l’intention de vérifier automati-
quement certaines propriétés de programmes. La vérification par évaluation de modèle
Model Checking en informatique peut être utile à plusieurs phases du développement,
tant à la conception (vérification d’algorithmes), à la programmation (vérification de
code), qu’à la phase de tests (validation de logiciels). Le système Eagle, en particulier, a
d’abord été développé pour ce type d’usage. L’avantage d’utiliser une logique commune
dans un tel cadre d’utilisation est qu’à chaque phase du processus de développement,
on peut vérifier à nouveau les mêmes propriétés avec un minimum d’ajustement. La
vérification par évaluation de modèle est très bien adapté aux problèmes booléens :
Chapitre 1. Systèmes de détection d’intrusions 47
est-ce que, oui ou non, telle propriété est satisfaite ?. Les problèmes du genre : dans
quelle mesure cette propriété est-elle satisfaite ? ou encore quelles sont toutes les façons
dont cette propriété est violée ? présentent peu d’intérêt dans ce domaine et c’est sans
doute pourquoi les tentatives d’y appliquer les logiques y ayant été développées peuvent
parfois sembler artificielles et tordues.
Chronicles, introduit dans [37], est un langage basé sur les logiques réifiées permet-
tant de reconnaı̂tre des chroniques dans un flot d’événements. Il vient avec CRS (Chro-
nicles Recognition System) un vérificateur qui peut fonctionner en ligne. Informellement,
une chronique est un ensemble d’événements reliés entre eux par des contraintes tempo-
relles. Dans la littérature, les chroniques sont reliées au calcul des événements présenté
plus haut.
La représentation du temps dans Chronicles est discrète et il est donc vu comme une
suite ordonnée d’instants dont la résolution est assez fine pour les besoins de l’analyse.
Deux événements différents peuvent survenir au même instant, mais lorsque deux ou
plusieurs événements identiques surviennent au même instant (selon la résolution choi-
sie), ceux-ci sont fusionnés en un seul événement. En détection d’intrusions, où plusieurs
événements identiques peuvent survenir en peu de temps, le choix de la résolution doit
donc être fait avec précautions.
L’environnement est décrit par les attributs du domaine. Un attribut du domaine est
un tuple P (a1 , . . . , an ) : v, où P est le nom de l’attribut, a1 , . . . , an ses arguments, et v
sa valeur. Certains attributs peuvent ne pas avoir de valeur. Ces derniers sont appelés
messages. Finalement, les événements correspondent à des changements de valeurs des
attributs du domaine.
Chapitre 1. Systèmes de détection d’intrusions 48
hold(P : v; (t1 ; t2 ))
event(P : (v1 ; v2 ); t)
event(P ; t)
noevent(P ; (t1 ; t2 ))
occurs((n1 ; n2 ); P ; (t1 ; t2 ))
1. chronicle exemple1 {
2. event(e1,t1) ;
3. event(e2,t2) ;
4. event(e3,t3) ;
5.
6. t1<t2<t3
7. t3-t2 <= 4
8. }
qu’un événement satisfaisant les contraintes exprimées par un modèle de chronique sur-
vient, celui-ci est ajouté à l’instance de chronique en cours après duplication de cette
dernière. La duplication sert à s’assurer de reconnaı̂tre toutes les instances possible.
Certaines instances en cours de reconnaissance peuvent être éliminées lorsque des as-
sertions sont violées ou que les contraintes temporelles ne peuvent plus être satisfaites.
Il est important, lors de la spécification de chroniques, de s’assurer de spécifier de telles
assertions ou contraintes temporelles. Autrement, et particulièrement dans un mode de
fonctionnement en ligne, certaines instances peuvent rester indéfiniment en mémoire et
même, éventuellement, faire exploser celle-ci.
Chronicles n’a pas été conçu spécialement en vue de faire de la détection d’intru-
sions. À ses débuts, il a été appliqué à l’analyse de journaux d’alarmes d’équipements
de télécommunications. Il a par la suite été appliqué à d’autres domaines tels que l’ana-
lyse de circulation routière et on lui a même trouvé quelques applications en médecine.
Dans [38], on a finalement suggéré une façon de l’utiliser en détection d’intrusions.
Bien qu’il soit possible d’utiliser Chronicles pour représenter des scénarios d’attaque,
l’utilisation proposée par les auteurs tend vers d’autres objectifs. Principalement, ils
proposent d’utiliser Chronicles comme outil d’analyse des journaux d’alarmes de sys-
tèmes de détection d’intrusions afin i) de permettre une détection plus précise, ii) de
réduire le nombre d’alarmes et iii) d’améliorer la sémantique des alarmes.
L’utilisation de Chronicles peut permettre une détection plus précise. Plus préci-
sément, elle peut aider à diminuer le nombre de faux-positifs en invalidant certaines
alarmes. L’invalidation d’alarmes avec Chronicles peut se faire en spécifiant certains
contextes dans lesquels certains comportement ne doivent pas être considérés comme
dangereux. Par exemple, l’initialisation d’une vidéo-conférence entre plusieurs utilisa-
teurs peut sous certains aspects ressembler à un balayage de ports, mais ne doit tout
de même pas déclencher d’alarme. L’utilisation de Chronicles peut réduire le nombre
d’alarmes, principalement en utilisant le prédicat occurs. Certaines attaques, telles que
la propagation de vers informatiques, se caractérisent par la répétition à outrance de
comportements identiques, et peuvent déclencher un nombre abrutissant et inutile-
ment élevé d’alarmes. Chronicles peut fusionner ces différents événements en un seul.
Chapitre 1. Systèmes de détection d’intrusions 50
Chronicles peut améliorer la sémantique des alarmes en combinant les différents symp-
tômes d’une même attaque. Mieux encore, certaines attaques ne doivent être considérées
comme réussies que si un ensemble bien déterminé d’actions ont été effectuées. Certains
systèmes de détection d’intrusions lanceront des alarmes à chacune de ces actions, don-
nant ainsi l’impression que de nombreuses attaques ont eu lieu alors qu’il s’agit en fait
d’une seule. L’utilisation de Chronicles permet de ne déclencher qu’une seule alarme
rendant ainsi mieux compte du fait qu’une seule attaque a eu lieu.
Les auteurs de [38], en plus de donner plusieurs exemples où Chronicles peut s’avérer
utile dans l’analyse de journaux d’alarme, montrent comment ce langage peut être
utilisé de paire avec M2D2 [10] pour corréler les alarmes avec le contexte. M2D2 est un
modèle formel incluant celui de NetSTAT [20]. Il permet de représenter non-seulement
la topologie physique du réseau, mais aussi toute sa configuration, les logiciels installés
sur chacune des machines, les vulnérabilités de ceux-ci de même que leurs effets, les
logiciels de sécurité installés sur chacune des composantes, les attaques détectables par
chacun des systèmes de détection d’intrusions, et bien encore. La façon dont les alarmes
sont représentées dans M2D2 sont de plus compatibles avec IDWG [39], un format
d’échange d’alarmes standardisé mis au point à l’IETF. L’utilisation d’un tel modèle
permet, en plus de corréler différentes alarmes entre elles, de tenir compte dans l’analyse
des caractéristiques du système attaqué et même du système de détection d’intrusions
ayant signalé l’attaque.
En résumé, Chronicles est un langage de surveillance basé sur une logique réifiée
dont la sémantique semble mieux adaptée à la détection d’intrusions que les logiques
traditionnellement utilisées en Model Checking. L’ordonnancement des différents évé-
nements est exprimé par un ensemble de contraintes sur les instants leur étant associés
plutôt que par des opérateurs dédiés. Les opérateurs spécifiques à Chronicles mettent
plutôt le focus sur les différents changements pouvant survenir pendant le processus
de reconnaissance d’une chronique. Un des ces opérateurs permet même de compter
de façon très naturelle le nombre d’événements semblables. Chronicles a de plus été
utilisé dans de nombreux domaines d’application à première vue assez différents les uns
des autres. Un des dangers reliés à l’utilisation de Chronicles est l’explosion de l’utili-
sation de la mémoire lorsque les chroniques ne sont pas spécifiées consciencieusement.
Finalement, des travaux ayant été effectués tendent à démontrer que Chronicles peut
s’avérer utile en corrélation d’alarmes. La notion de corrélation d’alarmes employée par
les auteurs de [38] est cependant légèrement différente de celle utilisée par les auteurs
de [29]. Les premiers utilisent le mot corrélation dans un sens plutôt général, désignant
par exemples différentes alarmes symptomatiques d’un même phénomène, alors que les
deuxièmes parlent plutôt d’une corrélation logique, voire même causale. Le vérificateur
de chroniques CRS permet de générer des événements en cours d’exécution. Cette fonc-
Chapitre 1. Systèmes de détection d’intrusions 51
tionnalité, jointe à l’utilisation du modèle M2D2, permet de bien mettre les attaques
signalées en contexte.
1.6 Conclusion
La revue que nous avons faite mettait cependant l’accent sur les langages et les
paradigmes utilisés pour exprimer les signatures dans un contexte de détection. D’autres
langages, comme ADeLe [2], NASL [40] et CASL [41] mettent plutôt l’accent sur le côté
attaquant. ADeLe a été développé en parallèle avec LAMBDA dans le but de développer
une base données d’attaques. Une des préoccupations des auteurs était de concevoir un
langage assez général pour pouvoir représenter, comme LAMBDA, les attaques à la fois
du point de vue de l’attaquant et de l’attaqué, de même que de permettre une corrélation
logique entre les différentes attaques et de spécifier comment réagir à ces attaques. NASL
est un langage de scripts développé pour l’identificateur de failles de sécurité Nessus. Il
fournit des primitives permettant d’envoyer des paquets forgés à la main sur le réseau
et de recevoir d’autres paquets en vue d’effectuer des tests bien précis. CASL a été
développé avec des objectifs similaires et offre des fonctionnalités semblables, mais a
été conçu avec l’objectif de réaliser des attaques plutôt que de simplement tester les
failles.
Chapitre 1. Systèmes de détection d’intrusions 52
1) scénarios à plusieurs événements Les différents exemples que nous avons étudié
nous convainquent que le fait de pouvoir exprimer des scénarios comportant plu-
sieurs événements est nécessaire pour effectuer une détection d’intrusions précise
et complète.
2) non-occurrence d’événements Il arrive parfois que le fait de ne pas observer un
événement soit révélateur d’information.
3) contraintes temps-réel On doit être capable de spécifier un délai entre les diffé-
rents événements d’un scénario.
4) comptage Dans certains cas, c’est la répétition d’un événement ou d’un scénario
donné qui est porteur d’information.
5) acquisition de connaissance Le fait de pouvoir extraire et conserver de façon dy-
namique de l’information des événements observés aide à réduire les faux positifs.
6) propriétés de parité Certaines conditions ne sont valides qu’entre deux événe-
ments donnés.
7) gestion des attributs absents ou multiples Le langage doit prendre automati-
quement en compte le fait que certains attributs peuvent être optionnels pour
certains événements. Aussi, il est possible qu’un attribut ait une liste de valeurs.
8) approche déclarative Un paradigme déclaratif permet, en plus de faciliter la
maintenance de la base de signatures, d’effectuer un calcul de corrélation entre
les différents scénarios. Ce calcul n’est cependant possible que si le langage est
purement déclaratif, au sens de la définition 1.1.
9) fonctionnement en ligne Il doit être possible d’élaborer un algorithme de vérifi-
cation capable de traiter les événements dans l’ordre où ils surviennent, et de les
effacer automatiquement de la mémoire dès qu’ils sont traités.
10) utilisation bornée de la mémoire Il doit être possible d’élaborer un algorithme
de vérification capable de fonctionner avec une quantité fixe de mémoire qui n’aug-
mente pas avec le nombre d’événements.
nom 1 2 3 4 5 6 7 8 9 10
Snort N N N N O O N O O O
NeVO O N O N O N N O O O
ASAX O N O O O O O N N N
Bro O N O O O O N N O O
STAT O O O O O O N N O N
IDIOT O N O O O O N N O N
BSML O O O O N N N N O O
P-BEST O N N N O O N N O N
LAMBDA O O O N O O N O - -
LogWeaver O O O N N N N O O N
Monid O O O O N N N O O N
Chronicles O O O O N N N O O N
Une approche dont nous n’avons pas parlé est celle de [42], basée sur un paradigme
de grammaires. Les auteurs de cet article proposent un formalisme pour représenter les
attaques comprenant entre autres des notions de filtre, de temps-réel, et de séquence-
ment. Ce formalisme vient avec un algorithme de vérification à base de tableaux qui est
prouvé complet et cohérent. On trouve aussi dans cet article une discussion intéressante
au sujet des problèmes pratiques découlant de la complétude des algorithmes de vérifi-
cation de signatures dans le cas des langages déclaratifs. Principalement, on soulève le
fait que la complétude, bien qu’intéressante d’un point de vue théorique, engendre sou-
vent des problèmes de complexité algorithmique et de surcharge d’alarmes. La méthode
proposée par les auteurs permet de régler l’algorithme de vérification en définissant une
relation d’équivalence sur les alarmes de façon à réduire le nombre d’alarmes tout en
conservant celles qui sont considérées comme étant les plus significatives. Le système
ARMD [43], mis au point pour le langage MuSigs, basé sur l’algèbre relationnelle, offre
lui aussi la possibilité de régler l’algorithme de vérification selon les besoins particuliers
de l’utilisateur.
Plusieurs approches issues de techniques d’intelligence artificielle ont aussi été propo-
sées pour attaquer le problème de la détection d’intrusions. Dans [44], on propose une
méthode utilisant les algorithmes génétiques pour apprendre, à partir d’un ensemble
d’entraı̂nement, à reconnaı̂tre les patrons d’attaque. Cette méthode, bien que compor-
tant une phase d’apprentissage, présente cependant deux différences importantes avec
les méthodes basées sur une détection d’anomalies (méthodes statistiques). Première-
ment, l’ensemble d’entraı̂nement fourni à l’algorithme doit être étiqueté. Alors que les
méthodes basées sur une détection d’anomalies ne prennent en entrée que des fichiers
Chapitre 1. Systèmes de détection d’intrusions 54
d’audit, la méthode proposée a besoin, en plus, de savoir où se trouvent les attaques dans
ce fichier. Deuxièmement, le résultat de la phase d’apprentissage ne donne pas un profil
qui doit être considéré comme normal, mais un ensemble de signatures qui peut par la
suite être utilisé par un algorithme de reconnaissance de scénarios. Dans [45] et [46], on
propose d’autres méthodes basées sur la programmation génétique. La programmation
génétique peut être vue comme un cas particulier des algorithmes génétiques où les
individus participant aux croisements sont des programmes, et les gênes sont des bouts
de code. Encore une fois, ces approches dépendent de la disponibilité de fichiers d’au-
dits étiquetés pour la phase d’apprentissage. Cette contrainte n’est pas des moindres
car, depuis les fichiers d’évaluation créés par le laboratoire Lincoln du MIT en 1998 et
1999 [47, 48], peu d’autres fichiers d’audit ou de traces de trafic ont été fournis à la
communauté pour permettre de tels travaux.
Chapitre 2
Logiques temporelles
Même au sein d’un domaine comme l’informatique, la définition semble différente dé-
pendamment de la branche qui nous intéresse. Les deux domaines où l’intérêt semble le
plus marqué pour les logiques sont l’intelligence artificielle et la vérification formelle. En
vérification formelle, une logique est perçue comme un langage permettant d’exprimer
certaines propriétés d’un programme ou d’un autre objet abstrait. En intelligence arti-
ficielle, où on s’intéresse davantage aux aspects déductifs et inductifs, la multiplication
des logiques semble un problème moins important et lorsque l’on lit, par exemple, Mc-
Dermott [49], Allen [50], ou Bacchus [51], les formalismes utilisés sont beaucoup moins
uniformes que celles que l’on rencontre en lisant Pnueli [52], Kozen [53], Wolper [33],
Emerson et Halpern [54, 55], ou Henzinger [56], qui eux s’intéressent à la vérification
formelle.
La présentation des logiques temporelles que nous ferons dans ce chapitre sera donc
faite d’abord en fonction des modèles utilisés, puis en fonction des différentes syntaxes
et sémantiques. Les principaux modèles utilisés pour les logiques temporelles sont pré-
sentés à la table 2.1. La première ligne, ainsi que la quatrième, sont les modèles statiques
de base, c’est-à-dire intemporels. La logique propositionnelle, telle quelle, ne comprend
Chapitre 2. Logiques temporelles 57
aucune notion de temps. On suppose qu’il existe un nombre fini de constantes pro-
positionnelles, regroupées dans un ensemble P qui sont toutes soit vraies soit fausses.
L’attribution du caractère de vérité à ces constantes est ce qu’on appelle une interpré-
tation, ou encore une valuation, et l’ensemble de ces interprétations constitue le modèle
de la logique propositionnelle. Dans le cas de la logique du premier ordre, l’ensemble
primitif P n’est plus un ensemble de constantes propositionnelles, mais un ensemble de
prédicats, c’est-à-dire un ensemble d’objets de la forme p(x1 , . . . , xn ). Une interpréta-
tion sera alors vue comme un ensemble de façons d’attribuer des valeurs aux variables
x1 , . . . , xn , lesquelles valeurs seront choisies dans un ensemble V .
qui suivent, nous traitons d’abord les logiques temporelles, puis les logiques temporisées.
Toutes les logiques temporelles que nous présentons dans cette section sont des
logiques dites linéaires, qui se définissent par opposition aux logiques dites arbores-
centes, telles que CTL, CTL∗ [58], ou le µ-calcul modal [53]. La différence entre une
logique linéaire et une logique arborescente se situe d’abord au niveau du modèle. Étant
donné l’ensemble des exécutions possibles d’un programme, une logique linéaire permet
d’exprimer les propriétés de chacune de ces exécutions, prises séparément, alors qu’une
logique arborescente permet de tenir compte des liens entre ces diverses exécutions. Prin-
cipalement, une logique arborescente permet d’exprimer la propriété à partir d’un cer-
tain moment, il sera possible d’aller vers un chemin d’exécution ayant la propriété. . . .
La pertinence de telles propriétés a fait l’objet d’une guerre de clochers pendant un
certain temps [55], et il semblerait que le succès technologique de l’outil de vérification
SPIN [59] ait fait pencher la balance du côté des logiques linéaires. Peu importe, la
raison pour laquelle les logiques arborescentes ne sont pas présentées ici est qu’étant
donné que, dans le contexte de notre travail, nous ne considérons qu’un seul chemin
d’exécution (celui en cours), la notion d’arborescence ne présente aucun intérêt pour
nous.
φ ::= p | ¬φ | φ1 ∨ φ2 | °φ | φ1 U φ2
De toutes les logiques présentées dans ce chapitre, la logique temporelle linéaire est
celle qui historiquement est apparue en premier [60, 52]. La majorité des autres logiques
que nous présentons dans ce chapitre sont des variantes de celles-ci ou s’y réfèrent. Le
modèle sur lequel LTL est définie, comme toutes les autres logiques présentées dans cette
section, est celui de la deuxième ligne de la table 2.1, c’est-à-dire une séquence infinie
d’états, aussi appelée trace, que l’on note σ. Les états de la trace sont des ensembles de
constantes propositionnelles. Par σ(i), on dénote le ieme état de σ.
Chapitre 2. Logiques temporelles 59
La syntaxe de LTL est donnée à la table 2.2. Les formules de LTL sont les mêmes
que celles du calcul propositionnel, auxquelles on ajoute les opérateurs temporels °
et U. Il s’agit d’opérateurs temporels futurs, au sens où ils permettent d’exprimer
des propriétés concernant les états à venir. L’opérateur ° est l’opérateur prochain
permettant d’exprimer une propriété du prochain état, et l’opérateur U est l’opérateur
jusqu’à ce que, permettant d’exprimer qu’une propriété qui doit être respectée jusqu’à
ce qu’une autre propriété le soit.
def
¦φ = vrai U φ
def
¤φ = ¬ ¦ ¬φ
def
φ1 U φ2 = (φ1 U φ2 ) ∨ ¤φ1
Outre les opérateurs ∧, →, et ↔, que l’on peut définir comme d’habitude à l’aide des
opérateurs ∨ et ¬, la logique temporelle linéaire comporte d’autres opérateurs définis,
dont la définition se trouve à la table 2.4. Le premier opérateur, ¦φ, se lit nécessairement,
φ se produira. Notons au passage qu’en anglais, cet opérateur se lit eventually. Le lecteur
francophone doit donc faire attention, ici, à la confusion linguistique entre le mot français
Chapitre 2. Logiques temporelles 60
lire i lire i
si i < 0 alors si i < 0 alors
i := −i i := −i
lire j lire j
k := i + j k := i + j
J
φ ::= p | ¬φ | φ1 ∨ φ2 | °φ | φ1 U φ2 | φ | φ1 S φ2
Les opérateurs temporels de LTL, comme nous l’avons déjà dit, sont des opérateurs
Chapitre 2. Logiques temporelles 61
J
σ(i) |= φ ssi i > 0 et σ(i − 1) |= φ
σ(i) |= φ1 S φ2 ssi ∃0 ≤ k ≤ i.σ(k) |= φ2 et ∀i ≥ j > k.σ(j) |= φ1
temporels futurs, au sens où leur sémantique ne réfère qu’à des états se trouvant après
l’état considéré. Il est alors naturel de se demander s’il serait possible d’augmenter
l’expressivité de LTL en lui ajoutant des opérateurs faisant référence au passé. La logique
obtenue en ajoutant les équivalents passés de U et ° à LTL s’appelle P-LTL (Past-
LTL).
La syntaxe de P-LTL est donnée à la table 2.5. Il s’agit de la même syntaxe que
J
pour LTL, à laquelle on a ajouté des opérateurs temporels passés S et . L’opérateur
J
S doit se lire depuis, et l’opérateur doit se lire juste avant.
def
¨φ = vrai S φ
def
¥φ = ¬¨¬φ
Comme dans le cas de LTL, il est possible de définir de nouveaux opérateurs à l’aide
de ces opérateurs. Quelques exemples de ces opérateurs sont donnés à la table 2.7. Les
opérateurs ¨ et ¥ sont les équivalents passés de ¦ et ¤. La formule ¨φ est satisfaite par
un état σ(i) si et seulement si celui-ci est précédé d’un état satisfaisant φ, et la formule
¥φ est satisfaite un état σ(i) si et seulement si tous ses prédécesseurs satisfont φ.
Pour reprendre l’exemple que nous déjà étudié, la formule ¤(read(v) → ¨write(v))
signifie alors que si une variable v est lue, alors avant cela, elle a été initialisée. Cette
formule est satisfaite, pour une variable v et une trace σ données, par les mêmes états
que la formule ¬read(v) U write(v). En effet, cette formule signifie que l’on ne doit pas
Chapitre 2. Logiques temporelles 62
lire la variable v avant de l’avoir initialisée. Ceci nous amène alors à douter du fait que
l’ajout d’opérateurs passés à LTL a vraiment augmenté son expressivité.
Pour répondre à cette question, nous devons d’abord nous demander de quelle ex-
pressivité on parle. En effet, dans les exemples étudiés jusqu’à maintenant, nous n’avons
considéré que l’état initial des chemins d’exécution. En effet, dans plusieurs cas d’ap-
plication, il est sous-entendu qu’un programme donné satisfait une propriété si son état
initial la satisfait. La relation d’équivalence entre des formules obtenue en ne considé-
rant que l’état initial est différente de celle obtenue si on regarde la trace en entier. On
distingue donc deux relations d’équivalence distinctes :
En raisonnant un peu, on voit que toute formule purement passée (n’utilisant pas les
J
opérateurs U et °) de la forme φ est initialement équivalente à faux, et que toute
formule purement passée de la forme φ1 S φ2 est initialement équivalente à φ1 ∧ φ2 .
Donc, si on était capable de sortir les opérateurs passés d’une formule combinant des
opérateurs passés et futurs, de façon à obtenir une combinaison booléenne de formules
purement passées et purement futures, on serait capable de lui trouver une formule
purement future qui lui serait initialement équivalente.
Le résultat suivant, démontré dans [61], nous dit que tel est le cas. La démonstration
étant un peu longue et hautement technique, nous ne la reproduirons pas ici.
Lemme 2.3 (Séparation) Toute formule de P-LTL peut être exprimée comme une
combinaison booléenne de formules purement futures et purement passées.
Il ne faut cependant pas tomber dans le piège de croire que les opérateurs passés
n’apportent rien à la logique temporelle linéaire. En fait, en plus de permettre d’exprimer
Chapitre 2. Logiques temporelles 63
certaines propriétés de façon plus intuitive et, comme nous le verrons sous peu, plus
succincte, il existe des cas d’utilisation où les algorithmes de vérification à utiliser sont
significativement plus efficaces. Par exemple, dans le cas où on ne vérifie qu’un seul
chemin d’exécution en temps réel, si la formule à vérifier est de la forme ¤φ, où φ ne
comporte que les opérateurs passés, il a déjà été démontré, dans [62], qu’il est possible
d’utiliser des algorithmes de vérification qui s’exécutent en temps linéaire et n’utilisent
qu’une quantité de mémoire constante en fonction de la taille du chemin vérifié. En
fait, il est aussi possible d’arriver au même résultat si on se limite aux opérateurs
futurs [31], mais comme les algorithmes utilisés partent alors de la fin de la trace, on
est forcé d’attendre que le programme ait terminé son exécution avant de passer à la
vérification. En plus de ne fonctionner que dans le cas où le programme à vérifier n’a
pas de chemin d’exécution infini, cette méthode oblige à sauvegarder toute la trace
d’exécution, plutôt que de simplement la vérifier en même temps qu’elle se crée. Pour
ces raisons, l’introduction d’opérateurs passés en logique temporelle représente donc un
grand intérêt.
J
φ ::= p | ¬φ | φ1 ∨ φ2 | °φ | φ1 U φ2 | φ | φ1 S φ2 | N φ
on se retrouve alors aux prises avec le problème suivant : l’alarme continue de sonner
après la remise à zéro. On voudrait être capable de dire que le problème est survenu
depuis la remise à zéro. Autrement dit, on voudrait être capable d’oublier le passé. Pour
régler ce problème, certains auteurs [63, 64] ont proposé d’ajouter à P-LTL l’opérateur
Chapitre 2. Logiques temporelles 64
Théorème 2.5 (Succinteté de P-LTL) P-LTL peut être exponentiellement plus suc-
cinte que LTL.
Théorème 2.6 (Succinteté de N-LTL) N-LTL peut être exponentiellement plus suc-
cincte que P-LTL.
J
φ ::= X | p | ¬φ | φ1 ∨ φ2 | °φ | φ | µX.φ
Une autre façon de percevoir le déroulement du temps est de ne prendre que les
opérateurs de passé et de futur immédiats, et de permettre de définir des formules
récursivement. Par exemple, on pourrait définir l’opérateur ¦φ de la façon suivante :
Par induction, cette formule serait alors satisfaite par σ(i) si et seulement si il existe
un état à partir de σ(i) satisfaisant la formule φ.
def
νX.φ = ¬µX.φ[¬X/X]
def
φ1 U φ2 = µX.φ2 ∨ (φ1 ∧ °X)
def J
φ1 S φ2 = µX.φ2 ∨ (φ1 ∧ X)
variable X par le i − 1ème approximant selon X. La formule µX.φ est alors satisfaite par
σ(i) si et seulement si il existe un approximant selon X satisfait par σ(i). Par exemple,
dans le cas de la formule ¦φ, la suite des approximants selon X est faux, φ ∨ °faux,
φ ∨ °(φ ∨ °faux),. . . .
On peut aussi définir, à partir de l’opérateur de plus petit point fixe, l’opérateur
ν, qui est l’opérateur de plus grand point fixe. Sa définition formelle est donnée à la
table 2.12. L’approximant de base devient alors true, et la formule νX.φ est alors
satisfaite si et seulement si elle est satisfaite par tous ses approximants selon X. Par
exemple, il permet de définir ¤φ comme étant νX.(φ ∧ °X).
Il est facile de voir que le µ-calcul linéaire est au moins aussi expressif que LTL ou
P-LTL. À la figure 2.12, on montre comment on peut définir les opérateurs de LTL et
P-LTL à l’aide des opérateurs du µ-calcul linéaire. Il est cependant moins facile de voir
si celui-ci est strictement plus expressif. En fait, le lemme suivant [33], que nous ne
démontrerons pas ici, nous permet de conclure que le µ-calcul linéaire est strictement
plus expressif que LTL :
Lemme 2.7 Il n’existe pas de formule de LTL satisfaite par tous les états σ(i) d’une
trace si et seulement si ∀i.p ∈ σ(i) ssi i est pair.
Théorème 2.8 Le µ-calcul linéaire est strictement plus expressif que LTL.
Nous terminons cette section en remarquant que nous n’avons pas justifié l’emploi
du terme point fixe pour désigner les opérateurs µ et ν. Ceci est dû au choix que
nous avons fait de présenter leur sémantique à l’aide de la notion d’approximant. Une
autre façon de faire est de voir une formule φ comportant une variable libre X comme
une fonction prenant en entrée un ensemble d’états et donnant en sortie l’ensemble
Chapitre 2. Logiques temporelles 67
des états satisfaisant φ sous l’hypothèse que ceux-ci satisfont déjà X. L’ensemble des
états satisfaisant µX.φ (νX.φ) est alors le plus petit (le plus grand) point fixe de
cette fonction. L’existence de ce point fixe est garantie à condition que la fonction
soit monotone (ce qui sera le cas si on pose les restrictions nécessaires sur l’imbrication
des négations ou mieux encore, si on ne permet de ne les appliquer qu’aux constantes
propositionnelles). L’existence de ce point fixe a été démontrée par Tarski [65] en 1955
dans un contexte plus général : celui des treillis. Nous nous arrêtons ici sur cette façon
de définir la sémantique des opérateurs de point fixe, mais sachons seulement qu’il est
possible de les démontrer comme étant équivalentes [53].
Les quatre logiques que nous venons de présenter étaient toutes comparables du point
de vue de l’expressivité car elles étaient toutes définies à partir du même modèle : celui
de la deuxième ligne de la table 2.1. Les logiques que nous présentons dans cette section
ne leur sont pas comparables car elles sont définies à partir de modèles temporisés.
Les deux premières logiques que nous présentons sont des logiques propositionnelles,
et leur modèle est celui de la troisième ligne. À chaque événement on associe, en plus
d’un ensemble de constantes propositionnelles, un instant t pris dans un domaine tem-
porel T. Les éléments σ(i) d’une trace σ sont donc des couples, dont la première compo-
sante, σ1 (i), est l’ensemble de constantes propositionnelles, et la seconde composante,
σ2 (i), est le temps qui lui est associé. Si l’ensemble T choisi pour représenter le temps
est R, on dispose d’une représentation du temps qui est continue, et s’il s’agit de N,
on dispose d’une représentation discrète. Dans le cas de ces deux premières logiques,
l’ensemble choisi par les auteurs est N.
La troisième logique que nous présentons se distingue des deux autres de par le fait
que le modèle sur lequel elle est définie est celui d’une logique temporisée du premier
ordre (avant-dernière ligne de la table 2.1). Aussi, le choix du domaine temporel (continu
ou discret) est laissé libre à l’utilisateur.
Les premiers articles sur la vérification de systèmes temps-réel datent de la fin des
années 80. Il ne faut pas confondre ce problème avec celui de la vérification de systèmes
Chapitre 2. Logiques temporelles 68
π ::= x + c | c
φ ::= p | π1 ≤ π2 | π1 ≡d π2 | x.φ | ¬φ | φ1 ∨ φ2 | °φ | φ1 U φ2
en temps réel (qui est celui qui nous intéresse dans le cadre du présent mémoire). Un
système temps-réel est un système dont la spécification comporte des aspects temporels
exprimés en unités de temps. Par exemple, au lieu de s’intéresser à ce que la porte du
garage se ferme après que l’auto y soit entrée, on s’intéressera à ce qu’elle se ferme pas
plus de une minute après que l’auto y soit entrée.
De la même façon que plusieurs formalismes ont été proposés pour décrire les sys-
tème non-temporisés (automates, algèbres de processus, réseaux de Petri, structures de
Kripke, systèmes de transition étiquetés) plusieurs formalismes ont aussi été proposés
pour représenter les systèmes temps-réel (automates temporisés, algèbres de processus
temporisées, etc.). Une des caractéristiques les plus importantes des systèmes temporisés
est que les différentes actions pouvant être effectuées par le système ne sont plus consi-
dérées comme pouvant être effectuées en un simple tic d’horloge, mais en un certaine
nombre de tics d’horloge. Par exemple, si un système peut effectuer la suite d’actions
ouvrir−entrer−f ermer, et que ces actions prennent respectivement 5,10 et 5 secondes,
alors le système peut effectuer l’action f ermer après avoir effectué l’action ouvrir, mais
il ne peut pas l’effectuer dans un délai de 2 secondes, car il doit entre temps effectuer
l’action entrer, qui elle prend 10 secondes.
Nous ne nous étendrons pas ici sur les différents formalismes ayant été proposés
pour représenter les systèmes temps-réel, mais sachons seulement que c’est dans ce
contexte que les logiques temporisées sont d’abord apparues. Un des auteurs ayant le
plus participé au développement de la vérification des systèmes temps-réel est sans
doute Thomas A. Henzinger qui, dans sa thèse de doctorat [66], a proposé tout une
méthodologie pour représenter et vérifier les systèmes temps-réel.
Une de ses contributions, du point de vue des logiques temporelles, est d’avoir pro-
posé d’ajouter aux diverses logiques temporelles déjà existantes (LTL [56], CTL [67],
µ-calcul modal [67], et même ETL [66], dont nous ne parlons pas ici) la notion de chrono-
mètre. Un chronomètre est une variable qui s’unifie avec le temps de certains événements
à l’aide d’un opérateur spécial, appelé opérateur d’initialisation (reset). Une fois initia-
lisé, un chronomètre peut être utilisé pour spécifier les contraintes temporelles devant
être vérifiées entre diverses actions.
Chapitre 2. Logiques temporelles 69
def def
avec ε(x + c) = ε(x) + c et ε(c) = c
def
¦t φ = x.(true U y.(φ ∧ y < x + t))
Dans le cas de LTL, l’ajout de chronomètres donne la logique T-LTL [56], dont la
syntaxe est donnée à la table 2.13. La construction π représente une référence temporelle.
Le cas c est une référence temporelle absolue, et le cas x + c est une référence temporelle
relative (c unités de temps après x). La construction x.φ est celle permettant d’unifier
la variable x avec l’instant présent. Finalement, les constructions π1 ≤ π2 et π1 ≡d π2
sont des contraintes temporelles. La première signifie que π1 est plus petite ou égale à
π2 , alors que la seconde signifie que π1 est congrue à π2 modulo d. On remarque que
si T-LTL était définie pour un domaine temporel continu, cette dernière construction
n’aurait aucun sens.
La sémantique des opérateurs ainsi ajoutés à LTL est donnée à la table 2.14. On
remarque qu’il s’agit de la seule logique parmi celles présentées dans ce chapitre pour
laquelle on a besoin d’une notion d’environnement. Un environnement ε est une fonc-
tion partielle associant des instants à certains des chronomètres de la formule. Ceux-ci
sont mis à jour à l’aide de l’opérateur d’initialisation. La formule x.φ est donc satis-
faite par σ(i) sous un environnement ε si et seulement si φ est satisfaite par σ(i) sous
l’environnement ε mis à jour de façon à associer au chronomètre x l’instant de σ(i).
Les contraintes temporelles, quand à elles, sont satisfaites sous ε si les relations qu’elles
expriment sont vraies en remplaçant les chronomètres par les valeurs auxquelles ils sont
associés dans ε.
Nous ne pousserons pas plus loin cette présentation de T-LTL. Nous terminons en
présentant comment on peut définir, à l’aide des opérateurs de T-LTL, l’opérateur ¦t φ.
Chapitre 2. Logiques temporelles 70
Cette définition se trouve à la figure 2.15. Elle signifie que si x est l’instant associé à
l’événement présent, alors, à un certain instant y situé à moins de t unités de temps
dans le futur, φ doit être satisfaite.
I ::= [m, n] | ≡d c
J
φ ::= p | ¬φ | φ1 ∨ φ2 | °I φ | φ1 UI φ2 | I φ | φ1 SI φ2
Pour exprimer les contraintes de temps-réel, Henzinger a choisi d’ajouter à LTL cer-
tains opérateurs. D’autres auteurs [68], contemporains à Henzinger, ont plutôt proposé
de modifier les opérateurs temporels déjà présents dans LTL, en les indexant direc-
tement par les contraintes. La logique ainsi obtenue s’appelle MTL (Metric Temporal
Logic).
La syntaxe de MTL est donnée à la table 2.16. Les contraintes temporelles sont de
deux types : intervalles ([m, n]) et relations de congruence (≡d ). Par exemple, l’opérateur
def
¦t , est défini dans MTL de la façon suivante : ¦t φ = true U[0,t] φ.
La sémantique des opérateurs de MTL est donnée à la table 2.17. Ils s’interprètent
Chapitre 2. Logiques temporelles 71
tous de la même façon que pour LTL, sauf que les états concernés sont restreints à la
contrainte temps-réel I. Par exemple, si σ2 (i) = 7, alors °[0,5] φ est satisfaite par σ(i)
ssi σ(i + 1) |= φ et σ2 (i + 1) ∈ [7, 12]. Même chose pour la relation de congruence. Si
σ2 (i) = 7, alors °≡5 φ est satisfaite par σ(i) ssi σ(i + 1) |= φ et σ2 (i + 1) ≡5 12.
Dans sa thèse, Henzinger affirme que les deux façons d’exprimer les contraintes
temps-réel sont équivalentes. Il affirme aussi que MTL, du point de vue de la décidabilité,
se prête mieux aux contraintes passées que T-LTL. Historiquement, il semblerait que
MTL ait gagné la préférence de la communauté, car au cours des recherches que nous
avons faites, nous avons trouvé un plus grand nombre d’auteurs utilisant MTL que
T-LTL.
2.2.3 TRIO
Nous terminons ce chapitre avec une brève description de TRIO [69], une logique
temporelle du premier ordre avec quantificateurs. Nous verrons que lorsque l’on dispose
de la puissance d’expression de la logique du premier ordre, le nombre d’opérateurs
temporels dont on a besoin chute rapidement.
La syntaxe de TRIO est donnée à la table 2.18. Cette logique ne comporte qu’un
seul opérateur temporel, Dist, dont la sémantique est donnée à la table 2.19. La formule
Dist(φ, t) est vraie pour les états σ(i) ayant un état situé à une distance t (positive ou
négative, finie ou infinie) satisfaisant φ.
def
°φ = Dist(φ, t) ∧ ∀d.Dist(true, d) → d > t
J def
φ = Dist(φ, −t) ∧ ∀d.Dist(true, −d) → d > t
def
¤t φ = ∀d.0 ≤ d < t → Dist(φ, d)
def
φ1 U φ2 = ∃t.Dist(φ2 , t) ∧ ¤t φ1
def
¥t φ = ∀d.0 ≥ d > −t → Dist(φ, d)
def
φ1 S φ2 = ∃t.Dist(φ2 , −t) ∧ ¥t φ1
voit quelques exemples d’opérateurs temporels que l’on peut définir à partir de l’opéra-
teur Dist. Il est sous-entendu, dans ces définitions, que les variables t et d sont positives.
Par exemple, l’opérateur °φ signifie qu’il existe un état situé à une distance positive t
satisfaisant φ, et que tous les états situés à une distance positive sont situés plus loin.
Les autres opérateurs sont définis de façon semblable.
2.3 Conclusion
Dans ce chapitre, nous avons donné une présentation de sept logiques temporelles
définies à partir de trois modèles différents. Le premier choix à faire, face à un problème
de vérification, est celui du modèle. Une fois que celui-ci est choisi, on se penche alors
sur celui de la logique.
Dans les deux chapitres suivants, nous verrons comment le choix du modèle et de la
logique peut influencer la qualité du travail. D’une part, le modèle choisi doit à la fois col-
ler le mieux possible à la vision que nous avons du problème auquel nous nous attaquons,
et d’autre part, la logique doit comporter des opérateurs appropriés non-seulement aux
propriétés vérifiées, mais aussi au contexte de la vérification. Au chapitre 3 comme au
chapitre 4, le modèle que nous choisirons est un modèle du premier ordre. Des hypo-
thèses différentes sont cependant faites par rapport aux interprétations (la fonction I
de la table 2.1) faites de chaque état. De plus, la logique utilisée au chapitre 3 est
une logique temporisée, alors que celle du chapitre 4 peut être temporisée, mais n’a
absolument pas besoin de l’être pour pouvoir l’utiliser telle que nous la décrivons. Fi-
nalement, la principale différence entre les deux logiques que nous présentons est que
celle du chapitre 3 dispose d’opérateurs temporels futurs, alors que celle du chapitre 4
dispose d’opérateurs temporels passés. Nous verrons que dans un contexte de vérifica-
Chapitre 2. Logiques temporelles 73
tion en temps-réel, une logique définie à partir d’opérateurs passés est non-seulement
plus intuitive, mais aussi algorithmiquement avantageuse.
Chapitre 3
Dans la section 3.1, nous donnons un aperçu général de la structure logicielle d’un
système de détection d’intrusions et d’analyse passive que nous avons développé. Dans
la section 3.2, nous présentons la syntaxe du langage de signatures du système déve-
loppé. Dans la section 3.3, nous montrons comment le langage peut être utilisé pour
détecter des scénarios d’attaque se déroulant sur plusieurs paquets. Dans la section 3.4,
nous montrons comment le langage peut être utilisé pour acquérir passivement de l’in-
formation sur un réseau informatique. Dans la section 3.5, nous présentons le langage
de signatures d’une façon formelle de même que l’algorithme de vérification en ligne
lui étant associé. Finalement, nous concluons en critiquant le système et le langage
développés en les comparant à ceux analysés au chapitre 1.
Chapitre 3. Détection d’intrusions et analyse passive 76
Modules d’affichage
Base de connaissances
Préprocesseurs Préprocesseurs
Libpcap Libpcap
Réseau Réseau
Fig. 3.1 – Architecture du système développée (à droite), et celle de Snort (à gauche).
Nous avons conçu et implanté un système de détection d’intrusions basé sur un lan-
gage simple qui, dans le style de LAMBDA, permet de tenir à jour et d’utiliser une
base de connaissances en vue d’effectuer une détection plus précise. Ce système réuti-
lise une partie de l’architecture logicielle de Snort - principalement les préprocesseurs
effectuant un travail de formattage sur les paquets - mais utilise un nouvel engin de
détection permettant, en plus de détecter les paquets pouvant être considérés comme
offensifs, de détecter des suites de paquets pouvant être considérées comme offensives.
Nous avons aussi ajouté à l’architecture du système une base de connaissances qui
permet, en plus d’accumuler les connaissances acquises, d’inférer de nouvelles connais-
sances. Les règles d’inférence de cette base de connaissances sont spécifiées en Prolog,
ce qui fait que le système développé peut être vu comme un système à deux niveaux,
avec un engin permettant de détecter les suites de paquets, et un autre permettant d’in-
férer les connaissances. La base de connaissances, en plus de permettre une détection
d’intrusions plus précise, peut être consultée directement par l’administrateur réseau en
cas de besoin. Par exemple, il pourrait l’utiliser afin de savoir quels sont les hôtes du
réseau offrant le service Telnet. Finalement, elle permet aussi de vérifier des politiques
de sécurité haut niveau, telles que il ne doit y avoir qu’un seul routeur dans le réseau.
Chapitre 3. Détection d’intrusions et analyse passive 77
3.2 Langage
Nous commençons par présenter de façon informelle le langage que nous avons dé-
veloppé, de façon à mettre en évidence les objectifs poursuivis et l’intuition derrière les
choix que nous avons faits. Les aspects formels ne sont traités qu’à partir de la sec-
tion 3.5. La grammaire du langage développé, sous la forme BNF (Backus Naur Form),
est donnée à la table 3.1.
clause htimeouti. Finalement, à chaque étape, il est possible de faire des ajouts ou des
retraits dans la base de connaissances. Cette interaction peut se faire soit à l’identi-
fication d’un paquet (dans le cas où le non-terminal houtputi suit immédiatement le
non-terminal hfilteri), soit à l’écoulement d’un délai (dans le cas où le non-terminal
houtputi suit immédiatement le non-terminal htimeouti). Dans l’implantation que nous
proposons, la base de connaissances est programmée en Prolog, car ce langage per-
met de traiter et de relier entre elles les connaissances. Le mot-clé output indique une
instruction qui doit être transmise telle quelle à la base de connaissances.
Les symboles hsnortsigi et hprologi ne sont pas définis car ils réfèrent à des langages
existants que nous nous dispenserons de décrire à nouveau ici [1, 72]. La syntaxe du
symbole hidi est la même que celle d’une variable dans le langage de programmation
C. Le symbole hfieldi devrait être décrit par énumération. Il désigne explicitement un
champ d’une entête donnée. Par exemple, les mots sip, dip, sport, dport désignent res-
pectivement les adresses IP de même que les ports source et destination. Dans le cas
d’un paquet ARP, les mots opcode, thdradr, tprotoadr, shdradr, sprotoadr désignent res-
pectivement : le type de paquet (requête ou réponse), les adresses matérielle et logicielle
de la cible de même que celles de la source.
Le balayage de ports est souvent une des premières étapes à effectuer pour attaquer
un système. Il permet de savoir quels sont les services offerts et de se faire ainsi une
première idée de l’approche à utiliser. En fait, bien que non-dommageable pour la vic-
time, le balayage des ports constitue tout de même une attaque en soi car il permet
à l’attaquant d’acquérir des informations privilégiées. Une des difficultés reliées à sa
détection relève du fait qu’il peut se faire en utilisant le protocole TCP de façon tout
à fait normale. C’est pourquoi un système à base de règles tel que Snort, fonctionnant
un paquet à la fois, ne peut à la base détecter ce type d’attaque. Pour ce faire, on
doit y aller par programmation, par exemple à l’aide des préprocesseurs, passant ainsi
par dessus le système de règles. Bien que très efficace, cette façon de faire ne donne
cependant pas beaucoup de souplesse à l’usager qui voudrait définir lui-même ce qu’il
considère comme un balayage.
Dans cette section, nous montrons comment le langage que nous proposons permet
de définir de façon claire et concise ce que nous considérons comme un balayage. Nous
voyons aussi comment notre approche permet de définir rapidement de nouveaux types
de balayages, une tâche autrement plus ardue lorsque l’on utilise les préprocesseurs.
Chapitre 3. Détection d’intrusions et analyse passive 79
La méthode de balayage TCP connect() est sans doute la plus répandue et la plus
facile. L’attaquant n’a qu’à effectuer une tentative de connection sur le port cible et
simplement vérifier si elle est acceptée ou pas. Bien que présentant certains avantages
de rapiditié et de simplicité, cette méthode n’est cependant pas à privilégier car elle se
détecte assez facilement par consultation des fichiers de log [7]. Pour éviter ce dernier
problème, on privilégiera une méthode plus subtile, comme par exemple le balayage
SYN. Dans un balayage SYN, en cas de réception d’un SYN-ACK, on enverra sim-
plement un RST au lieu d’un ACK comme on devrait le faire pour finir d’établir la
connection. Le système ne gardera ainsi généralement pas trace de la connection.
Dans un cas comme dans l’autre, dans les situations où l’attaquant voudra balayer
plusieurs ports sur une même machine, la signature montrée à la figure 3.2 devrait être
en mesure de détecter le comportement malicieux de l’attaquant. Elle spécifie que si trois
demandes de connection, addressées au même hôte et provenant du même hôte, mais
faites sur des ports cibles différents, survenaient à moins de deux secondes d’intervalle,
il faudrait alors ajouter à la base de connaissances qu’une attaque de portscan vient
d’avoir lieu.
La limite de trois connections, de même que le délai de deux secondes, sont tout à
fait arbitraires. En fait, il faut faire un compromis entre la possibilité de faux négatifs
et l’engorgement de la mémoire. L’important ici est de remarquer qu’une modification
mineure du fichier de signatures permet d’ajuster rapidement notre politique de sécurité.
Par exemple, on peut modifier la signature précédente afin de redéfinir la notion de
balayage de ports au niveau réseau. Certains attaquants peuvent en effet ne pas être
intéressés à attaquer une machine en particulier, mais plutôt un service donné, comme
dans le cas de certains serveurs warez qui exploitent des comptes FTP anonymous
laissés négligemment actifs par certains administrateurs réseau lors de l’installation du
système. Dans ce cas, la politique ne sera pas d’interdire la connection sur différents
Chapitre 3. Détection d’intrusions et analyse passive 80
ports du même hôte, mais plutôt sur le même port de différents hôtes.
De la même façon, on pourrait aussi définir un balayage Ping comme étant une
certaine quantité de requêtes ICMP echo provenant du même hôte vers des hôtes
différents dans un court laps de temps.
Comme nous l’avons déjà dit, l’analyse passive regroupe un ensemble de techniques
permettant d’acquérir de l’information sur un réseau seulement en écoutant le trafic
circulant à quelques endroits stratégiques, par exemple à la sortie du routeur principal.
D’une certaine façon, on peut la considérer comme une généralisation de la détection
d’intrusions au niveau réseau. En détection d’intrusions, on est seulement intéressé à
savoir si oui ou non un certain type de comportement, considéré comme malicieux, sur-
vient. En analyse passive, on sera plutôt intéressé à déduire le maximum d’information
du trafic (normal aussi bien que malicieux) que l’on voit circuler sur le réseau. Cette
information peut concerner autant la configuration des hôtes que l’activité se déroulant
en général sur le réseau, comme par exemple les sessions TCP actives.
Encore une fois, chacune des techniques mentionnées dans cette section peut se faire
aussi par programmation. Par exemple, dans le cas de Snort, la détection des sessions
actives est déjà prise en compte par le préprocesseur Flow. Cependant, il ne faut pas
perdre de vue que l’idée ici est de se munir d’un langage de spécification qui soit assez
général pour : minimiser le plus possible le temps de développement, maximiser le plus
possible la lisibilité et la réutilisation, et permettre un maximum de corrélation entre
les composantes, via la base de connaissances.
Les ports TCP ouverts des hôtes, de même que leurs sessions actives, constituent un
exemple simple mais important d’information qu’il est intéressant d’acquérir de façon
passive. En effet, on ne peut pas toujours se fier à la façon dont l’on croit avoir configuré
les hôtes de notre réseau, et cela pour plusieurs raisons. Il se peut (1) qu’un utilisateur
malveillant ou insouciant ait lui-même installé une application qui va à l’encontre de
nos politiques de sécurité, comme par exemple Kazaa, (2) qu’un utilisateur ait reçu par
courriel un virus ayant pour effet de démarrer un serveur illicite (cheval de Troie), (3)
Chapitre 3. Détection d’intrusions et analyse passive 81
qu’un visiteur avec un ordinateur portable, en dehors de notre contrôle, ne respecte pas
notre politique de sécurité.
Pour découvrir passivement quels sont les ports TCP ouverts sur nos hôtes, on n’a
qu’à écouter les demandes de connection. Lorsque les demandes sont acceptées par le
serveur, on peut conclure que le port est ouvert. Ce n’est cependant qu’à la troisième
étape de la connection, lors de la réception de confirmation, que l’on peut conclure que
réellement il y a une connection en cours. Dans le cas où cette confirmation n’a pas
lieu, il y a fortement lieu de penser qu’une attaque de type synscan est en cours. À la
figure 3.3, on peut voir comment notre langage supporte ces techniques d’acquisition
d’information. On remarque que l’ajout, dans la base de connaissances, de l’attaque
synscan se fait après l’écoulement du délai (timeout).
Pour détecter quels sont les ports TCP fermés, la technique est relativement sem-
blable à celle utlisée pour les ports ouverts. Dans ce cas-ci, c’est cependant le RST, au
lieu du SYN-ACK, qui nous permettra de conclure. Le scénario correspondant à cette
technique se trouve à la figure 3.4.
Nous venons de montrer comment il est possible de détecter qu’une session est
active entre deux hôtes. Pour la considérer comme étant terminée, il faut attendre
qu’un paquet FIN soit envoyé par un des deux hôtes. La connection sera alors fermée
dans une direction, et il faudra attendre l’envoi d’un autre paquet FIN pour fermer
l’autre direction. Dans le cas où il n’y avait aucune connection active, il faut signaler
une alerte de finscan. En effet, la méthode de balayage FIN est encore plus subtile que
celles précédemment mentionnées. Il s’agit d’envoyer un paquet de fin de connection
sans en avoir établie auparavant. Le comportement normal d’un port ouvert est de ne
pas répondre, alors que celui d’un port fermé sera d’envoyer un paquet RST. Aussi, il
ne faut pas oublier que certaines connections peuvent aussi se terminer par un RST.
Dans ce cas, les deux sens de la connection sont fermés en même temps. Le scénario de
la figure 3.5 implante ces techniques d’acquisition.
Chapitre 3. Détection d’intrusions et analyse passive 83
step syn : tcp any any -> any any (flags :S ; tcpopts :MTW)
step synack : tcp any any -> any any (flags :SA ; tcpopts :M ; ttl :64 ; window :12)
match : syn.sip=synack.dip ; syn.dip=synack.sip ;
syn.sport=synack.dport ; syn.dport=synack.sport ;
output :assert(os(syn.dip, "Free BSD 3.0")),
assert(os(syn.dip, "FreeBSD 3.1")),
...
assert(os(syn.dip, "FreeBSD 4.3")).
timeout : syn.timestamp + 2sec ;
La prise en compte du contexte dans lequel survient une attaque permet de dimi-
nuer le nombre de faux-positifs générés par un système de détection d’intrusions. Par
exemple, dans le cas de Snort, l’ajout du préprocesseur Flow a permis de diminuer
le nombre de faux-positifs en spécifiant que certains attaques doivent survenir dans
le contexte d’une session TCP active pour réussir. La prise en compte du contexte
peut cependant aller beaucoup plus loin que la simple vérification des sessions TCP.
Par exemple, la plupart des attaques utilisent des vulnérabilités connues de certains
systèmes. La connaissance de la présence éventuelle de ces vulnérabilités peut alors per-
mettre de diminuer le nombre de faux-positifs en vérifiant que le système attaqué est
bel et bien vulnérable à l’attaque identifiée.
À la figure 3.6, on montre comment on peut détecter, à l’aide du langage que nous
avons développé, un système d’exploitation FreeBSD. Cette signature a été rédigée à
partir d’une base de données de signatures [73] ayant été mise au point au Centre de
Recherches sur les Communications. La deuxième étape du scénario (le SYN-ACK) est
Chapitre 3. Détection d’intrusions et analyse passive 84
alert tcp any any -> any 21 (msg :"FTP EXPLOIT wu-ftpd 2.6.0 site
exec format string overflow FreeBSD" ; flow :to_server,established ;
content :"1|C0|PPP|B0| |CD 80|1|DB|1|C0|" ; reference :bugtraq,1387 ;sid :343 ;)
step exploit : tcp any any -> any 21 (content :"1|C0|PPP|B0| |CD 80|1|DB|1|C0|" ;)
match : session(exploit.sip,exploit.dip,exploit.sport,exploit.dport) ;
os(exploit.dip, "Free BSD 4.3") ;
output :assert(alert("FTP EXPLOIT", exploit.sip, exploit.dip)).
À la figure 3.7, se trouve une version simplifiée d’une règle Snort permettant de
détecter une tentative d’exploiter une vulnérabilité du serveur FTP de certaines versions
du système d’exploitation FreeBSD. Cette vulnérabilité est référenciée (entre autres) par
l’organisme Bugtraq [74] par le numéro 1387. En consultant le site web de Bugtraq, on
peut savoir quelles sont les versions de FreeBSD qui sont vulnérables à cette attaque.
Il est alors possible, comme on le montre à la figure 3.8, d’utiliser la connaissance
que nous avons du système d’exploitation de la machine attaquée pour vérifier s’il
est vraisemblable que l’attaque ait réussi. On voit aussi comment on peut replacer
l’utilisation du préprocesseur Flow pour vérifier la présence d’une session TCP active.
Lorsque l’on voit passer plusieurs paquets ayant la même adresse MAC source, mais
des adresses IP source différentes, on peut conclure que l’hôte ayant cette adresse MAC
agit comme routeur. Une fois que l’on connaı̂t l’adresse MAC du routeur, on peut dé-
duire son adresse IP en regardant les réponses aux requêtes ARP qui lui sont adressées.
Ces techniques sont illustrées à la figure 3.9.
Chapitre 3. Détection d’intrusions et analyse passive 85
gatewayip(ip) :- macip(X,ip),gatewaymac(X).
L’exemple que nous venons tout juste de voir est généralisable. On peut le formuler
ainsi : si on sait que la machine ayant l’adresse MAC X a la caractéristique c, et que la
machine ayant l’adresse IP Y a l’adresse MAC X, alors la machine ayant l’adresse IP
Y a aussi la caractéristique c. Il s’agit presque d’une lapalissade, mais dans un contexte
où l’on a besoin de rechercher de l’information à partir de différentes clés, comme c’est
le cas lorsque l’on doit utiliser le peu d’information qui se trouve dans un paquet donné,
il est souhaitable d’avoir le plus de transparence possible. C’est pourquoi, au lieu de
la règle que nous avons définie ci-haut, il serait préférable, par exemple, d’utliser les
réponses aux requêtes ARP pour emmagasiner les associations entre les adresses IP et
les adresses MAC.
Par la suite, on peut définir l’adresse IP d’un routeur comme étant une adresse IP
associée à l’adresse MAC d’un routeur. Cette inférence est faite au niveau de la base de
connaissances, et c’est pourquoi la dernière ligne de la figure 3.10 est écrite en Prolog,
Chapitre 3. Détection d’intrusions et analyse passive 86
et non dans notre langage de signatures. Cette signature consitue une version améliorée
de la seconde signature présentée à la figure 3.9. En effet, la seconde signature de la
figure 3.9 implique que la détection de l’adresse MAC du routeur doit être faite avant
de détecter quelle est l’adresse IP lui étant associée. Avec la signature de la figure 3.10,
l’ordre dans lequel les deux connaissances sont acquises n’a pas d’importance.
Cependant, on voit que cette règle d’inférence n’est pas encore assez générale, car il
faudrait la redéfinir pour chacun des types d’information. Comme on le voit, la struc-
turation et la représentation de l’information à acquérir, dans une optique où on veut
en inférer de nouvelles, est une étape très importante et qui, en fait, constitue en soi un
défi sérieux à relever.
Maintenant que nous avons une meilleure intuition des objectifs poursuivis par le
langage que nous avons développé, nous allons voir comment il est possible d’en faire
une étude formelle à l’aide des notions que nous avons vues au chapitre 1. Cette étude
nous permettra de mieux percevoir les limites de notre langage, et de le critiquer en
regard des critères dont nous avons donné la liste à la table 1.9. Quatre aspects du
langage seront donc traités dans cette section, soit le modèle sur lequel il travaille, sa
syntaxe, sa sémantique, et son algorithme de vérification.
3.5.1 Modèle
Le modèle sur lequel nous travaillons, celui d’une trace de trafic, est généralisable
à celui de trace d’enregistrements. Il s’agit essentiellement du même modèle que celui
utilisé par ASAX et LogWeaver. Le concept d’enregistrement se distingue de celui d’état,
utilisé par exemple avec LTL, de par le fait qu’il permet d’associer des valeurs à des
étiquettes, et ainsi de comparer les valeurs associées aux étiquettes de différents états.
Avec le concept d’état traditionnellement utilisé, qui consiste en un sous-ensemble d’un
ensemble de constantes propositionnelles P , la comparaison entre les différents états se
fait beaucoup moins naturellement (bien que théoriquement possible).
croissant, c’est-à-dire que les enregistrements se situent dans la trace dans le même
ordre que celui des événements qu’ils représentent. Ces idées se formalisent donc dans
la définition suivante :
Définition 3.1 Un enregistrement est une fonction dont le domaine est un ensemble
d’étiquettes, aussi appelées champs, et dont le codomaine est un ensemble de valeurs
numériques (par exemple N). Une de ces étiquettes, τ , représente le temps. Une trace
σ est une suite infinie d’enregistrements. Par σ(i), on désigne le ieme enregistrement, et
par σ i , on désigne le suffixe de σ commençant à σ(i). Le temps est croissant, c’est-à-dire
que ∀i.σ(i).τ < σ(i + 1).τ .
3.5.2 Syntaxe
expressions) peuvent, comme nous le verrons, contenir des variables faisant référence aux
différents champs des enregistrements consituant les scénarios. La troisième partie de la
syntaxe permet d’exprimer des délais. Un délai peut être relatif à un événement id passé
(id.τ + δ), ou infini (∞). D’un point de vue expressivité, l’ajout de ces délais n’apporte
rien, car il aurait été possible de les représenter par une expression p. Cependant,
comme ils jouent un rôle crucial dans l’algorithme de vérification en ligne que nous
présenterons plus loin, nous avons jugé bon de spécifier ceux-ci au niveau même de
la syntaxe. Finalement, remarquons que les opérateurs temporels que nous utilisons
constituent des cas particuliers de ceux utilisés avec MTL.
avec
σ |= tt
σ |= hid, φit ψ ssi ∃i.σ(i).τ <Eval(t) et Eval(φ[σ(i).n/id]) =vrai
et σ i+1 |= ψ[σ(i).n/id]
σ |= ¬ψ ssi σ 6|= ψ
σ |= ψ1 ∧ ψ2 ssi σ |= ψ1 et σ |= ψ2
Eval(p) ∈ {vrai,faux}
Eval(¬φ) = ¬Eval(φ)
Eval (φ1 ∧ φ2 ) = Eval(φ1 )∧Eval(φ2 )
3.5.3 Sémantique
La sémantique du langage est donnée à la table 3.3. Les quatres premières règles
définissent la règle de satisfiabilité qui précise quand est-ce qu’une trace σ satisfait une
formule ψ. Toute trace satisfait la formule tt. Une trace σ satisfait la formule hid, φit ψ
si elle admet, avant que le délai t ne soit écoulé, un paquet σ(i) qui, une fois substituées
toutes les occurrences de la variable id par σ(i).n (le nom de σ(i)) satisfait φ, et que le
reste de la trace à partir de ce paquet, σ i+1 , satisfait la formule ψ dans laquelle toutes
les occurrences de la variable id sont aussi substituées par σ(i).n. Une trace satisfait la
formule ¬ψ si elle ne satisfait pas la formule ψ. Elle satisfait la formule ψ1 ∧ ψ2 si elle
satisfait à la fois ψ1 et ψ2 .
Les trois dernières règles étendent la relation de satisfiabilité pour préciser comment
évaluer, à l’aide de la fonction Eval, une formule de paquets φ. Le cas intéressant est
celui d’une expression p. Comme cette expression contient des variables s’identifiant à
des paquets, comme par exemple syn.sip = synack.dip, la fonction Eval substitue les
variables par les valeurs contenues dans le modèle, et évalue l’expression obtenue en
fonction des opérateurs (par exemple, des opérateurs de comparaison) de l’expression.
Les cas de la négation et de la conjonction se traitent comme d’habitude. L’évaluation
de la négation d’une formule retourne la valeur opposée de l’évaluation de cette formule,
et l’évaluation d’une conjonction de formules retourne la conjonction de l’évaluation de
ces formules.
Par exemple, la trace montrée à la figure 3.11 satisfait la formule de la figure 3.12
car, après substitution de syn par σ(0), de synack par σ(1) et de ack par σ(2), les
formules de paquet φ1 , φ2 et φ3 deviennent :
Chapitre 3. Détection d’intrusions et analyse passive 90
3.5.4 Algorithme
ψ ::= hid, φit tt | ¬hid, φit tt | hid, φit ψ (Formules pour traces)
φ ::= p | ¬φ | φ1 ∧ φ2 (Formules pour paquets)
t ::= id.τ + δ | ∞ (Délais)
Le langage de scénarios que nous avons décrit à la section 3.2 ne permet pas de
représenter toutes les formules du langage abstrait que nous venons de définir. Plus
précisément, il permet seulement de représenter le sous-ensemble engendré par la syn-
taxe de la table 3.4. Les deux scénarios de base sont ceux où un paquet donné survient
(hid, φit tt) ou ne survient pas (¬hid, φit tt) avant un délai t donné. On compose en-
suite les scénarios plus complexes en leur ajoutant des étapes préalables (hid, φit ψ). Il
n’est pas possible, à l’aide du langage de scénarios, de représenter une conjonction de
scénarios ou encore l’absence de toute une suite de paquets.
avec
L’algorithme de vérification, pour une trace et une spécification données, est montré
à la table 4.8. Les opérateurs + et − représentent l’ajout et le retrait d’un élément
d’un ensemble. L’ensemble k représente la base de connaissances, et e l’ensemble des
scénarios à reconnaı̂tre. Ce dernier évolue à mesure que les préfixes des formules φ de
la spécification originale sont reconnus.
La substitution textuelle, à mesure que les étapes sont reconnues, est appliquée
non-seulement au reste de la formule, comme spécifié par la sémantique, mais aussi
aux actions à prendre lors de la reconnaissance (lignes 9 et 13). Par exemple, l’action
assert(session(syn.sip, . . .) se transforme ainsi en assert(session(192.168.0.100, . . .).
celle de la figure 3.13. Dans la colonne du centre sont affichées les actions posées sur
la base de connaissances pendant le traitement de chaque paquet, et dans celle de
droite le nombre de scénarios en cours de reconnaissance. Au paquet 0, une demande de
connection TCP, ne provoque aucun changement sur la base de connaissances. Chacun
des scénarios est cependant dupliqué, en subsituant id1 par σ(0). L’ensemble e contient
donc six éléments. Lorsque, au paquet 1, la connection est acceptée, on acquière la
connaissance que le port 23 de la machine 192.168.0.101 est ouvert. Seulement deux
scénarios sont dupliqués, pour un total de 8. Au paquet 2, la connection est complétée
et la session est ouverte dans chacune des directions. Le délai des trois premiers scénarios
copiés est écoulé, et ils sont donc retirés de la base de l’ensemble e. De plus, la condition
pour un synscan ayant été violée, la copie du scénario correspondant est aussi retirée.
Aucun scénario n’est dupliqué, on se retrouve donc avec un ensemble e de taille 4.
Le paquet 3 n’amène aucun changement sur la base de connaissances. Cependant, la
demande de connection fait en srte que les trois scénarios de base sont dupliqués. Le
délai du scénario de reconnaissance d’une poignée de main TCP pendant étant écoulé,
celui-ci est retiré de e, qui se retrouve de nouveau avec 6 éléments. Au paquet 4, la
connection est acceptée. Deux scénarios sont ainsi dupliqués, et on apprend que le port
21 de la machine 192.168.0.109 est ouvert. Au paquet 5, tous les délais sont écoulés. On
apprend ainsi qu’une attaque de type synscan vient d’avoir lieu. Aucun scénario n’est
dupliqué.
La condition Evalk1 (p) = Evalk2 (p) provient du fait que l’évaluation d’une expres-
sion p peut dépendre du contenu de la base de connaissances. Comme nous n’avons
pas tenu compte de la base de connaissances dans la définition de la syntaxe et de la
sémantique abstraites du langage, nous devons énoncer le théorème de validité comme
si celle-ci n’avait aucun effet sur l’exécution de l’algorithme. Si pour toute expression p
et pour toutes bases de connaissances k1 et k2 , Evalk1 (p) = Evalk2 (p), alors l’expression
Eval(p) est bien définie. On peut maintenant passer à la démonstration du théorème.
Démonstration :
⇒Supposons que @i.σ(i).τ <Eval(t) et Eval(φ[σ(i)/id]). Encore une fois, par l’hypo-
thèse de croissance, act sera commise (ligne 13) avant que le couple hψ ⇒ acti ne soit
retiré de e (ligne 14).
⇐Si, au contraire, il existe un tel i, alors la ligne 13 est exécutée avant la ligne 14.
L’action act ne sera donc pas commise.
⇐ Deux cas sont possibles. Soit que 6 ∃i.σ(i).τ <Eval(t) et Eval(φ[σ(i)/id]) = vrai, soit
qu’un tel i existe, mais que σ(i+1) 6|= ψ[σ(i)/id]. Dans le premier cas, la ligne 16 est exé-
cutée avant la ligne 17, et dans le second cas, c’est l’hypothèse d’induction qui entre en
jeu, ce qui termine la démonstration. ¤
3.6 Conclusion
Dans ce chapitre, nous avons vu comment il est possible d’améliorer la structure lo-
gicielle et le langage de signatures d’un système de détection d’intrusions tel que Snort
de façon à ajouter la possibilité de reconnaı̂tre des scénarios d’attaque se déroulant
sur plusieurs paquets et à pouvoir passivement acquérir de l’information. L’information
acquise peut être, en retour, utilisée par le système de détection d’intrusions pour ef-
fectuer une détection plus précise (moins de faux positifs, et moins de faux négatifs).
Elle peut aussi être consultée directement par l’administrateur réseau en cas de besoin.
Avec le langage actuel de Snort, la reconnaissance de scénarios impliquant plusieurs
paquets et l’acquisition passive d’information sont deux tâches impossibles, et doivent
être effectuées par programmation au niveau des préprocesseurs.
Le langage de signatures que nous avons proposé est basé sur une logique temporelle
avec des opérateurs temporels futurs. Comme l’ont fait les concepteurs de LogWeaver,
nous avons seulement conservé l’opérateur ♦, laissant ainsi tomber U et °. Une diffé-
rence importante entre notre logique et celle de LogWeaver est que nous avons restreint,
comme le proposaient les auteurs, la syntaxe aux scénarios purement linéaires, c’est-à-
dire ceux de la forme ♦p1 (∧♦p2 (∧♦p3 (∧ . . .))). Cette restriction présente l’avantage de
simplifier l’algorithme de vérification, tout en offrant suffisamment d’expressivité pour
Chapitre 3. Détection d’intrusions et analyse passive 96
les besoins liés à la détection d’intrusions. Nous avons cependant laissé la possibilité
d’exprimer le fait qu’il s’écoule, une fois une certaine suite d’événements reconnue, un
certain délai (de longueur finie) durant lequel un événement donné ne survient pas.
Le fait de pouvoir exprimer la non-occurence d’un événement donné durant un certain
laps de temps est un besoin courant en détection d’intrusions auquel il était possible de
subvenir à moindre coût. Dans un même ordre d’idées, une autre différence avec Log-
Weaver est le statut particulier que nous avons donné au temps. Nous avons travaillé
sous l’hypothèse qu’à chaque événement il était possible d’associer un temps et que ces
temps sont croissants suivant le fichier d’audit (ou la trace de trafic).
De plus, outre les inconvénients déjà cités, se trouvent aussi ceux reliés au choix d’une
suite d’enregistrements comme modèle de trace. Bien que ce choix de modèle présente
l’avantage, comparativement au modèle propositionnel, de permettre de comparer les
différents événements entre eux, il ne reflète cependant pas adéquatement, comme nous
l’avons déjà remarqué pour ASAX et LogWeaver, le fait que certains champs peuvent
être absents de certains enregistrements ou encore avoir des valeurs multiples.
Logique d’acquisition de
connaissances
Au chapitre précédent, nous avons vu comment il est possible d’utiliser une logique
temporelle future pour exprimer des scénarios d’attaque se déroulant sur plusieurs pa-
quets. L’utilisation d’opérateurs temporels futurs comporte cependant un inconvénient
majeur en ce qui regarde la vérification en ligne : comme nous ne savons jamais ce que
l’avenir nous réserve, nous sommes pratiquement toujours face à une formule en cours
de vérification, ce qui nous amène, au niveau de l’algorithme, à construire des ensembles
de formules partiellement satisfaites et à tenir à jour ces ensembles du mieux que l’on
peut. De plus, à mesure que les scénarios évoluent, nous devons dupliquer les scénarios
(lignes 17 et 20 de l’algorithme de la table 4.8, page 120), utilisant ainsi une quantité
de mémoire sans cesse grandissante. Un autre inconvénient de l’utilisation d’opérateurs
temporels futurs dans un contexte de vérification en ligne réside au niveau de la sé-
mantique donnée à ces opérateurs et de la décidabilité des formules exprimables. Que
penser, par exemple, de la décidabilité de la formule ¦p, qui dit qu’à un certain mo-
ment dans le futur, la proposition p doit être satisfaite ? Si, effectivement, p survient,
on peut affirmer que ¦p est satisfaite, mais tant qu’elle ne survient pas, on ne peut rien
dire. De ce point de vue, la formule ¦p est donc indécidable dans un contexte en ligne
(à moins de travailler sous l’hypothèse que les traces sont finies, comme l’ont fait les
auteurs de [31]). Au chapitre précédent, nous avons contourné le problème en utilisant
l’opérateur ¦t p, permettant de spécifier que p doit survenir avant que le délai t ne se
soit écoulé. Nous avons cependant été obligés de permettre que, au moins à la première
étape, le délai t soit de valeur infinie afin que la vérification puisse toujours continuer.
Par exemple, pour exprimer que il ne doit pas y avoir de p suivi, dans les 2 secondes,
d’un q, on doit utiliser la formule ¬ ¦∞ (p ∧ ¦2 q). Cette permission spéciale laisse un bien
mauvais arrière goût et nous aimerions bien être capables de nous en sortir autrement.
Dans cette section, la présentation que nous donnons de la logique temporelle que
nous proposons d’utiliser pour le langage de signatures se borne au modèle proposition-
nel. Le passage au premier ordre se fera à la section suivante.
Chapitre 4. Logique d’acquisition de connaissances 99
φ ::= p | ¬φ | φ1 ∧ φ2 | [φ1 , φ2 ]
4.1.1 Modèle
4.1.2 Syntaxe
Un cas particulier important est celui de [φ1 , ff], où ff est définie comme étant une
constante propositionnelle qui n’est jamais vraie. La formule [φ1 , ff] doit donc être com-
prise comme φ1 a déjà été vraie. Il est alors possible de spécifier des séquences d’événe-
ments, en utilisant des formules de la forme r ∧ [q ∧ [p, ff], ff]. Cette formule sera vraie
aux événements pour lesquels non-seulement r est vraie, mais aussi q a déjà été vraie,
et avant cela p. Elle spécifie donc la séquence p − q − r.
À la figure 4.1, on voit comment un tel séquencement peut être utilisé pour spécifier
une poignée de main TCP. La formule doit se lire de l’intérieur vers l’extérieur. Le
premier événement qui doit survenir est un paquet syn. Une fois que l’on a vu ce paquet
syn, on doit voir un paquet synack. La poignée de main n’est complétée que lorsque le
dernier acquiescement, ack, est envoyé. La deuxième formule montre comment on peut
utiliser cette poignée de main pour spécifier une session TCP active. Elle dit qu’une
session TCP est active à partir du moment où la poignée de main est complétée, jusqu’au
moment où on voit passer un paquet fin ou un paquet rst. Finalement, on peut utiliser
cette information pour valider l’occurrence d’une attaque donnée survenant au-dessus
de TCP, comme le fait le préprocesseur Flow de Snort. Si la formule Attack représente la
complétion d’une attaque au-dessus de TCP, alors la formule Attack ∧ Session signifie
qu’une poignée de main doit avoir été complétée avant que l’attaque n’ait été observée,
et que la session ne doit pas avoir été close par un paquet rst ou un paquet fin.
4.1.3 Sémantique
Remarquons que la relation de satisfaction n’est définie que pour les différents états
de la trace, et non pour la trace au complet. Informellement, nous pouvons dire qu’une
trace satisfait une formule si et seulement si tous ses états la satisfont. Cependant, étant
Chapitre 4. Logique d’acquisition de connaissances 101
donné le problème auquel nous nous attaquons, le fait de savoir si une trace satisfait ou
non une formule ne présente que peu d’intérêt. Ce qui nous intéresse vraiment, c’est de
savoir quels sont les paquets (états) qui doivent être considérés comme offensifs. C’est
pourquoi la sémantique est définie par rapport à ces derniers plutôt que par rapport à
la trace au complet.
i p(x, y) q(v)
0 p(2, 3) q(3)
p(2, 4) q(0)
1 p(5, 4)
2
... ... ...
4.2 Unification
4.2.1 Modèle
Au chapitre précédent, nous avons remarqué que le modèle consistant en une suite
d’enregistrements est insuffisant pour les besoins de la détection d’intrusions. En fait,
le problème est le suivant : étant donné un prédicat p(x), le modèle d’enregistrements
nous force à considérer qu’il existe une et une seule valeur de x telle que p(x) est vraie,
alors qu’en réalité, il peut n’y en avoir aucune ou plusieurs. C’est pour remédier à ce
problème que nous introduisons la notion d’unification au coeur même du modèle et de
la sémantique.
4.2.2 Syntaxe
maintenant par unification. Par exemple, dans le cas de la formule [p(s, t)∧¬q(t), p(5, t)],
la variable t est unifiée lors de l’événement satisfaisant la sous-formule p(s, t) ∧ ¬q(t).
Pour satisfaire l’autre sous-formule, p(5, t), on doit attendre de voir un événement per-
mettant d’associer la même valeur à la variable t.
La formule τ (x) signifie donc que l’événement courant est survenu au temps x. Alors,
la formule [φ ∧ τ (x1 ), τ (x2 ) ∧ x2 − x1 > δ] est vraie pour une période de δ unités de
temps après que φ ait été vraie. Cette formule étant souvent utilisée, nous l’abrégerons
par
[φ, δ].
La formule q ∧ [p, 3] est donc vraie pour les événements où q est vraie après que p ait
été vraie, et que la période entre les deux n’est pas plus longue que 3 unités de temps.
À la figure 4.5, on montre comment on peut utiliser cette façon de spécifier des
délais pour donner une meilleure spécification d’une poignée de main TCP que celle
de la figure 4.1. Un problème important avec cette spécification est qu’elle permet aux
trois étapes d’être arbitrairement séparées dans le temps. Par exemple, le ack pourrait
survenir une heure après le synack. En pratique, on sait que le système d’exploitation de
la machine ayant émis le synack aurait abandonné l’attente du ack depuis longtemps,
et que le fait de considérer la poignée de main comme étant réussie serait alors une
erreur. De plus, comme nous l’avons déjà dit, si cet acquiescement ne survient toujours
pas après une période de temps raisonnable, il y a lieu de penser que le paquet syn était
falsifié [7] et qu’une attaque de balayage est en cours.
De façon générale, la formule exprimant le fait que si une formule φ1 vient à être
vraie, alors une autre formule φ2 doit être vraie dans un délai de δ unités de temps est
la suivante :
Elle dit que si φ1 a été vraie à plus de δ unités de temps dans le passé, alors, durant la
Chapitre 4. Logique d’acquisition de connaissances 105
tcpFlow(sa,sp,da,dp) :=
ip.sadr(sa) ∧ tcp.sport(sp) ∧ ip.dadr(da) ∧ tcp.dport(dp)
handShake(sa,sp,da,dp) :=
ack ∧ tcpFlow(sa,sp,da,dp) ∧
[synack ∧ tcpFlow(da,dp,sa,sp) ∧
[syn ∧ tcpFlow(sa,sp,da,dp),
3sec],
2sec]
sessionClosing(sa,sp,da,dp) :=
(fin ∨ rst) ∧ (tcpFlow(sa,sp,da,dp) ∨ tcpFlow(da,dp,sa,sp))
session(sa,sp,da,dp) :=
[handShake(sa,sp,da,dp),
sessionClosing(sa,sp,da,dp)]
période de δ unités de temps suivant le moment où φ1 a été vraie, φ2 doit avoir été vraie.
Étant donné que cette formule peut être un peu lourde à utiliser, nous l’abrégerons par
un nouvel opérateur défini, noté
δ
φ1 ,→ φ2 ,
dont la définition est exactement la formule ci-haut. Il est alors possible d’utiliser cet
opérateur pour spécifier, par exemple, que lorsque les deux premières étapes d’une
poignée de main TCP ont été observées, la troisième doit suivre dans un délai de 2
unités de temps. En version simplifiée, cela pourrait s’écrire de la façon suivante :
2
synack ∧ [syn, 3] ,→ ack,
ce qui signifie que si un synack a été observé dans les trois unités de temps suivant un
syn, et que 2 unités de temps se sont écoulées depuis, alors on doit avoir observé un
ack après ce synack.
Chapitre 4. Logique d’acquisition de connaissances 106
4.2.3 Sémantique
Maintenant que nous avons cette banque d’exemples en main, nous pouvons détailler
la sémantique de la version du premier ordre de la logique, présentée à la table 4.4. Cette
sémantique repose d’abord sur la notion d’environnement :
La première ligne de la table 4.4 indique qu’un prédicat p(x1 , . . . , xn ) est satisfait, par
un événement σ(i) donné, sous les environnements e tels que le n-tuple he(x1 ), . . . , e(xn )i
est un élément de l’ensemble associé à p(x1 , . . . , xn ) dans σ(i). La seconde ligne dit que la
négation d’une formule est satisfaite par un état σ(i) sous les environnements qui, bien
que comportant la définition des variables nécessaires, ne satisfont pas la formule don-
née. La troisième ligne dit qu’une conjonction est satisfaite, par un événement σ(i), sous
les environnements sous lesquels σ(i) satisfait chacune des deux propositions conjointes.
Finalement, la quatrième ligne nous dit qu’une formule temporelle est satisfaite, par un
événement σ(i), sous les environnements sous lesquels φ1 a déjà été satisfaite, et que
depuis il a été impossible d’étendre de façon à satisfaire φ2 .
Nous terminons cette section avec la présentation de trois résultats nous donnant une
meilleure intuition de ce que représente l’ensemble var(φ). Le premier nous dit qu’un
environnement doit absolument définir toutes les variables contenues dans var(φ) pour
pouvoir satisfaire φ. Le second nous dit que ces variables sont suffisantes, et que le fait
d’en ajouter plus ne change rien. Le troisième nous fournit un cas particulier du second
dont nous aurons besoin plus loin dans ce chapitre.
Démonstration :
On procède par induction sur φ. Les cas p(x1 , . . . , xn ) et ¬φ1 découlent directement de
la sémantique. Si σ(i) |=σ,e φ1 ∧ φ2 , alors σ(i) |=σ,e φ1 et σ(i) |=σ,e φ2 . Par induction,
var(φ1 ) ⊆ dom(e) et var(φ2 ) ⊆ dom(e), donc, var(φ1 ) ∪ var(φ2 ) ⊆ dom(e), c’est-à-dire,
d’après la définition de var, var(φ1 ∧ φ2 ) ⊆ dom(e). Si σ(i) |=σ,e [φ1 , φ2 ], alors il existe
j < i.σ(j) |=σ,e φ1 . Par l’hypothèse d’induction, dom(e) ⊇ var(φ1 ) = var([φ1 , φ2 ]). ¤
Proposition 4.5 Soit e1 tel que var(φ) = dom(e1 ). Pour tout e2 . e1 , σ(i) |=σ,e1 φ si
et seulement si σ(i) |=σ,e2 φ.
Démonstration :
cas p(x1 , . . . , xn ) :
(⇒)
(⇐)
Chapitre 4. Logique d’acquisition de connaissances 109
cas ¬φ1 :
(⇒)
premièrement,
deuxièmement,
e2 . e1 et var(φ) = dom(e1 )
⇒ h définition de l’ensemble var i
e2 . e1 et var(φ1 ) = dom(e1 )
⇒ h définition de la relation . i
dom(e2 ) ⊆ dom(e1 ) et var(φ) = dom(e1 )
⇒ h théorie des ensembles i
var(φ1 ) ⊆ dom(e2 )
donc,
(⇐)
premièrement
deuxièmement,
var(φ) ⊆ dom(e1 )
⇒ h définition de l’ensemble var, var(φ) = var(φ1 ) i
var(φ1 ) ⊆ dom(e1 )
donc,
cas φ1 ∧ φ2 :
(⇒)
σ(i) |=e1 φ1 ∧ φ2
⇒ h definition de la relation |= i
σ(i) |=σ,e1 φ1 et σ(i) |=σ,e1 φ2
⇒ h direction ⇐ de l’hypothèse d’induction,
en posant e3 = e1 |var(φ1 ) et e4 = e1 |var(φ2 ) i
σ(i) |=σ,e3 φ1 et σ(i) |=σ,e4 φ2
⇒ h direction ⇒ de l’hypothèse d’induction i
σ(i) |=σ,e2 φ1 et σ(i) |=σ,e2 φ2
⇒ h définition de la relation |= i
σ(i) |=σ,e2 φ1 ∧ φ2
Chapitre 4. Logique d’acquisition de connaissances 111
(⇐)
σ(i) |=σ,e2 φ1 ∧ φ2
⇒ h définition de la relation |= i
σ(i) |=σ,e2 φ1 et σ(i) |=σ,e2 φ2
⇒ h direction ⇒ de l’hypothèse d’induction,
en posant e3 = e1 |var(φ1 ) et e4 = e1 |var(φ2 ) i
σ(i) |=σ,e3 φ1 et σ(i) |=σ,e4 φ2
⇒ h direction ⇐ de l’hypothèse d’induction i
σ(i) |=σ,e1 φ1 et σ(i) |=σ,e1 φ2
⇒ h définition de la relation |= i
σ(i) |=σ,e1 φ1 ∧ φ2
cas [φ1 , φ2 ] :
(⇒)
(⇐)
Démonstration :
4.3 Récursivité
4.3.1 Approximants
Comme nous l’avons déjà remarqué, il existe certains types de scénarios d’attaque
ou d’acquisition d’information pour lesquels la notion de répétition est fondamentale.
On n’a qu’à penser, par exemple, aux attaques de balayage, de force brute, ou encore
à certaines attaques de déni de service. Les formules exprimant de tels comportements
répétitifs sont de la forme :
signifiant que φ1 est satisfaite un certain nombre de fois, sans que φ2 ne le soit. Dans le
cas d’une attaque de force brute ayant pour objectif de deviner un mot de passe Telnet,
par exemple, φ1 pourrait représenter une demande de connexion suivie d’un refus, alors
que φ2 pourrait représenter l’écoulement d’un délai.
On sent alors que la formule φ1 dont on a besoin devrait en quelque sorte respecter
la propriété :
φ := [φ1 ∧ φ, φ2 ]
et nous voudrions être capables d’écrire de telles formules. Pour répondre à ce besoin,
nous introduisons la notation montrée à la table 4.5, inspirée des approximants. Nor-
malement, les approximants sont définis pour introduire les points fixes, ceux-ci étant
définis comme des approximants limites, mais il s’avère que dans le cas qui nous occupe,
les points fixes ne sont pas nécessaires et que les approximants suffisent.
Chapitre 4. Logique d’acquisition de connaissances 113
def
ν 0 X.φ = tt
def
ν n X.φ = φ[ν n−1 X.φ/X]
def
ν 3 X.syn ∧ [X, 2] = syn ∧ [ν 2 X.syn ∧ [X, 2], 2]
def
= syn ∧ [syn ∧ [ν 1 X.syn ∧ [X, 2], 2], 2]
def
= syn ∧ [syn ∧ [syn ∧ [tt, 2], 2], 2]
À la figure 4.7, on voit comment on peut utiliser les approximants pour écrire la
formule ν 3 X.syn ∧ [X, 2], représentant 3 demandes de connexion TCP séparées par au
plus 2 unités de temps.
4.3.2 Tableaux
Les approximants ne sont cependant pas seulement utiles pour exprimer des com-
portements répétitifs. Ils peuvent aussi servir à exprimer toutes sortes de formules où la
récursivité peut intervenir. Supposons par exemple, que l’on définisse un nouveau type
tableau, tel que si tab est une variable de type tableau, alors tab[0], tab[1], tab[2],
. . . sont des variables ordinaires, et supposons que l’on veuille définir une formule all-
Different(tab,i) qui s’évalue à vrai si et seulement si les i premiers éléments du
tableau tab sont différents. Ce sera vrai si tab[0] est différent de tous les autres (de 1
à i-1), et si tab[1] est différent de tous ceux qui restent (de 2 à i-1), et ainsi de suite.
signifie alors que les i premiers éléments du tableau tab ont des valeurs deux à deux
différentes. À la figure 4.8, on voit comment interpréter cette formule dans le cas où
i = 3. À la figure 4.9, on voit comment il est possible de définir un balayage de ports en
Chapitre 4. Logique d’acquisition de connaissances 114
def
ν i:[f,f ] X.φ = tt
def
ν i:[d,f ] X.φ = φ[ν i:[d+1,f ] X.φ/X, i/d] (d < f )
def
ν j:[0,3] X.(X ∧ ν k:[j+1,3] Y.(Y ∧ tab[j] 6= tab[k])) =
def
ν j:[1,3] X.(X ∧ ν k:[j+1,3] Y.(Y ∧ tab[j] 6= tab[k]) ∧ ν k:[1,3] Y.(Y ∧ tab[0] 6= tab[k]) =
def
... =
def
ν j:[1,3] X.(X ∧ ν k:[j+1,3] Y.(Y ∧ tab[j] 6= tab[k]) ∧ tab[0] 6= tab[2] ∧ tab[0] 6= tab[1] =
def
... =
tab[1] 6= tab[2] ∧ tab[0] 6= tab[2] ∧ tab[0] 6= tab[1]
4.4 Algorithmes
portScan(sa,da) :=
ν i:[0,5] X.
syn ∧
tcpFlow(sa,sp[i],da,dp[i]) ∧
allDifferent(dp,i) ∧
[X, 2sec]
qu’événement par événement, dans l’ordre où ils surviennent. De plus, la sémantique
de la logique étant purement passée, après chaque traitement d’un événement σ(i), il
est possible de dire si oui ou non σ(i) |= φ. Nul n’est besoin d’attendre à l’événement
suivant, ou encore de tenir à jour une liste de scénarios partiels. En ce sens, l’algorithme
que nous présentons est donc en ligne (voir table 1.9, page 52).
Nous donnons aussi, dans chacun des cas, une démonstration de la validité (com-
plétude et cohérence) de l’algorithme, ainsi qu’une analyse de complexité. Entre autres,
dans le cas propositionnel, nous verrons que l’espace mémoire nécessaire ne dépasse pas
quelques bits. La validité, dans le cas du premier ordre, ne sera obtenue qu’à condition
de faire quelques concessions sur la forme des formules à vérifier. Ces concessions ne
sont cependant pas plus restrictives que celles qui doivent être faites lors de l’utilisation
du langage de programmation Prolog.
fonction verif(φ, s, k, k 0 )
entrées : φ : une formule, s : un état, k : la connaissance passée
sortie : k 0 : la connaissance mise à jour
valeur de retour : un booléen indiquant si s |= φ dans le contexte k
variables locales : v1 , v2 : des booléens
début
selon la forme de φ :
cas p :
retourne p ∈ s
cas ¬φ1 :
retourne ¬verif(φ1 , s, k, k 0 )
cas φ1 ∧ φ2 :
v1 :=verif(φ1 , s, k, k 0 )
v2 :=verif(φ2 , s, k, k 0 )
retourne v1 ∧ v2
cas [φ1 , φ2 ] :
v1 :=verif(φ1 , s, k, k 0 )
v2 :=verif(φ2 , s, k, k 0 )
si v1 alors k 0 := k 0 ∪ {φ}
sinon si v2 alors k 0 := k 0 \ {φ}
retourne φ ∈ k
fin
fonction verification(φ, σ)
entrées : φ : une formule, σ : une trace
sortie : sat, un tableau de booléens tel que sat[i] ssi σ(i) |= φ
variables locales : k,k 0 : des ensembles
début
k 0 := ∅
pour i allant de 0 à |σ|
k := k 0
sat[i] :=verif(φ, σ(i), k, k 0 )
retourner sat
fin
sance est mise à jour juste avant le traitement de chaque événement. Il aurait été
possible d’optimiser en n’ayant qu’un seul ensemble, en s’assurant que les sous-formules
sont traitées dans le bon ordre, mais il était plus commode, du point de vue de l’analyse,
de garder deux ensembles.
Démonstration :
cas ¬φ1 : Par l’hypothèse d’induction sur φ, verif(φ1 , σ(i), k, k 0 ) retourne vrai
si et seulement si σ(i) |= φ1 . On retourne donc vrai si et seulement si σ(i) 6|= φ1 .
cas [φ1 , φ2 ] : Comme k = ∅, on retourne faux, ce qui est correct car comme il n’existe
pas de j < 0, σ(0) 6|= [φ1 , φ2 ]. Pour ce qui est de la mise à jour de k, par définition de
la sémantique, σ(1) |= [φ1 , φ2 ] si et seulement si σ(0) |= φ1 . Par l’hypothèse d’induction
sur φ, [φ1 , φ2 ] est ajouté à k 0 si et seulement si σ(0) |= φ1 .
Pour i > 0, les trois premiers cas d’induction sur φ sont identiques au cas où i = 0.
Pour le cas [φ1 , φ2 ], par l’hypothèse d’induction sur i, la valeur retournée est correcte.
Il reste à montrer que k est mis à jour correctement. Par définition de la sémantique,
σ(i + 1) |= [φ1 , φ2 ] si et seulement si σ(i) |= φ1 ou (σ(i) |= [φ1 , φ2 ] et σ(i) 6|= φ2 ).1
Si σ(i) |= φ1 , alors, par l’hypothèse d’induction sur φ, [φ1 , φ2 ] sera dans k 0 (il y est
ajouté s’il n’y est pas déjà).
Si, au contraire, σ(i) 6|= φ1 , alors par l’hypothèse d’induction sur i, [φ1 , φ2 ] ∈ k 0 si et
seulement si σ(i) |= [φ1 , φ2 ]. Par l’hypothèse d’induction sur φ, [φ1 , φ2 ] est retiré de k 0
si et seulement si σ(i) |= φ2 , ce qui termine la démonstration. ¤
1
La forme du reste de la démonstration est (p ⇔ q ∨ r) ⇔ ((q ⇒ p) ∧ (¬q ⇒ (p ⇔ r))).
Chapitre 4. Logique d’acquisition de connaissances 118
Nous venons donc de démontrer que l’algorithme de la table 4.7 est complet et cohé-
rent. Il est complet car pour chaque i tel que σ(i) |= φ, l’appel de la fonction verif(φ,
σ(i), k, k 0 ) retourne vrai, et il est cohérent car si la valeur vrai est retournée, alors
σ(i) |= φ.
Avant de passer à l’analyse de la complexité, nous avons besoin de définir |φ|, la taille
d’une formule. Intuitivement, la taille d’une formule est son nombre de sous-formules.
def
|p| = 1
def
|¬φ| = 1 + |φ|
def
|φ1 ∧ φ2 | = 1 + |φ1 | + |φ2 |
def
|[φ1 , φ2 ]| = 1 + |φ1 | + |φ2 |
Une propriété intéressante de notre algorithme est que l’espace mémoire qu’il utilise
est constant en fonction de |σ|, et que le temps qu’il met à s’exécuter est linéaire en
fonction de |σ|.
Démonstration :
L’analyse de la taille de k est directe : comme cet ensemble ne contient que des sous-
formules de φ, celui-ci ne peut être plus grand que le nombre de sous-formules. Le nombre
d’appels de la fonction verif, pour chaque événement, est égal à φ (peu importe i). Le
nombre total d’appels est donc |φ||σ|. ¤
L’algorithme de vérification, pour le cas du premier ordre, est présenté à la table 4.8.
Le tableau sat, au lieu de contenir des variables booléennes, contient un ensemble d’en-
vironnements sous lesquels chaque événement de la trace satisfait la formule φ. Dans le
cas général, sat ne contient pas tous ces environnements, mais nous verrons dans cette
section qu’il est possible de définir un ensemble de formules pour lequelles σ(i) |=σ,e φ
ssi il existe e0 ∈ sat[i] tel que e . e0 . De même l’ensemble k ne contient plus des sous-
formules de φ, mais des environnements sous lesquels les sous-formules de φ de la forme
Chapitre 4. Logique d’acquisition de connaissances 119
Formules croissantes
La clause 1 de la définition nous dit qu’un prédicat seul est toujours croissant. La
clause 2 peut paraı̂tre surprenante au premier abord, mais elle prend tout son sens
lorsqu’on la met en lien avec la clause 3. La clause 2 nous dit, entre autres, que toutes
les variables de φ1 doivent être dans X pour que ¬φ1 puisse être considérée comme
croissante, et la 3 nous dit que φ2 doit être croissante au moins pour les variables de
φ1 . Par exemple, les formules p(x) ∧ q(x) et p(x) ∧ ¬q(x) sont croissantes, mais les
formules ¬p(x) ∧ q(x), ¬p(x) ∧ ¬q(x) et ¬(p(x) ∧ q(x)) ne le sont pas. Ces deux clauses,
ensemble, proviennent du fait que nous avons l’intention d’effectuer une unification de
gauche à droite. Comme dans le cas d’une unification faite avec Prolog (du moins avec
l’implémentation de Amzi 7.11), les prédicats situés le plus à gauche servent à construire
des environnements candidats, et les négations situées à droite servent à écarter les
Chapitre 4. Logique d’acquisition de connaissances 120
fonction verif(φ, s, k, E, k 0 )
entrées : φ : une formule, s : un état, k : la connaissance passée,
E : un ensemble d’environnements
sortie : k 0 : la connaissance mise à jour
valeur de retour : un ensemble d’environnements
variables locales : v1 , v2 : des ensembles d’environnements
début
selon la forme de φ :
cas p(x1 , . . . , xn ) :
retourne kp(x1 , . . . , xn )kEs
cas ¬φ1 :
E1 :=verif(φ1 , s, k, E, k 0 )
pour chaque e ∈ E
si ∃e0 ∈ E1 .e0 . e alors E := E − e
retourne E
cas φ1 ∧ φ2 :
E1 :=verif(φ1 , s, k, E, k 0 )
E2 :=verif(φ2 , s, k, E1 , k 0 )
retourne E2
cas [φ1 , φ2 ] :
E2 :=verif(φ2 , s, k, E, k 0 )
pour chaque e tel que hφ, ei ∈ k
si ∃e0 ∈ E2 .e0 . e alors k 0 := k 0 \ {hφ, ei}
E1 :=verif(φ1 , s, k, ∅, k 0 )
pour chaque e ∈ E1
k 0 := k 0 ∪ {hφ, e|var(φ1 ) i}
retourne {e1 ∪ e2 tels que hφ, e1 i ∈ k, e2 ∈ E
et 6 ∃x ∈ dom(e1 ) ∩ dom(e2 ).e1 (x) 6= e2 (x)}
fin
k[φ1 , φ2 ]keσ,0 = ∅
e0 |
k[φ1 , φ2 ]keσ,i (i > 0) = kφ1 keσ,i−1 ∪ {e0 ∈ [φ1 , φ2 ]keσ,i−1 tels que kφ2 kσ,i−1
var(φ1 )
= ∅}
candidats construits. Dans notre cas, les candidats sont cependant aussi construits par
les formules temporelles, qui représentent la base de connaissances. La clause 4 de la
définition dit que pour qu’une formule temporelle soit croissante, la formule d’activation
doit être croissante, mais la formule de désactivation, elle, ne doit être croissante que
par rapport aux variables de la formule d’activation. Encore une fois, ce renforcement
(plus X est petit, plus il est difficile d’être croissant pour X) de la contrainte provient
du fait, que nous avons déjà noté lors de la présentation de la sémantique, que le retrait
d’une connaissance ne doit être effectué que sur la base des connaissances acquises lors
de l’acquisition de cette connaissance.
Sémantique dénotationnelle
Pour nous aider dans notre analyse, nous avons extrait de l’algorithme présenté à la
table 4.8 une sémantique dénotationnelle abstraite, présentée à la table 4.9. Désormais,
c’est à partir de cette sémantique que nous raisonnerons. Étant donné une formule φ,
une trace σ et un environnement e, on montre comment calculer kφkeσ,i , un ensemble
d’environnements e0 tels que e0 étend e juste ce qu’il faut pour pouvoir prétendre à
satisfaire φ dans l’état i de la trace σ. Le juste ce qu’il faut en question est celui de
la proposition 4.5, qui nous dit qu’il suffit que l’environnement soit défini sur var(φ).
Chapitre 4. Logique d’acquisition de connaissances 122
Prenons, par exemple, la première ligne de la table 4.9. Pour un prédicat p(x1 , . . . , xn ),
l’ensemble kp(x1 , . . . , xn )keσ,i est l’ensemble des environnements étendant e en définis-
sant exactement les variables qui lui manquent pour pouvoir satisfaire p(x1 , . . . , xn ). Si
aucune telle extension n’est possible, alors l’ensemble kp(x1 , . . . , xn )keσ,i est ∅. Dans le
cas de la négation, aucune extension n’est nécessaire pour pouvoir prétendre à satisfaire
¬φ1 . Au contraire, dans le cas où il est possible de calculer une extension permettant de
satisfaire φ1 , alors l’ensemble retourné est ∅. Si il n’est pas possible de calculer une telle
extension, alors e peut prétendre à satisfaire ¬φ1 et l’ensemble retourné est seulement
le singleton {e}. Le cas de la conjonction est le plus délicat. Les tentatives d’unifica-
tion sont faites de gauche à droite, sans retour en arrière. L’exploration de tous les
arbres possibles nous semblait un gruge-temps additionnel non-nécessaire du point de
vue de l’expressivité (souvenons-nous que nous travaillons dans un contexte en-ligne).
On commence donc par calculer les extensions pouvant prétendre à satisfaire φ1 , puis
on regarde comment il est possible d’étendre ces extensions de façon à satisfaire φ2 .
Lorsque E est un ensemble d’environnements, l’expression kφkE σ,i est un raccourci pour
e
∪ kφkσ,i . Finalement, on traite le cas de la formule [φ1 , φ2 ] par déploiement récursif.
e∈E
Les extensions prétendant à satisfaire [φ1 , φ2 ] sont celles, premièrement, qui dans l’état
précédent prétendaient à satisfaire φ1 , et deuxièmement, qui, en plus de déjà prétendre
à satisfaire [φ1 , φ2 ] dans l’état précédent, ne prétendaient en aucune façon pouvoir me-
ner à la satisfaction de φ2 (toujours en se restreignant aux variables de φ1 ).
Nous avons maintenant les outils nécessaires pour énoncer tout de suite le théorème
de validité, que nous démontrerons plus loin :
{∅}
kp(x)kσ,i = {{x 7→ 1}}, et
{{x7→1}}
k¬q(x)kσ,i = {{x 7→ 1}}
par contre, si on considère la formule (équivalente) ¬q(x) ∧ p(x), l’unification de gauche
à droite donne
{∅}
k¬q(x)kσ,i = ∅, et
kp(x)k∅σ,i = ∅
Une fois que nous aurons démontré ce thorème, nous pourrons en déduire le corollaire
Chapitre 4. Logique d’acquisition de connaissances 123
suivant, qui est le cas particulier qui nous intéresse. Il nous dit que si la formule à
vérifier respecte les conditions syntaxiques d’une formule croissante, alors il est possible
de calculer, pour chaque événement, un ensemble d’environnements représentant tous
les environnements sous lesquels φ est satisfaite.
Corollaire 4.14 Si φ est croissante, alors pour tout environnement e, σ(i) |=σ,e si et
{∅}
seulement si il existe e0 ∈ kφkσ,i tel que e . e0 .
Les deux lemmes dont nous avons besoin pour la démonstration du théorème servent
à justifier la définition que nous avons donné des formules croissantes. Le lemme 4.11
nous dit que la vérification d’une formule croissante par rapport à un environnement
donné nous donne un ensemble d’environnements dont le domaine contient les variables
de cette formule, et le lemme 4.12 nous dit que ces environnements sont des extensions
du premier environnement.
Lemme 4.11 Si φ est croissante pour dom(e) et e0 ∈ kφkeσ,i , alors dom(e0 ) = dom(e) ∪
var(φ).
Démonstration :
Cas ¬φ1 : Si ¬φ1 est croissante pour dom(e), alors var(φ1 ) ⊆ dom(e), et par défi-
nition de la sémantique dénotationnelle, e0 ∈ k¬φ1 keσ,i implique e0 = e. Donc, dom(e0 ) =
dom(e) = dom(e) ∪ var(φ1 ) = dom(e) ∪ var(¬φ1 ).
00
Cas φ1 ∧ φ2 : Soit e00 ∈ kφ1 keσ,i tel que e0 ∈ kφ2 keσ,i (un tel e00 existe par définition de la
sémantique dénotationnelle). Comme φ1 est croissante pour dom(e), on peut appliquer
l’hypothèse d’induction pour dire que dom(e00 ) = dom(e) ∪ var(φ1 ). Alors, par défini-
tion d’une formule croissante, φ2 est croissante pour dom(e00 ), et on peut appliquer une
seconde fois l’hypothèse d’induction sur φ pour dire que dom(e0 ) = dom(e00 ) ∪ var(φ2 ) =
dom(e) ∪ var(φ1 ) ∪ var(φ2 ) = dom(e) ∪ var(φ1 ∧ φ2 ).
Pour le cas i > 0, les trois premiers cas d’induction sur φ sont identiques. Pour le
cas [φ1 , φ2 ], soit que e0 ∈ kφ1 keσ,i−1 , soit que e0 ∈ k[φ1 , φ2 ]keσ,i−1 . Dans le premier cas,
c’est l’hypothèse d’induction sur φ qui permet de conclure, et dans le second cas, c’est
celle sur i qui finit le travail, complétant ainsi la démonstration du lemme. ¤
Démonstration :
Pour le cas i > 0, les trois premiers cas d’induction sur φ sont identiques. Pour le
cas [φ1 , φ2 ], soit que e0 ∈ kφ1 keσ,i−1 , soit que e0 ∈ k[φ1 , φ2 ]keσ,i−1 . Dans le premier cas,
c’est l’hypothèse d’induction sur φ qui permet de conclure, et dans le second cas, c’est
celle sur i qui finit le travail, complétant ainsi la démonstration du lemme. ¤
Nous avons maintenant tous les outils nécessaires pour passer à la démonstration
du théorème de validité.
Démonstration du théorème:
Cas p(x1 , . . . , xn ) :
(⇒)
(⇐)
e1 . e2 et e2 ∈ kp(x1 , . . . , xn )keσ,i
⇒ h définition de la sémantique dénotationnelle i
e1 . e2 et {x1 , . . . , xn } ⊆ dom(e2 ) et p(e2 (x1 ), . . . , e2 (xn )) ∈ σ(i)
⇒ h définition de la relation . i
{x1 , . . . , xn } ⊆ dom(e2 ) ⊆ dom(e1 ) et e1 (xl ) = e2 (xl ) pour l = 1 . . . n
et p(e2 (x1 ), . . . , e2 (xn )) ∈ σ(i)
⇒ h théorie des ensembles i
p(e1 (x1 ), . . . , e1 (xn )) ∈ σ(i)
⇒ h définition de la relation |= i
σ(i) |=σ,e1 p(x1 , . . . , xn )
Cas ¬φ1 :
(⇒)
(⇐)
Cas φ1 ∧ φ2 :
(⇒)
σ(i) |=σ,e1 φ1 ∧ φ2
⇒ h Comme φ1 est croissante pour dom(e), σ(i) |=σ,e1 φ1 , et e1 . e, on
peut appliquer la direction ⇒ de l’hypothèse d’induction sur φ. i
∃e1 ∈ kφ1 keσ,i .e1 . e01
0
⇒ h Comme σ(i) |=σ,e1 φ2 , que e1 . e01 , et que par le lemme 4.11, φ2 est
croissante pour dom(e01 ), on peut appliquer la direction ⇒ de l’hypo-
thèse d’induction sur φ. i
e0
∃e2 ∈ kφ2 kσ,i1 .e1 . e2
⇒ h définition de la sémantique dénotationnelle i
∃e2 ∈ kφ1 ∧ φ2 keσ,i .e1 . e2
Chapitre 4. Logique d’acquisition de connaissances 127
(⇐)
e1 . e2 et e2 ∈ kφ1 ∧ φ2 keσ,i
⇒ h définition de la sémantique dénotationnelle i
e0
e1 . e2 et ∃e01 ∈ kφ1 keσ,i .e2 ∈ kφ2 kσ,i1
⇒ h Comme φ1 ∧ φ2 est croissante pour dom(e), par définition, φ1 est
croissante pour dom(e). On peut donc appliquer la direction ⇐ de
l’hypothèse d’induction. i
e0
e1 . e2 et σ(i) |=σ,e01 φ1 et e2 ∈ kφ2 kσ,i1
⇒ h Par le lemme 4.11, φ2 est croissante sous dom(e01 ). On peut donc
appliquer encore une fois la direction ⇐ de l’hypothèse d’induction. i
e1 . e2 et σ(i) |=σ,e01 φ1 et σ(i) |=σ,e2 φ2
⇒ h Par le lemme 4.12, e2 . e01 , on peut donc appliquer le corollaire 4.6. i
e1 . e2 et σ(i) |=σ,e2 φ1 et σ(i) |=σ,e2 φ2
⇒ h définition de la relation |= i
e1 . e2 et σ(i) |=σ,e2 φ1 ∧ φ2
⇒ h corollaire 4.6 i
σ(i) |=σ,e1 φ1 ∧ φ2
Cas [φ1 , φ2 ] :
Cas i > 0 : Les trois premiers cas de l’induction sur φ sont identiques au cas où i = 0.
Il reste à montrer le cas où φ = [φ1 , φ2 ].
(⇒) Si σ(i) |=σ,e1 [φ1 , φ2 ], alors, par définition de la sémantique opérationnelle, soit
que σ(i − 1) |=σ,e1 φ1 , soit que σ(i − 1) |=σ,e1 [φ1 , φ2 ] et 6 ∃e01 . e1 |var(φ1 ) .σ(i − 1) |=σ,e01 φ2 .
Premier cas :
σ(i − 1) |=σ,e1 φ1
⇒ h Comme φ1 est croissante par rapport à dom(e1 ), on peut appliquer
la direction ⇒ de l’hypothèse d’induction sur φ. i
∃e2 ∈ kφ1 keσ,i−1 .e1 . e2
⇒ h définition de la sémantique dénotationnelle i
Chapitre 4. Logique d’acquisition de connaissances 128
Second cas :
(⇐) Si e2 ∈ k[φ1 , φ2 ]keσ,i , alors e2 ∈ kφ1 keσ,i−1 ou e2 ∈ [φ1 , φ2 ]keσ,i−1 et est tel que
e2 |
var(φ1 )
kφ2 kσ,i−1 = ∅.
Premier cas :
e1 . e2 et e2 ∈ kφ1 keσ,i−1
⇒ h Par définition d’une formule croissante, φ1 est croissante pour dom(e),
on peut donc appliquer la direction ⇐ de l’hypothèse d’induction sur
φ. i
σ(i − 1) |=σ,e1 φ1
⇒ h i − 1 < i et @k.i − 1 < k < i i
∃j < i.σ(j) |=σ,e1 φ1 et @j < k < i, e01 . e1 |var(φ1 ) .σ(k) |=σ,e01 φ2
⇒ h définition de la relation |= i
σ(i) |=σ,e1 [φ1 , φ2 ]
Second cas :
e2 |
e1 . e2 et e2 ∈ k[φ1 , φ2 ]keσ,i−1 et kφ2 kσ,i−1
var(φ1 )
=∅
⇒ h direction ⇒ de l’hypothèse d’induction sur i (deux fois) i
e1 . e2 et σ, i − 1 |=σ,e1 [φ1 , φ2 ] et @e02 . e2 |var(φ1 ) .σ(i − 1) |=σ,e02 φ2
⇒ h e1 . e2 . e2 |var(φ1 ) et transitivité de . i
Chapitre 4. Logique d’acquisition de connaissances 129
Les deux cas étant démontrés, on peut utiliser le corollaire 4.6 pour conclure que
σ(i) |=σ,e1 [φ1 , φ2 ]. Ceci complète le dernier cas d’induction et par le fait même la
démonstration du théorème. ¤
4.5 Conclusion
Dans ce chapitre, nous avons vu comment l’utilisation d’une logique passée permet
de modéliser l’acquisition passive d’information dans un paradigme qui soit complète-
ment déclaratif. Le problème, avec une logique future, est que l’on ne peut pas expri-
mer la condition si tel comportement a déjà été observé. Au chapitre précédent, nous
avons résolu ce problème en permettant de gérer, au niveau de l’implantation, une base
de connaissances à laquelle il était possible de dynamiquement ajouter et retirer des
connaissances. Ces ajouts et ces retraits, dans le contexte d’une logique passée, sont
modélisés par l’utilisation de l’opérateur [φ1 , φ2 ]. De plus, nous avons vu que cet opéra-
teur permet aussi bien de modéliser les scénarios à reconnaı̂tre que ceux d’une logique
future, les rendant ainsi superflus. Nous avons donc vu que l’opérateur [φ1 , φ2 ] est suf-
fisant pour modéliser à la fois l’acquisition de connaissances et les scénarios d’attaque
s’étendant sur plusieurs paquets.
Nous avons aussi vu comment un modèle et une sémantique basés sur un concept
d’unification permettent de mieux modéliser le fait que différents paquets contiennent
différentes entêtes de protocoles, que certains champs puissent être optionnels, ou encore
être associés à plusieurs valeurs différentes. De plus, l’utilisation du concept d’unifica-
tion, lié à l’ajout d’opérateurs passés, permet d’oublier une fois pour toutes le concept de
formule en cours de reconnaissance, qui non-seulement était présent dans l’approche que
nous avons proposée utilisant une logique future, mais aussi dans LogWeaver, Eagle et
Chronicles. De plus, les autres approches, utilisant d’autres formalismes pour les scéna-
rios, tels que les systèmes de transition avec STATL, ou les réseaux de Petri avec IDIOT,
ont eux aussi cette notion de scénario en cours de reconnaissance. En fusionnant les
concepts de scénario et de connaissance, tout devient de la connaissance acquise. On
n’a plus besoin de tenir à jour un ensemble de formules partiellement satisfaites, mais
simplement une base de connaissances. Nous croyons qu’une implémentation judicieuse
des algorithmes que nous avons présentés devrait tenir compte de la nature globale de
Chapitre 4. Logique d’acquisition de connaissances 130
la connaissance acquise pour offrir une factorisation efficace à la fois du code exécutable
et des spécifications.
L’analyse de ce dernier algorithme n’a pas encore été faite. L’analyse de la com-
plexité en espace est le cas le moins compliqué. En fait, on peut tout de suite dire
qu’elle est de l’ordre de |φ||var(φ)||V|, où V est le domaine des valeurs unifiables à une
variable. Le raisonnement à effectuer pour arriver à ce résultat est le même que dans le
cas propositionnel. L’analyse en temps ne s’avère cependant pas aussi simple. Le pro-
blème est qu’il nous semble difficile, étant donnée la façon dont l’algorithme est décrit,
de s’arrêter sur le choix d’une instruction élémentaire. Pour le cas propositionnel, nous
avons choisi le nombre d’appels à la fonction verif. Étant donné que les opérations
effectuées à l’intérieur de chacun de ces appels étaient facilement associables à des opé-
rations pouvant s’effectuer en temps constant, cette mesure nous semblait raisonnable.
Dans le cas du premier ordre, les opérations effectuées à l’intérieur de chaque appel sont
des opérations d’unification, et nous n’avons donné que la spécification de celles-ci, sans
entrer dans les détails. Même si la taille des événements (le nombre de tuples associés à
chaque prédicat élémentaire) était toujours la même, ou du moins toujours bornée par
une certaine constante (ce qui est tout de même une hypothèse raisonnable) le problème
se poserait toujours. En fait, le problème se situe au niveau de la base de connaissances.
Il est clair qu’à mesure que le temps avance, la quantité d’information accumulée avance
elle aussi et que les tentatives d’unification avec la base de connaissances prendront de
Chapitre 4. Logique d’acquisition de connaissances 131
plus en plus de temps. D’un autre côté, en supposant borné le domaine des valeurs
auxquelles les variables sont unifiables, il est raisonnable de penser que la taille de la
base de connaissances viendra à se stabiliser. Comme nous l’avons déjà dit, d’un point
de vue théorique, le même raisonnement que pour le cas propositionnel nous amène
à dire que la quantité d’espace mémoire utilisé est bornée. Cependant, d’un point de
vue pratique, une base de connaissance pleine à craquer, bien que ne demandant pas un
temps d’exécution grandissant, peut tout de même demander un temps qui soit inaccep-
tablement trop grand. Autrement dit, dire que l’algorithme s’exécute en temps O(|σ|)
est théoriquement correct, mais nous savons bien que la constante cachée peut être, en
pire cas, d’une taille assomante. D’un autre côté, avec des spécifications bien choisies, il
est fort possible que le pire cas n’arrive pas si souvent que ça. Pour ces raisons, et aussi
à cause du fait que nous ne disposons pas d’une opération élémentaire à utiliser comme
mesure, l’analyse de l’algorithme dans le cas du premier ordre n’a pas été faite.
Chapitre 5
Travaux futurs
Dans ce travail, nous avons développé un nouveau langage de signatures pour les
systèmes de détection d’intrusions. Le focus a principalement été mis sur l’expressivité
de ce langage, de même que sur les aspects algorithmiques reliés à son implémentation.
L’utilisation d’un paradigme basé sur une logique temporelle nous a permis, dans le
cadre du travail effectué, de développer un langage qui soit purement déclaratif, au sens
où l’utilisateur n’a pas à se soucier de la façon dont l’algorithme de vérification s’exécute
pour spécifier ses signatures. L’utilisation d’une logique temporelle présente cependant
d’autres avantages, et ouvre la voie pour nos futurs travaux de recherche. Dans cette
section, nous donnons quelques exemples de directions de recherche que nous prévoyons
explorer dans les mois à venir.
5.1 Satisfiabilité
Un des avantages reliés à l’utilisation d’un paradigme basé sur une logique est de
permettre la vérification de la cohérence de la base de règles. La sémantique du langage
développé s’applique aux états d’une trace, et il est sous-entendu qu’une trace σ satisfait
une politique φ donnée si et seulement si pour chaque événement σ(i), on a σ(i) |=σ φ.
Il existe cependant des formules qui ne sont satisfaites par aucune trace. De telles
formules sont appelées contradictions. À la figure 5.1, on trouve quatre exemples de
contradictions. La première est une contradiction au niveau propositionnel, c’est-à-
dire que non-seulement n’est-elle pas satisfiable par aucune trace, mais elle ne l’est
pas non-plus pour aucun état d’aucune trace. La deuxième formule est un exemple de
contradiction qui se situe au niveau temporel. Elle est satisfaite par les états qui ont été
Chapitre 5. Travaux futurs 133
p ∧ ¬p
[p, faux]
[vrai, p]
p → [q, faux] ∧ q → [p, faux] ∧ (p ∨ q)
précédés par un état où p était vrai. Certainement, cette formule n’est jamais satisfaite
par le premier état d’une trace. Conséquemment, elle n’est satisfiable par aucune trace
et il s’agit d’une contradiction. La troisième est une contradiction pour la même raison.
Bien que la formule vrai soit satisfaite par tous les états, il n’existe pas d’état précédent
le premier état d’une trace. En particulier, aucun état précédent σ(0) ne satisfait vrai.
La quatrième formule est une contradiction au niveau des contraintes temporelles. Elle
dit que q doit survenir avant p, et que p doit survenir avant q. Sans la partie p ∨ q, cette
formule pourrait être satisfaite par les traces où p et q ne surviennent jamais.
Dans un même ordre d’idées, les formules qui sont satisfaites par tous les états de
toutes les traces sont appelées des tautologies. À priori, les tautologies et les contradic-
tions ne sont pas des politiques de sécurité pertinentes, puisque l’on sait à l’avance si
elles seront satisfaites. Il serait donc utile de pouvoir les détecter à l’avance. Une telle
détection constituerait une première étape en vue de la validation d’une politique. À la
table 5.1, on peut voir un système de preuves à base de tableaux permettant de vérifier
si un état d’une trace donnée satisfait une formule. Les trois premières règles d’infé-
rence sont celles du calcul propositionnel, et les trois dernières concernent l’opérateur
temporel [φ1 , φ2 ]. Ce système de preuves ne permet cependant pas de vérifier si une
formule donnée est une tautologie, une contradiction, ou ni l’une ni l’autre. Une avenue
possible pour la détection des tautologies et des contradictions pourrait être d’étendre
ou de modifier ce système de preuves de façon à tenir compte des noeuds déjà traversés
dans l’arbre de preuves.
Un peu plus loin dans ce chapitre, nous proposerons cependant une autre façon de
détecter les tautologies et les contradictions qui utilise seulement les trois premières
règles de ce système de preuves. Juste avant, nous verrons comment il pourrait être
possible d’améliorer la sémantique du langage que nous avons développé de façon à en
faire non-pas un simple langage de détection, mais aussi un langage de contrôle. Nous
utilisons les terme contrôle plutôt que réaction pour mettre en évidence le fait que la
seule réaction permise par l’extension que nous proposons est de bloquer les actions
malicieuses. L’extension ne permet pas, telle quelle, d’engendrer des actions.
Chapitre 5. Travaux futurs 134
⊥
p ∈ σ(i) φ, σ(i) φ1 , σ(i) φ2 , σ(i)
p, σ(i) ¬φ, σ(i) φ1 ∧ φ2 , σ(i)
Le bien-fondé d’une telle méthode de travail repose cependant sur l’hypothèse que
les signatures représentent bel et bien les attaques, et non l’effet de celles-ci. En effet,
une tendance en détection d’intrusions est d’écrire des signatures qui ne lanceront des
alarmes que si l’effet de l’attaque est détecté, et non l’attaque elle-même. Cette tech-
nique est principalement motivée par le besoin de diminuer le nombre de faux positifs.
Prenons, par exemple, la propagation d’un vers dans un réseau local. Supposons qu’un
utilisateur A reçoive par un courrier électronique envoyé par son meilleur ami un petit
jeu rigolo permettant la fois de punir son politicien préféré à l’aide d’un tue-mouche et
d’infecter son ordinateur d’un virus qui essayera alors de se propager aux stations de
travail avoisinantes. Le virus utilise alors chaque machine infectée comme relais pour
en infecter d’autres, et ainsi de suite. Supposons, de plus, que ce virus exploite une
vulnérabilité du système d’exploitation Windows contre laquelle 117 stations des 120
du réseau local sont prémunies. Une signature concernant le comportement offensif gé-
nérerait alors des alarmes pour les 120 machines attaquées, alors qu’une signature pour
l’effet de l’attaque ne génère des alarmes que pour les trois machines qui sont réellement
affectées. Le responsable de la sécurité du réseau peut alors réagir plus efficacement car
il sait exactement quelles sont les machines infectées.
Dans le cas du langage que nous avons développé, certaines des signatures que nous
Chapitre 5. Travaux futurs 135
V
Φ ::= (attack|symptom)φj
avons proposées, notamment celles où on exprime l’écoulement d’un délai, l’événement
permettant de détecter l’attaque n’est pas du tout lié à celui correspondant au compor-
2
tement offensif en tant que tel. Prenons, par exemple, la formule synack∧[syn, 3] ,→ ack
dont nous avons discuté à la page 105. L’événement permettant la détection de l’attaque
est le premier suivant l’écoulement du délai, et cet événement ne doit en aucun cas être
considéré comme offensif. Il n’est qu’un symptôme permettant la détection de l’attaque,
et ne fait pas partie l’attaque elle-même. C’est pourquoi un système de détection d’in-
trusions muni de fonctionnalités de contrôle bloquant cet événement commettrait alors
une erreur.
Une avenue possible pour s’attaquer à ce problème pourrait être de typer les formules
constituant une politique de sécurité de façon à différencier les symptômes des attaques.
Dans le cas du langage LAMBDA [29], Cuppens et al. ont proposé de partitionner
les actions en actions malicieuses et suspicieuses. Les actions suspicieuses ne sont pas
offensives en tant que telles, mais peuvent contribuer au succès d’une attaque. Les
actions malicieuses, elles, entrent directement en conflit avec la politique de sécurité.
L’approche que nous proposons ici a en commun avec celle de Cuppens le fait qu’elle
permet de mieux décider quelles sont les actions qui doivent être bloquées.
avant de continuer la détection à partir de l’événement suivant. Dans le cas où aucune
attaque n’a été repérée, l’événement courant est conservé dans la trace et on continue la
détection à partir de l’événement suivant. L’action de retrait d’un événement de la trace
modélise la capacité de contrôle du système de détection d’intrusions. Si l’événement
est retiré de la trace, alors son effet ne doit pas être pris en compte dans la poursuite
de la détection.
Au chapitre d’introduction de ce mémoire, nous avons dit que le travail que nous
allions effectuer s’inscrivait dans un contexte de validation, au sens où le problème
auquel nous nous attaquions concernait la vérification de certaines traces d’exécution
d’un système donné, et non celle de toutes ses traces d’exécution possibles. Cependant,
lorsque l’on commence à donner la possibilité au programme de vérification de modifier
le modèle en cours d’exécution, il ne s’agit plus d’un problème de validation tant que
d’un problème de synthèse. Un tel programme de vérification, que l’on appellera dès
lors contrôleur, peut être vu comme partie intégrante du système en cours de validation,
Chapitre 5. Travaux futurs 137
Problème 5.1 Étant donné un programme P , une propriété φ, et une loi de composi-
tion de programmes ×, générer un programme C tel que C × P |= φ.
Ce problème a déjà été largement exploré dans la littérature [79, 80, 81, 82], et
son niveau de difficulté varie dépendemment de l’algèbre utilisée pour représenter P
et C et de la logique utilisée pour exprimer φ. Or, il y a fortement lieu de penser que
lorsque φ est exprimée avec le cas propositionnel de la logique que nous avons utilisée
pour développer notre langage, et que l’algèbre utilisée est celle des automates finis, la
résolution de ce problème dépend uniquement de φ.
A ::= 0 | a.A1 | A1 + A2 | A1 × A2
P
(0) = {stop}
P P
(a.A1 ) = a. (A1 )
P P P
(A + A2 ) = (A1 ) ∪ (A1 )
P 1 P P
(A1 × A2 ) = (A1 ) ∩ (A2 )
P P
avec a. (A) = {a.σ | σ ∈ (A)}
Avant d’aller plus loin dans l’annonce des conjectures auxquelles nous comptons
nous attaquer, définissons précisément de quels automates nous parlons. La syntaxe de
l’algèbre des automates est présentée à la table 5.3. Un automate est soit l’automate ne
pouvant faire aucune action (0), soit un automate pouvant faire une action a avant de
devenir un autre automate (a.A1 ), soit un automate pouvant arbitrairement adopter le
comportement de deux autres automates (A1 + A2 ), soit le produit synchrone de deux
automates (A1 ×A2 ). Nous n’avons pas besoin, pour la portée de notre propos, de définir
le parallélisme entre deux automates ni de s’attarder à définir une relation d’équiva-
lence. C’est pourquoi nous ne parlons pas ici d’algèbre de processus, mais simplement
d’automates. Aussi, comme tout notre intérêt tourne autour des traces d’exécution de
ces automates, la sémantique que nous leur accordons est directement exprimée en fonc-
tion de ces traces. L’automate 0 ne peut faire que l’action stop et s’arrêter. L’automate
Chapitre 5. Travaux futurs 138
Fig. 5.4 – Contrôleur pour l’automate TCP relativement à ¬(rst ∧ [synack, ack]).
a.A1 peut faire l’action a, suivie de toutes les traces que peut effectuer l’automate A1 .
L’automate A1 + A2 peut faire toutes les traces de A1 , de même que toutes celles de
A2 . Finalement, l’automate A1 × A2 peut faire les traces que A1 et A2 peuvent faire.
À la figure 5.3, on voit comment on peut utiliser cette algèbre pour représenter une
version très simplifiée de l’automate TCP. Cet automate peut compléter une poignée de
main (syn.synack.ack) pour devenir l’automate représentant une session. Une session
peut se terminer en posant une action f in avant de retourner à l’état initial. Aussi, peu
importe dans quel état il se trouve, l’action rst retourne à l’état initial. On remarque
que cet automate est déterministe, mais pas complètement défini. Il aurait aussi bien
pu ne pas être déterministe.
Maintenant que nous avons défini notre algèbre, nous sommes en mesure de définir
ce qu’est, dans notre cas, un contrôleur pour un automate relativement à une formule φ :
Supposons, par exemple, que l’on veuille contrôler la propriété : l’action rst ne doit
pas être posée entre un synack et un ack. Dans notre logique, cette propriété se formule
Chapitre 5. Travaux futurs 139
Act := 2P
s0 := ∅
a
s1 → s2 ssi ϕ(s1 ) ∧ ϕ(a) ` φ
et σ(i) |= ϕ(s1 ) ∧ ϕ(a) implique σ(i + 1) |= ϕ(s2 )
V V
ϕ(s) := φi ∧ ¬φi
φi ∈s1 φi 6∈s1
V V
ϕ(a) := p∧ ¬p
p∈a p6∈a
de la façon suivante : ¬(rst ∧ [synack, ack]). En effet, cet automate engendre toutes
les mêmes traces que celui de l’automate TCP, sauf celles où un rst survient entre un
synack et un ack.
Nous avons construit cet automate de façon ad-hoc, sans vraiment suivre de méthode
particulière. Rien ne nous pousse à croire que cette solution était l’unique solution. En
particulier, si le contrôleur que nous avons défini avait été capable d’engendrer des traces
que l’automate TCP n’était pas capable d’engendrer, celle-ci auraient été supprimées
par l’opération de produit. Donc, si on était capable, étant donnée une formule φ, de
donner un automate engendrant toutes les traces σ telles que σ |= φ, le problème de la
synthèse du contrôleur tel que nous l’avons énoncé serait résolu une fois pour toutes.
Ceci nous amène à définir la notion de contrôleur universel :
Définition 5.3 Un automate A est un contrôleur universel pour une formule φ si pour
P
toute trace σ, σ ∈ (A) ⇔ σ |= φ. Si A est un contrôleur universel pour φ, on dit que
A implémente φ.
Comme nous avons déjà donné un algorithme de vérification pour notre logique, on
devrait s’attendre à pouvoir transformer cet algorithme en automate et ainsi de pouvoir
définir un contrôleur universel pour n’importe quelle formule φ. Cette transformation
donne exactement l’automate défini à la table 5.5. L’ensemble des états correspond
exactement à l’ensemble des valeurs pouvant être prises par l’ensemble k de l’algorithme
Chapitre 5. Travaux futurs 140
L(C) = ∅
L(C1 ) = {[synack, ack]}
de vérification présenté à la table 4.7 (page 116). L’ensemble des actions pouvant être
posées correspond à l’ensemble des événements (une action ou un événement est un
ensemble de constantes propositionnelles). Une action a effectuée à partir d’un état
s1 mène à un état s2 si le fait de poser cette action, dans l’état s1 , ne mène pas à
une violation de la propriété φ et si, une fois l’action posée, la connaissance accumulée
correspond à celle représentée dans l’état s2 . Comme l’évolution de la connaissance est
définie de façon unique, il ne peut y avoir qu’un seul état s2 défini pour s1 et a. De plus,
comme toute l’information pertinente concernant les actions posées jusqu’à maintenant
est accumulée dans l’état s1 , les règles d’inférences du calcul propositionnel (les trois
premières de la table 5.1) suffisent pour décider si le fait de poser l’action a viole le
propriété φ. Finalement, si le fait de poser l’action a mène à une violation de φ, l’état
s1 n’a pas de transition sortante pour a.
Nous énonçons maintenant deux résultats que nous contons démontrer dans les mois
à suivre. Le premier dit que l’automate défini à la table 5.5 implémente bien la propriété
φ pour laquelle il est défini, et le second nous dit comment on peut utiliser cet automate
pour trouver des tautologies et des contradictions.
Conjecture 5.4 Pour toute formule φ, l’automate défini à la table 5.5 implémente φ.
Conjecture 5.5 Une formule φ est une contradiction (n’est pas satisfiable) si l’auto-
mate défini à la table 5.5 n’engendre pas de traces, et elle est une tautologie si il est
complètement défini.
Chapitre 5. Travaux futurs 141
Une fois ces résultats démontrés, il faudra voir comment on peut adapter ces idées
à la logique du premier ordre de la logique que nous avons utilisée pour notre langage.
Aussi, il faudra voir comment on peut incorporer les concepts de symptôme et d’attaque
au problème de synthèse du contrôleur. Certainement, l’algèbre des automates, avec son
produit complètement synchrone, ne suffira plus. Il faudra voir comment on peut définir
une loi de composition permettant un contrôle partiel, au sens où la violation d’un
certain sous-ensemble de la politique ne bloque pas complètement le système contrôlé,
mais seulement certaines de ses actions. Certains travaux ont déjà été effectués dans
cette direction dans [83].
Conclusion
Dans ce travail, nous avons établi les bases d’un nouveau langage de signatures
d’attaques pour un système de détection d’intrusions et d’acquisition passive d’infor-
mation. Nous avons commencé par effectuer un état de l’art des langages de signatures
déjà existants. Nous avons donné un nouveau système de classification des langages
de signatures, que nous avons répartis en cinq catégories, en fonction des paradigmes
utilisés pour définir les langages. Nous avons aussi, suite à cette étude, dressé une liste
de dix propriétés identifiées comme souhaitables pour le langage de signatures d’un
système de détection d’intrusions.
Une des cinq catégories selon lesquelles nous avons classé les langages des systèmes à
base de signatures est celle de ceux basés sur des logiques temporelles. Comme cette voie
nous semblait être la plus prometteuse, nous avons ensuite approfondi nos connaissances
en logiques temporelles. Nous avons trouvé que plusieurs logiques avaient été dévelop-
pées pour répondre à plusieurs besoins. Les différences les plus importantes entre ces
logiques, étant donnés nos besoins, sont la direction des opérateurs temporels (passés
ou futur), et la capacité d’exprimer des contraintes de temps-réel.
Une fois réalisée l’étude des logiques, nous avons défini et implémenté un langage de
signatures ayant la capacité d’acquérir passivement de l’information dans le principal
but d’effectuer une détection d’intrusions plus précise. Le système résultant présentait
une architecture à deux niveaux, avec une séparation claire entre les scénarios et la
connaissance acquise. La partie du langage servant à représenter les scénarios pouvait
être vus comme un sous-ensemble d’une logique temporelle future, à laquelle nous avons
ajouté la capacité de référer à certains événements passés. Bien que le système mis
en oeuvre ne comportait pas encore toutes les caractéristiques recherchées, le langage
développé a permis de clarifier nos idées quand à la façon dont devrait se formaliser
l’acquisition passive d’information.
Nous avons alors défini un second langage, cette fois basé sur une logique temporelle
passée. Il s’est avéré qu’en plus que d’être beaucoup mieux adapté à la surveillance de
Conclusion 143
Finalement, nous avons vu comment le choix d’un langage basé sur une logique
temporelle ouvrait la voie à d’autres travaux de nature théorique orientés vers l’appli-
cation. Entre autres, ce choix permet de définir un algorithme permettant de vérifier
la cohérence de la base de signatures, il permet aussi d’étendre le langage de façon à
permettre au système de détection d’intrusions de bloquer uniquement les actions ma-
licieuses, et il permet finalement de générer automatiquement un contrôleur pour une
politique donnée.
[1] C. Green and M. Roesch, “Snort users manual 2.1.0 - the snort project,” Dec. 2003.
[2] C. Michel and L. Mé, “Adele : an attack description language for knowledge-based
intrusion detection,” in Proceedings of the 16th International Conference on
Information Security (IFIP/SEC 2001), June 2001, pp. 353–365. [Online].
Available : citeseer.ist.psu.edu/michel01adele.html
[3] D. Spinellis and D. Gritzalis, “Panoptis : Intrusion detec-
tion using a domain-specific language,” Journal of Compu-
ter Security, vol. 10, pp. 159–176, 2002. [Online]. Available :
http://www.dmst.aueb.gr/dds/pubs/jrnl/2002-JCS-Panoptis/html/paper.html
[4] V. Jacobson, C. Leres, and S. McCanne, “tcpdump man page,”
http://www.tcpdump.org/tcpdump man.html.
[5] ——, “libpcap man page,” http://www.tcpdump.org/pcap3 man.html, Nov. 2003.
[6] C. Giovanni, “Fun with packets : Designing a stick,”
http://packetstormsecurity.nl/distributed/stick.htm.
[7] Fyodor,“The art of port scanning,”http://www.insecure.org/nmap/nmap doc.html,
1997.
[8] R. Deraison, R. Gula, and T. Hayton, “Passive vulnerability scanning- an intro-
duction to nevo,” http://www.tenablesecurity.com/papers.html, august 2003.
[9] R. Gula, “Correlating ids alerts with vulnerability information,”
http://www.tenablesecurity.com/papers.html, december 2002.
[10] H. D. B. Morin, L. Mé and M.Ducassé, “M2d2 : A formal data model for ids
alert correlation,” in 5th International Conference on Recent Advances in Intrusion
Detection (RAID 2002), ser. LNCS, vol. 2516. Zurich : Springer, October 2002,
pp. 177–198.
[11] H. Anderson,“Introduction to nessus,”http://www.securityfocus.com/infocus/1741,
october 2003.
[12] ——, “Nessus, part 2 : Scanning,” http://www.securityfocus.com/infocus/1753, de-
cember 2003.
Bibliographie 145
[50] J. F. Allen and G. Ferguson, “Actions and events in interval temporal logic, Tech.
Rep. TR521, 1994. [Online]. Available : citeseer.ist.psu.edu/allen94actions.html
[51] F. Bacchus, J. Tenenberg, and J. Koomen, “A non-reified temporal logic,” in Pro-
ceedings of the First International Conference on Principles of Knowledge Repre-
sentation and Reasoning, R. Brachman, H. Levesque, and R. Reiter, Eds. Toronto,
Ontario, Canada : Morgan Kaufmann, May 1989, pp. 2–10.
[52] O. Lichtenstein and A. Pnueli, “Checking that finite state concurrent programs sa-
tisfy their linear specification,” in Proceedings of the 12th ACM SIGACT-SIGPLAN
symposium on Principles of programming languages, 1985.
[53] D. Kozen, “Results on the propositional mu-calculus,” Theoretical Computer
Science, vol. 27, pp. 333–354, 1983.
[54] E. A. Emerson and J. Y. Halpern, “Sometimes and not never revisited : on bran-
ching versus linear time temporal logic,” Journal of the ACM, vol. 33, no. 1, pp.
151–178, 1986.
[55] E. A. Emerson and C. L. Lei, “Modalities for model checking : Branching time
logics strikes back,” in Proceedings of the First Symposium on Logic in Computer
Science, Cambridge, June 1986, pp. 267–278.
[56] R. Alur and T. A. Henzinger, “A really temporal logic,” in IEEE Symposium on
Foundations of Computer Science, 1989.
[57] J. Ma and B. Knight, “Reified temporal logics : An overview,” Artificial Intelligence
Review, vol. 15, no. 3, pp. 189–217, May 2001.
[58] T. Wilke, “Ctl+ is exponentially more succinct than ctl,” in 19th Conf. Found.
of Software Technology and Theor. Comp. Sci., ser. Lecture Notes in Computer
Science, vol. 1738. Springer, 1999, pp. 110–121.
[59] G. J. Holzmann, “The model checker SPIN,” IEEE Trans. Softw. Eng., vol. 23,
no. 5, pp. 279–295, 1997.
[60] Z. Manna and A. Pnueli, “Verification of concurrent programs : The temporal
framework, in the correctness problem in computer science,” International Lecture
Series in Computer Science, 1981.
[61] D. M. Gabbay, “The declarative past and imperative future : Executable temporal
logic for interactive systems,” in Conference on Temporal Logic in Specification,
ser. Lecture Notes in Computer Science, H. B. Behnam Banieqbal and A. Pnueli,
Eds., vol. 398. Springer, 1989, pp. 409–448.
[62] P. Thati and G. Roşu, “Monitoring algorithms for metric temporal logic specifica-
tions,” in Runtime Verification 2004 (RV04), 2004.
[63] F. Laroussine and P. Schnoebelen, “A hierarchy of temporal logics with past,”
Theoritical Computer Science, vol. 48, no. 2, pp. 303–324, 1995.
Bibliographie 149
[81] M. Barbeau, F. Kabanza, and R. St.-Denis, “A method for the synthesis of control-
lers to handle safety, liveness, and real-time constraints,” IEEE Transactions on
Automatic Control, vol. 43, no. 11, pp. 1543–1559, Novembre 1998.
[82] R. St-Denis, “Designing reactive systems : integration of abstraction techniques
into a synthesis procedure,” J. Syst. Softw., vol. 60, no. 2, pp. 103–112, 2002.
[83] A. Lacasse, M. Mejri, and B. Ktari, “Formal implementation of network security
policies,”in Second Annual Conference on Privacy, Security and Trust,, Wu Centre,
University of New Brunswick, Fredericton, New Brunswick, Canada, October 2004.