scapy le couteau suisse du NetOps
Scapy est un programme Python interactif et une lib Python qui permet de forger, envoyer, sniffer et décoder des paquets réseau, avec une approche très souple pour le test, l’analyse et la découverte réseau.
Ce que Scapy sait faire
Scapy sert surtout à trois choses : construire des paquets, les envoyer, puis analyser les réponses reçues.
Il peut aussi remplacer partiellement des outils comme hping, arping, p0f, tcpdump ou tshark pour certains usages ciblés. Son point fort est de renvoyer le paquet complet reçu, pas juste une interprétation binaire du type “ouvert/fermé”, ce qui permet d’analyser soi-même les détails utiles.[scapy.readthedocs]
Les usages les plus courants en sécurité réseau sont :
- Découverte et cartographie réseau.
- Scan de ports et validation de filtrage.
- Traceroute TCP/ICMP.
- Sniffing et analyse de trafic.
- Fuzzing de champs de protocoles.
- Tests d’hypothèses sur des stacks réseau ou des équipements.
Démarrage propre
Pour travailler proprement, utilise un environnement isolé, Python récent compatible, et lance Scapy avec les privilèges nécessaires seulement quand il faut émettre ou sniffer des paquets. Scapy documente aussi l’usage de conf.use_pcap, conf.L3socket, et conf.L2socket pour choisir les sockets réseau selon la plateforme et les besoins. Sur certaines plateformes, le pare-feu local peut répondre à la place de ta machine ou gêner les tests ; il faut alors adapter la configuration locale avec prudence.
Exemple minimal :
from scapy.all import *
a = IP(dst="192.0.2.10")/ICMP()
a.show()
ans = sr1(a, timeout=2)Philosophie d’usage
La bonne manière d’utiliser Scapy, c’est de raisonner en couches réseau et en faits observables. Tu construis un stimulus précis, tu observes la réponse brute, puis tu interprètes toi-même le résultat. C’est beaucoup plus fiable que de dépendre d’une conclusion automatique, surtout dans des environnements filtrés, asymétriques ou mal configurés.
Trois réflexes utiles :
- Toujours commencer par un paquet simple.
- Ajouter une seule variable à la fois.
- Sauvegarder les captures et rejouer ensuite l’analyse offline.
Flux de travail efficace
Un flux efficace ressemble souvent à ceci :
- Construire un paquet de base.
- Vérifier sa structure avec
show()ethexdump(). - Envoyer avec
sr1(),sr(),send(), ousendp()selon la couche. - Capturer les réponses avec filtres.
- Rejouer l’analyse sur un PCAP avec
rdpcap()ousniff(offline=...).
Exemple pratique :
pkt = IP(dst="192.0.2.10", ttl=1)/TCP(dport=80, flags="S")
pkt.show2()
ans, unans = sr(pkt, timeout=2)Construction de paquets
Scapy utilise le symbole / pour empiler les couches, ce qui rend la construction très lisible. Les valeurs par défaut sont intelligentes : IP source, checksum, ports usuels, type Ethernet, etc. sont souvent remplis automatiquement. C’est pratique, mais en sécurité il faut parfois forcer explicitement les champs pour éviter les ambiguïtés d’analyse.
Exemples utiles :
Ether()/IP(dst="198.51.100.1")/TCP(dport=443, flags="S")
IP(dst=["198.51.100.1","198.51.100.2"])/ICMP()
IP(ttl=(1,5))/UDP(dport=[53,123])Sniffing et analyse
sniff() est central pour l’apprentissage, car il permet d’observer le trafic tel qu’il est réellement vu par l’interface. Tu peux filtrer avec BPF, traiter paquet par paquet avec prn=, ou capturer hors ligne depuis un fichier PCAP. Scapy supporte aussi des sessions pour réassembler certains flux TCP et défragmenter l’IP à la volée, ce qui est très utile pour analyser des protocoles comme HTTP ou TLS.
Bonnes pratiques :
- Filtre toujours au plus tôt.
- Mets
store=Falsesi tu n’as pas besoin de tout conserver. - Utilise
prn=lambda p: p.summary()pour du tri rapide. - Passe ensuite à
show()ousprintf()pour extraire les bons champs.
Envoi et réception
La distinction est simple :
send(): couche 3.sendp(): couche 2.sr(): envoi + réception à couche 3.srp(): envoi + réception à couche 2.
Scapy peut gérer des ensembles de paquets et renvoyer les couples requête/réponse, ce qui est idéal pour des tests de portée ou de filtrage. Pour les scans, mieux vaut exploiter les réponses brutes que de se contenter d’un état interprété, car un ICMP peut signifier plusieurs choses selon le contexte.
Exemple :
ans, unans = sr(IP(dst="192.0.2.0/30")/TCP(dport=[22,80,443], flags="S"), timeout=2)
ans.summary()Scan et découverte
Scapy permet de faire des scans très ciblés, par exemple un SYN scan minimal avec sr1(IP()/TCP(flags="S")) ou des tests multi-hôtes/multi-ports avec sr(). Il peut aussi générer des tableaux exploitables pour visualiser rapidement quels ports répondent et comment. L’intérêt n’est pas de “faire du bruit”, mais de formuler une hypothèse précise sur le réseau, puis de la vérifier.
Pour apprendre proprement :
- Scan un seul port.
- Puis une petite plage.
- Puis plusieurs hôtes.
- Puis change un seul paramètre à la fois : TTL, flags TCP, options, MSS, fenêtre, etc.
Fuzzing et robustesse
fuzz() est très utile pour la recherche de comportements inattendus, car il randomise les champs non calculés automatiquement. C’est particulièrement intéressant pour des protocoles simples ou des équipements réseau que tu veux tester de façon contrôlée. En revanche, il faut l’utiliser avec discipline, car du fuzzing mal cadré peut provoquer du bruit, des faux positifs ou des perturbations sur le réseau.
Bonne méthode :
- Définis une base valide.
- Fuzz uniquement une couche ou quelques champs.
- Journalise ce que tu envoies.
- Compare les réponses sur plusieurs itérations.
Automatisation et scripts
Scapy reste un framework Python, donc tu peux intégrer boucles, fonctions, structures de données et parsing avancé. C’est là qu’il devient vraiment puissant : corréler plusieurs séries de tests, normaliser des résultats, créer des mini-outils spécialisés, ou automatiser des vérifications récurrentes. Les objets PacketList, Results, Unanswered, summary(), make_table(), nsummary() et filter() sont très pratiques pour produire des analyses lisibles.
Exemple :
targets = ["192.0.2.10", "192.0.2.11"]
ports = [22, 80, 443]
ans, unans = sr(IP(dst=targets)/TCP(dport=ports, flags="S"), timeout=2)
open_only = ans.filter(lambda s, r: TCP in r and r[TCP].flags & 0x12 == 0x12)
open_only.summary()Bonnes pratiques
Voici les bonnes pratiques que je te recommande :
- Utiliser Scapy dans un lab ou sur des systèmes autorisés uniquement.
- Travailler avec des comptes non permanents et des privilèges minimaux.
- Capturer sur un fichier puis analyser offline dès que possible.
- Écrire des scripts courts, lisibles et versionnés.
- Toujours valider la structure du paquet avant envoi.
- Toujours fixer
timeout,retry,interet les filtres pour maîtriser le comportement.[github]
Côté sécurité opérationnelle, il faut aussi surveiller les effets de bord locaux : réponses RST du noyau, pare-feu personnel, offload NIC, ou traitement par la pile locale qui fausse les résultats. En environnement pro, c’est important pour éviter de conclure à tort qu’un équipement distant a répondu d’une certaine manière.[github]
Astuces de travail
Quelques astuces qui font gagner du temps :
- Utilise
show2()au lieu deshow()si tu veux voir les champs recalculés comme les checksums.[blog.shuvangkardas] - Utilise
sprintf()pour extraire seulement les champs utiles. - Utilise
hide_defaults()après désassemblage si l’affichage est trop verbeux. - Utilise
wrpcap()etrdpcap()pour rejouer et comparer. - Utilise
AsyncSniffersi tu veux arrêter le sniffing proprement par code.
Exemple d’extraction ciblée :
pkt.sprintf("%IP.src% -> %IP.dst% %TCP.sport% -> %TCP.dport% %TCP.flags%")Pièges fréquents
Les erreurs classiques avec Scapy sont :
- Mélanger couche 2 et couche 3.
- Oublier que certaines fonctions demandent des privilèges élevés.
- Interpréter trop vite une absence de réponse.
- Laisser le pare-feu local perturber les tests.
- Oublier que les champs par défaut peuvent masquer un problème de compréhension.[github]
Un autre piège courant est de se fier à un seul paquet alors que la réalité réseau peut dépendre de l’état, du timing, du chemin, ou d’un équipement intermédiaire. Pour l’apprentissage, répéter la même expérience plusieurs fois et comparer les réponses est souvent plus instructif qu’un gros scan.
Cheatsheet
Installation
# installation simple sous Debian
sudo apt install python3-scapy
# ou via Python & venv
python3 -m venv .venv
source .venv/bin/activate
python -m pip install scapy
python -m pip show scapyLancement
sudo scapy
python
from scapy.all import *Création
IP()
IP(dst="192.0.2.1")
IP(dst="192.0.2.1")/ICMP()
Ether()/IP()/TCP()
IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"Inspection
pkt.show()
pkt.show2()
pkt.summary()
ls(IP)
hexdump(pkt)
raw(pkt)
pkt.command()
pkt.json()Manipulation
pkt[IP].ttl = 10
del pkt[IP].ttl
pkt.haslayer(TCP)
pkt[TCP].flags
pkt.getlayer(IP)
pkt[IP].fieldsEnvoi
send(IP(dst="192.0.2.1")/ICMP())
sendp(Ether()/IP(dst="192.0.2.1")/ICMP(), iface="eth0")
sr(IP(dst="192.0.2.1")/ICMP())
sr1(IP(dst="192.0.2.1")/ICMP())
srp(Ether()/ARP(pdst="192.0.2.0/24"))
srp1(Ether()/ARP(pdst="192.0.2.1"))Réception
sniff(count=10)
sniff(filter="icmp", iface="eth0")
sniff(prn=lambda p: p.summary(), store=False)
AsyncSniffer(iface="eth0", prn=lambda p: p.summary(), store=False)Sauvegarde
wrpcap("capture.pcap", pkts)
pkts = rdpcap("capture.pcap")
pkts = sniff(offline="capture.pcap")Scan
sr(IP(dst="192.0.2.1")/TCP(dport=80, flags="S"))
sr(IP(dst="192.0.2.1")/TCP(dport=[22,80,443], flags="S"))
ans, unans = sr(IP(dst="192.0.2.0/30")/TCP(dport=[22,80], flags="S"))
ans.summary()
ans.make_table(lambda s, r: (s.dst, s.dport, r.sprintf("%TCP.flags%")))Fuzzing
fuzz(IP()/UDP()/DNS())
fuzz(IP(dst="192.0.2.1")/UDP()/Raw(b"A"*20))Filtrage et extraction
pkt.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport% %TCP.flags%")
ans.filter(lambda s, r: TCP in r and r[TCP].flags & 0x12 == 0x12)
ans.nsummary()TCP / traceroute
sr(IP(dst="192.0.2.1", ttl=(1,30))/TCP(flags="S"))
traceroute(["192.0.2.1", "198.51.100.1"])ARP
arping("192.0.2.0/24")
srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.0.2.0/24"))DNS
sr1(IP(dst="192.0.2.53")/UDP()/DNS(rd=1, qd=DNSQR(qname="example.com")))TCP options utiles
TCP(options=[("MSS", 1460), ("SAckOK", b""), ("WScale", 7), ("Timestamp", (123, 0))])Utilitaires utiles
conf.use_pcap = True
conf.iface
conf.ifaces
conf.L3socket
conf.L2socketVariables et boucles
for p in packets:
print(p.summary())Export/Import brut
b = bytes(pkt)
pkt2 = Ether(b)Tableaux
ans.make_table(lambda s, r: (s.dst, s.dport, r.src))
ans.filter(lambda s, r: TCP in r).make_table(lambda s, r: (s.dst, s.dport, "X"))Lecture rapide des réponses TCP
if TCP in r and r[TCP].flags & 0x12 == 0x12:
print("SYN-ACK")
elif TCP in r and r[TCP].flags & 0x14 == 0x14:
print("RST-ACK")Plan d’apprentissage
- Construction de paquets.
- Lecture et décodage.
send()/sr()/sniff().- Filtrage et extraction.
- PCAP et analyse offline.
- Scan et traceroute contrôlés.
- Fuzzing ciblé.
- Mini-outils spécialisés Python.
Ressources utiles
La documentation officielle Scapy est la meilleure base pour les concepts, les fonctions et les limites pratiques. Le dépôt officiel et la section “troubleshooting” sont aussi utiles pour les problèmes de sockets, de firewall et de comportement local.