Dire qu’au début je voulais juste connaitre les volumes de données par mois transitant sur une seule interface, information que vnStat m’a donné rapidement mais sans historique et en mode texte. Maintenant me voilà voulant connaitre à chaque moment, quelle ip et quel port se connecte et puis tant qu’on y est : autant géolocaliser l’IP et afficher le nom du service associé au port.
Donc une petit recherche sur Google montre que sous Linux il existe un petit utilitaire en ligne de commande, magique mais d’une utilisation…. pas simple qui permet l’analyse des trames réseaux. Là je me dis : Aïe si c’est comme les outils de trace des moteurs de base de données, cet outil risque de rudement consommer du CPU et d’autres ressources et si c’est le cas, je devrai chercher une autre solution.
Mais un petit test m’a montré que non : que neni, tcpdump est super discret et fait bien son job donc en avant.
Que fait tcpdump ?
Il stocke en binaire les trames qui circulent sur une interface réseau. Pas bête, il sait ne filtrer que certaines trames et permet de limiter les trames stockées pour ne pas remplir un disque dur. Il sait aussi transformer ses données binaires en un fichier texte compréhensible par l’homme : chouette !
Par dessus le marché, il intercepte les trames avant le pare-feu !! Donc je vais voir tous les petits malins qui tentent par tous les moyens de pirater mon serveur et il y en a ! C’est un bon moyen de voir à quel point sur internet règne une véritable guerre mondiale. Au moindre serveur connecté, il suffit nombres tentatives de piratages.
Jouons un peu , mon 1er objectif consiste à obtenir un fichier texte pas trop gros et assez rapidement, avec $iface=le nom de mon interface réseau, $nbpacket=100 et $txtfile = le nom di fichier texte en sortie, si on fait ceci :
tcpdump -i "$iface" -n -nn -N -q -p -c"$nbpacket" -z '/usr/sbin/tcpdump -n -nn -N -q -p -r' > "$txtfile"
On obtient un fichier compréhensible. en quelques minutes.
Quelques explications :
- -i : pour donner le nom de l’interface à sniffer
- -n : pour empêcher tcpump de m’afficher des nom d’hôtes : je veux des ip
- -nn : pour empêcher tcpdump de me donner le nom du port : je ne veux que le numéro de port
- -q (quiet) : pour diminuer ce qui s’affiche à l’écran car tcpdump est plutôt bavard
- -p : pour empêcher tcpdump de mettre l’interface en mode promiscuité : je ne veux pas que cela altère le fonctionnement normal des services.
- -c: pour arrêter le traitement au bout de nbpacks packets
- -z : pour qu’à la suite, juste après la lecture des nbpacket paquets, tcpdump lancer la commande qui suit
- Cette commande (entre quote) est la même sauf qu’il ne lit plus le flux réseaux mais le fichier binaire qu’il vient de créer (option -r : read)
- et la redirection (>) vers le nom du fichier à créer.
Comme c’est du live, il n’y a aucun historique : donc on va stocker cela dans une table MySQL. Mais pour cela il nous faut un beau fichier de sortie bien construit.
Voici le fichier obtenu avec tcpdump :
cat $txtfile 17:14:35.055649 IP 192.168.1.200 > 171.98.152.135: ICMP host 192.168.1.200 unreachable - admin prohibited, length 137 17:14:35.426403 IP 91.121.77.35.443 > 192.168.1.200.36458: tcp 0 17:14:35.426568 IP 91.121.77.35.443 > 192.168.1.200.36456: tcp 0 17:14:36.688035 IP 192.168.1.200.55584 > 172.217.18.206.443: tcp 0 17:14:36.729640 IP 172.217.18.206.443 > 192.168.1.200.55584: tcp 0 17:14:36.766616 IP 192.168.1.200.39984 > 192.168.1.1.53: UDP, length 34 17:14:36.766638 IP 192.168.1.200.39984 > 192.168.1.1.53: UDP, length 34 17:14:36.767116 IP 192.168.1.200.33665 > 192.168.1.1.53: UDP, length 34 17:14:36.767790 IP 192.168.1.1.53 > 192.168.1.200.39984: UDP, length 50 17:14:36.768037 IP 192.168.1.1.53 > 192.168.1.200.39984: UDP, length 34 17:14:36.768392 IP 192.168.1.1.53 > 192.168.1.200.33665: UDP, length 50 17:14:36.769403 IP 192.168.1.200.40312 > 91.121.77.35.443: tcp 0 17:14:36.814252 IP 91.121.77.35.443 > 192.168.1.200.40312: tcp 0 17:14:36.814819 IP 192.168.1.200.40312 > 91.121.77.35.443: tcp 0 17:14:36.820507 IP 192.168.1.200.40312 > 91.121.77.35.443: tcp 577
On a donc un fichier dont la structure est : des données séparées par des espaces et dont les champs sont :
- 1 : l’heure au format H:MM:SS.mmmmmm
- 2 : le mot clé « IP »
- 3 : Une IPv4+un numéro de port séparés par un point (toujours présent si tcp, absent si UDP)
- 4: le sigle >
- 5 : Une IPv4+un numéro de port séparés par un point (il arrive que le numéro de port soit remplacé par ICMP…blabla) + deux-point (:)
- 6 : un mot clé (ICMP, tcp, UDP) parfois compléter avec du blable, parfois compléter par une virgule (UDP,)
- 7 : si tcp : le mot clé « length », si UDP, le mot clé « length », si ICMP : du blabla
- 8 : la taille de la trame (en octet)
Je connais l’adresse IP de mon serveur sur cette interface (192.168.1.200) et à voir le positionnement, le signe > ne change pas, c’est la position de 192.168.1.200 qui me dira si c’est de l’émission ou de la réception. En fait c’est plus que cela car mon serveur fait passerelle, donc je peux avoir une iplocale différente de celle du serveur et une ip externe. En ce cas, on a à la fois émission et réception via à vis du serveur.
Je ne m’intéresse pas aux trames ICMP. Je vais donc prendre que les tcp et les udp mais udp étant en majuscules, on va utiliser l’option i de grep (pour le rendre insensible à la casse) et je ne veux pas de lignes dont la taille vaut 0. Le pipe (|) dans le format soumis à grep représent eun « Ou » et v un « NOT »
cat "$txtfile" | grep -i "tcp\|udp" | grep -iv "tcp 0\|udp 0\|udp, length 0" > "data.txt"
[root@pntserv ~]# cat data.txt 17:14:20.702807 IP 192.168.1.200.2222 > 77.207.115.7.7584: tcp 208 17:14:22.391774 IP 173.249.33.72.7273 > 192.168.1.200.51413: UDP, length 97 17:14:25.127337 IP 184.154.74.66.42396 > 192.168.1.200.8888: tcp 205 17:14:25.128795 IP 192.168.1.200.8888 > 184.154.74.66.42396: tcp 968 17:14:30.254226 IP 192.168.1.200.51413 > 84.216.41.124.6881: UDP, length 94 17:14:30.338974 IP 84.216.41.124.6881 > 192.168.1.200.51413: UDP, length 289 17:14:30.339694 IP 192.168.1.200.51413 > 142.167.79.99.64830: UDP, length 58 17:14:30.339724 IP 192.168.1.200.51413 > 73.95.245.175.8999: UDP, length 58 17:14:30.339749 IP 192.168.1.200.51413 > 37.48.80.211.53000: UDP, length 58 17:14:30.396814 IP 37.48.80.211.53000 > 192.168.1.200.51413: UDP, length 80 17:14:30.494893 IP 142.167.79.99.64830 > 192.168.1.200.51413: UDP, length 70 17:14:30.523709 IP 73.95.245.175.8999 > 192.168.1.200.51413: UDP, length 80 17:14:34.113594 IP 46.39.49.166.61708 > 192.168.1.200.51413: UDP, length 106 17:14:34.429335 IP 192.168.1.200.56250 > 40.67.252.206.443: tcp 74 17:14:34.492018 IP 40.67.252.206.443 > 192.168.1.200.56250: tcp 126 17:14:35.055581 IP 171.98.152.135.6881 > 192.168.1.200.51413: UDP, length 101
Maintenant je voudrai que toutes les lignes aient la même structure donc remplacer « UDP, length » par « udp », c’est le boutlot de sed. L’anti-slash (\) est obligatoire à cause de la virgule
sed 's/UDP\, length/udp/g' -i data.txt
Regardons le résultat :
[root@pntserv ~]# cat data.txt 17:14:20.702807 IP 192.168.1.200.2222 > 77.207.115.7.7584: tcp 208 17:14:22.391774 IP 173.249.33.72.7273 > 192.168.1.200.51413: udp 97 17:14:25.127337 IP 184.154.74.66.42396 > 192.168.1.200.8888: tcp 205 17:14:25.128795 IP 192.168.1.200.8888 > 184.154.74.66.42396: tcp 968 17:14:30.254226 IP 192.168.1.200.51413 > 84.216.41.124.6881: udp 94 17:14:30.338974 IP 84.216.41.124.6881 > 192.168.1.200.51413: udp 289 17:14:30.339694 IP 192.168.1.200.51413 > 142.167.79.99.64830: udp 58 17:14:30.339724 IP 192.168.1.200.51413 > 73.95.245.175.8999: udp 58 17:14:30.339749 IP 192.168.1.200.51413 > 37.48.80.211.53000: udp 58 17:14:30.396814 IP 37.48.80.211.53000 > 192.168.1.200.51413: udp 80 17:14:30.494893 IP 142.167.79.99.64830 > 192.168.1.200.51413: udp 70 17:14:30.523709 IP 73.95.245.175.8999 > 192.168.1.200.51413: udp 80 17:14:34.113594 IP 46.39.49.166.61708 > 192.168.1.200.51413: udp 106 17:14:34.429335 IP 192.168.1.200.56250 > 40.67.252.206.443: tcp 74 17:14:34.492018 IP 40.67.252.206.443 > 192.168.1.200.56250: tcp 126 17:14:35.055581 IP 171.98.152.135.6881 > 192.168.1.200.51413: udp 101 17:14:36.766616 IP 192.168.1.200.39984 > 192.168.1.1.53: udp 34 17:14:36.766638 IP 192.168.1.200.39984 > 192.168.1.1.53: udp 34 17:14:36.767116 IP 192.168.1.200.33665 > 192.168.1.1.53: udp 34 17:14:36.767790 IP 192.168.1.1.53 > 192.168.1.200.39984: udp 50 17:14:36.768037 IP 192.168.1.1.53 > 192.168.1.200.39984: udp 34 17:14:36.768392 IP 192.168.1.1.53 > 192.168.1.200.33665: udp 50
On a maintenant un fichier qui convient dont la structure est en ensemble de valeurs séparées par des espaces et dont les colonnes qui nous intéressent sont :
- colonne 1 : l’heure au format h:MM:SS.mmmmmm
- colonne 3 : IPV4 Emettrice + « . » + Port
- colonne 5 : IPV4 Réceptrice + « . » + Port + »: »
- Colonne 6 : mot clé : udp ou tcp
- Colonne 7 : la taille
Comme je ne sais pas trop si ce sont des espaces, un seul, plusieurs ou des tabulations qui séparent les données, autant utiliser awk pour récupérer les valeurs
Donc comme on va lire le fichier ligne à ligne, je vais lire la 1ère ligne et la stocker dans lig :
lig=$(head data.txt -n1 )
echo $lig
17:14:20.702807 IP 192.168.1.200.2222 > 77.207.115.7.7584: tcp 208
Laissons awk faire le job pour récupérer les valeurs de chaque colonnes :
col1=$(echo "$lig" | awk '{print $1}') echo $col1 17:14:20.702807 col3=$(echo "$lig" | awk '{print $3}') echo $col3 192.168.1.200.2222 col5=$(echo "$lig" | awk '{print $5}') echo $col5 77.207.115.7.7584: col6=$(echo "$lig" | awk '{print $6}') echo $col6 tcp col7=$(echo "$lig" | awk '{print $7}') echo $col7 208
col3 contient une IP suivit d’un point suivit du port. Pour séparer tout ça, utilisons cut
ipsource=$(echo "$col3" | cut -d. -f1-4) echo $ipsource 192.168.1.200 portsource=$(echo "$col3" | cut -d. -f5) echo $portsource 2222
col5 contient une IP suivit d’un point suivit du port, suivit de 2 points . Pour séparer tout ça, utilisons cut
ipdest=$(echo "$col5" | cut -d. -f1-4) echo $ipdest 77.207.115.7 portdest=$(echo "$col5" | cut -d. -f5 | cut -d: -f1) echo $portdest 7584
Maintenant on a peut construire un beau fichier csv contenant des données séparées par exemple par des point-virgules contenant :
- heure
- ipsource
- portsource
- ipdest
- portdest
- type (udp/tcp)
- taille
Et enfin passer à la suite : charger le fichier de dump dans une table MySQL
Je suis sur qu’il y en a un qui pense : « ‘c’est bien joli tout ça mais après….. on ne peut pas mettre ça en crontab ! « ……exact : ça finira par un service avec des tâches en multi thread (si on veut aller au plus loin, sinon on se passera du multi thread)