tcpdump : pour aller au niveau le plus fin

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)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.