La commande
Pour lancer Logsurfer, c'est très simple. Il suffit de taper logsurfer avec différentes options.
-c configfile : le fichier de configuration voulu.
-d dumpfile : le fichier de résultats.
-l start_linenum : la première ligne à traiter.
file : le nom du fichier à traiter.
Les autres options ne semblent pas très intéressantes.
Ainsi, Logsurfer va analyser le fichier file à l'aide du fichier de configuration configfile, et les résultats seront inscrits dans le fichier dumpfile.
On peut donc ne commencer l'analyse qu'à partir d'une certaine ligne pour ne pas analyser plusieurs fois la même chose et ainsi découper le travail à effectuer par tranche de temps.
--------------------------------------------------------------------------------
La configuration
Il s'agit de la partie la plus importante, mais aussi de la plus difficile, puisque c'est elle qui conditionne tout le travail. En effet, c'est ici que l'on spécifie les règles d'analyse. Il s'agit donc d'une suite de règles définies ainsi :
(regex désigne une expression régulière du type egrep.)
match_regex not_match_regex stop_regex not_stop_regex timeout [continue] action
match_regex : la règle est appliquée si la ligne "matche" avec l'expression. Il s'agit de la partie la plus générale de la règle.
not_match_regex : la règle n'est pas appliquée si la ligne matche avec cette expression. Cela est très utile pour définir des exceptions à une règle, par exemple exclure les machines d'un certain réseau.
stop_regex : la règle est détruite si la ligne matche avec cette expression. Permet d'avoir une règle existante uniquement jusqu'à l'apparition d'une autre.
Par exemple on crée une règle qui dit que dès qu'on voit une expression(Ex:fin ok), on détruit le contexte pour les messages de cette session, puisqu'elle s'est bien terminée. Cette règle s'autodétruira : une fois appliquée, elle ne sera plus utile.
not_stop_regex : en complément de la précédente, la règle n'est pas détruite si elle répond à cette expression.
timeout : durée d'existence en seconde de la règle (0 signifie toujours). Pour éliminer une règle au bout d'un certain temps, le nombre de ligne testé dépendant de la vitesse...
continue : si cette règle marche, on essaiera quand même les règles suivantes.
action : il s'agit de la deuxième partie de la règle, celle qui définie ce qu'on doit faire une fois la ligne matchée. Les differentes actions sont :
Ignore : on passe à la ligne suivante, très utile dans le cas d'une analyse par élimination (on décide ce qui est normal, tout le reste est suspect).
Exec : le premier argument est le nom du programme à exécuter avec le path complet, les autres seront passés à ce programme. On peut utiliser les variables de match_regex ($0 pour la ligne, $1 pour le "match", et $2 à $9 pour les sous-matchs). Ex : exec "/bin/echo $0 matche avec $1".
Pipe : la même chose qu'exec, mais la ligne actuelle ($0) est passée en entrée standard (stdin).
Open : permet d'ouvrir un nouveau contexte. Si un contexte avec le même match_regex existe déjà, alors cette action ne fait rien.
Delete : permet de fermer un contexte. L'action par défaut du contexte n'est pas exécutée.
Report : le premier argument est le nom du programme à exécuter avec le path complet, les autres représentent des définitions de contexte et ceux-ci sont passes en entrée standard (stdin).
Rule : permet de créer une nouvelle règle en dynamique. Le mot qui suit doit être : before (inserée juste avant), behind (juste après), top (en premier) ou bottom (en dernier). Le reste est identique au format d'une règle.
Les contextes
Un contexte permet de sauvegarder toutes les lignes répondant à certains critères. Cela est par exemple très utile pour retenir toutes les actions d'une session ftp mal terminée, ou tous les paquets d'une machine jugée suspecte.
Ils sont définis ainsi :
match_regex match_not_regex line_limit timeout_abs timeout_rel default_action
match_regex : si la ligne matche avec cette expression, elle est inserée dans ce contexte.
match_not_regex : la ligne n'est pas inserée si elle correspond à cette expression, permet de définir des exceptions.
line_limit : le nombre maximum de ligne à sauvegarder dans ce contexte.
Une petite remarque : contrairement à ce qu'indiquent les mans, l'atteinte de la limite en nombre de ligne du contexte ne provoque pas l'action associée. Celle-ci est uniquement déclenchée lors d'un timeout (absolu ou relatif). Cela nous aurait évité de passer par AWK pour analyser la sortie. (A moins que je ne me trompe).
timeout_abs : le contexte est détruit et l'action par défaut exécutée si ce temps en seconde est depassé (0 pour jamais).
timeout_rel : même chose si aucune ligne n'est ajoutée durant cet intervalle.
default_action : ce sont les mêmes actions que dans les règles, sauf Open, Delete et Rule.
--------------------------------------------------------------------------------
Les résultats
Le fichier de résultats a la forme suivante : une partie contient les règles courantes -car les règles sont dynamiques-, et l'autre contient les contextes en cours.
Les règles ont la forme suivante :
-----
list.*denied udp ([0-9\.]*)\(([0-9]*)\) -> ([0-9\.]*)\(([0-9]*)\) -
- - 0 CONTINUE
OPEN "list.*denied udp $2.*->.*($5)" - 100 0 0 Ignore
match_regex not_match_regex
stop_regex not_stop_regex timeout [CONTINUE]
action
Les contextes ont la forme suivante :
-----
list.*denied udp 192.168.0.1.*->.*(137) -
100 0 0 IGNORE
128: 321: Jul 2 16:27:49 router-gw 684229: %SEC-6-IPACCESSLOGP: list 102 denied udp 192.168.0.1(137) -> 152.81.242.207(137), 1 packet
1188: 5130: Jul 13 17:35:16 router-gw 238: %SEC-6-IPACCESSLOGP: list 102 denied udp 192.168.0.1(137) -> 152.81.11.249(137), 1 packet
match_regex match_not_regex
line_limit timeout_abs timeout_rel default_action
Line 1
Line 2
...
--------------------------------------------------------------------------------
Un exemple d'utilisation
Voici un exemple de ce que nous avons pu réaliser avec Logsurfer, concernant la détection des scans à partir des logs cisco.
scan01.conf
# fichier de configuration Logsurfer pour les logs cisco.debug.
# on cree d'abord un rapport avec tous les denied udp, tcp et icmp en un seul exemplaire
# un contexte est cree a chaque triplet @source-@destination-port destination, qui ne matche pas
# avec le couple @destination-port destination, pour eliminer la redondance.
#
# si on trouve un denied, on cree un contexte sur @-port sources et destinations.
#
# forme des "matchs" : denied udp @src(port src) -> @dest(port dest)
# sauf les machines sources 152.81
# pas de destruction de la regle (ni matchs ni timeout).
# si la ligne matche, on ouvre un contexte pour sauvegarder ce denied en un seul exemplaire
# 10 pour la limite des contextes(en fait on n'aura qu'une seule ligne dans chaque contexte)
# 0 et 0 pour les timeouts : le contexte reste toujours (on veut une trace a la fin).
# Ignore est l'action par defaut de la fin de contexte : jamais accomplie.
' denied udp ([0-9\.]*)\(([0-9]*)\) -> ([0-9\.]*)\(([0-9]*)\)' '152\.81.* ->' - - 0
Open " denied udp $2.*-> $4($5)" "$4\\($5\\)" 10 0 0 Ignore
' denied tcp ([0-9\.]*)\(([0-9]*)\) -> ([0-9\.]*)\(([0-9]*)\)' '152\.81.* ->' - - 0
Open " denied tcp $2.*-> $4($5)" "$4\\($5\\)" 10 0 0 Ignore
' denied icmp ([0-9\.]*) -> ([0-9\.]*)' '152\.81.* ->' - - 0
Open " denied icmp $2 -> $3" "$3" 10 0 0 Ignore
# On ne s'occupe ici que des scans, donc on ignore le reste
'.*' - - - 0 Ignore
scan02.conf
# On centralise les informations recensees par scan01.conf afin d'obtenir pour
# chaque machine source, la liste des machines et des ports "essayes"
#
# quand on trouve un denied, on cree un contexte sur @ source, et @ ou port destination.
# ainsi, pour chaque machine source en cause, on a un contexte sur les ports essayes sur chaque machine
# destination, et un contexte sur les machines essayees pour chaque port.
# on n'aura plus qu'a compter le nombre de lignes dans chaque contexte avec AWK.
#
# on utilise CONTINUE si une machine scan a la fois les ports et les machines...
# pour les scans d'un port en udp
'list.*denied udp ([0-9\.]*)\(([0-9]*)\) -> ([0-9\.]*)\(([0-9]*)\)' - - - 0 CONTINUE
Open "list.*denied udp $2.*->.*($5)" - 100 0 0 Ignore
# pour les scans d'une machine en udp
'list.*denied udp ([0-9\.]*)\(([0-9]*)\) -> ([0-9\.]*)\(([0-9]*)\)' - - - 0
Open "list.*denied udp $2.*-> $4(" - 100 0 0 Ignore
# pour les scans d'un port en tcp
'list.*denied tcp ([0-9\.]*)\(([0-9]*)\) -> ([0-9\.]*)\(([0-9]*)\)' - - - 0 CONTINUE
Open "list.*denied tcp $2.*->.*($5)" - 100 0 0 Ignore
# pour les scans d'une machine en tcp
'list.*denied tcp ([0-9\.]*)\(([0-9]*)\) -> ([0-9\.]*)\(([0-9]*)\)' - - - 0
Open "list.*denied tcp $2.*-> $4(" - 100 0 0 Ignore
# pour les scans en icmp
'list.*denied icmp ([0-9\.]*) -> ([0-9\.]*)' - - - 0
Open "list.*denied icmp $2.*->" - 100 0 0 Ignore
# On ignore le reste
'.*' - - - 0 Ignore
scan.awk
# on realise ici un comptage des machines et des ports essayes a partir du fichier produit
# par Logsurfer (scan02.conf).
# pour chaque ligne de contenu d'un contexte (du type list...) on effectue quelques operations.
# lorsqu'on voit ----- ou la fin, on affiche les informations si NB (fourni en parametre) est depasse.
BEGIN { commence = 0; }
/CONTEXT/{ commence = 1; # a partir des contextes, on commence le comptage }
/-----/{ ligne = 0; # on remet le compteur a zero
if (oldligne>NB+0) # ne marche pas sans le +0 ???
{if (type == 1) print jour,mois,heure,": tentative",proto,"de",source[1],"sur",dest[1],"(",oldligne,"ports )";
if (type == 2) print jour,mois,heure,": tentative",proto,"de",source[1],"sur le port",port[1],"(",oldligne,"machines )" } }
/list.*denied udp/{ if (commence == 0) next; # si on n'est pas dans les contextes, on saute;
oldligne = ligne; # on memorise le nombre de ligne;
olddest = dest[1]; # on memorise la machine;
oldport = port[1]; # on memorise le port;
ligne=ligne+1; # on ajoute une ligne;
mois = $3; jour = $4; heure= $5; s=$13;d=$15; # recuperation des infos source et destination;
split(s,source,"("); # on a la machine source dans source[1];
split(d,dest,"("); # recuperation machine dans dest[1];
split(dest[2],port,")"); # recuperation port dans port[1];
if (olddest == dest[1]) type=1; # la machine est la meme -> scan des ports;
if (oldport == port[1]) type=2; # le port est le meme -> scan des machines;
proto = "udp "; # pour savoir le type du protocole utilise;
break; # on passe a la ligne suivante; }
/list.*denied tcp/{ if (commence == 0) next; # idem udp;
oldligne = ligne;
olddest = dest[1];
oldport = port[1];
ligne=ligne+1;
mois = $3; jour = $4; heure= $5; s=$13;d=$15;
split(s,source,"(");
split(d,dest,"(");
split(dest[2],port,")");
if (olddest == dest[1]) type=1;
if (oldport == port[1]) type=2;
proto = "tcp ";
break; }
/list.*denied icmp/{ if (commence == 0) next; # idem udp;
oldligne = ligne;
olddest = dest[1];
oldport = port[1];
ligne=ligne+1;
source[1]=$13; mois = $3; jour = $4; heure= $5;
type = 2;
port[1] = "---";
proto = "icmp";
break; }
END { if (oldligne>NB+0) # meme action pour la derniere ligne
{ if (type == 1) print jour,mois,heure,": tentative",proto,"de",source[1],"sur",dest[1],"(",oldligne,"ports )";
if (type == 2) print jour,mois,heure,": tentative",proto,"de",source[1],"sur le port",port[1],"(",oldligne,"machines )" } }
Remarque
Dans le cas d'une utilisation simple, comme ici, il est préférable d'employer un script Perl qui sera beaucoup plus rapide à travail égal, et dont le fonctionnement sera plus souple et maîtrisable, en l'absence d'intermédiaire. De plus, le traitement des informations recueillies est plus aisé également. Pour cet exemple notamment, il n'y a pas besoin de fichiers temporaires, et un seul script réalise l'ensemble des opérations.
Voir Detescan.