Introduction
Bienvenue dans le laboratoire de création d'un scanner de ports.
Un scanner de ports est un outil fondamental en sécurité réseau, utilisé pour identifier les ports ouverts et les services en cours d'exécution sur une machine cible. Des outils comme Nmap sont incroyablement puissants, mais comprendre leur fonctionnement interne est essentiel pour tout aspirant professionnel de la cybersécurité ou ingénieur réseau.
Dans ce laboratoire, vous allez construire votre propre scanner de ports TCP personnalisé en partant de zéro avec Python. Vous commencerez par analyser les arguments de la ligne de commande, puis vous implémenterez la logique de connexion socket de base pour vérifier si un port est ouvert. Ensuite, vous ajouterez une gestion des exceptions pour rendre votre outil robuste, et enfin, vous implémenterez le multithreading pour accélérer considérablement le processus de scan. À la fin de ce laboratoire, vous disposerez d'un scanner de ports rapide et fonctionnel, ainsi qu'une solide compréhension de la programmation réseau en Python.
L'objectif pédagogique n'est pas de remplacer des outils matures comme Nmap, mais d'exposer les mécanismes que ces outils automatisent : gestion des entrées, tentatives de connexion, délais d'attente (timeouts), interprétation des résultats et compromis de performance. Une fois que vous aurez construit votre propre scanner, les outils professionnels deviendront beaucoup moins mystérieux.
Analyser les arguments de la ligne de commande
Dans cette étape, vous allez poser les bases de votre scanner de ports en créant un script Python capable d'accepter des arguments en ligne de commande. Les arguments de ligne de commande permettent aux utilisateurs de spécifier l'adresse IP cible et la plage de ports à scanner sans avoir à modifier le code.
C'est un exemple de séparation entre la logique de l'outil et les entrées utilisateur. Les bons outils de sécurité sont réutilisables car le code reste identique tandis que l'opérateur modifie les paramètres depuis la ligne de commande.
Nous utiliserons le module intégré argparse de Python, qui facilite l'écriture d'interfaces en ligne de commande conviviales.
Tout d'abord, assurez-vous d'être dans votre répertoire de projet. C'est ici que vous créerez et exécuterez votre script Python.
cd ~/project
Créez un nouveau fichier nommé scanner.py à l'aide de l'éditeur de texte nano :
nano scanner.py
Copiez et collez le code suivant dans l'éditeur. Ce code importe le module argparse et définit une fonction pour gérer les entrées concernant l'adresse IP cible et la plage de ports.
import argparse
import sys
def get_arguments():
parser = argparse.ArgumentParser(description="A simple TCP port scanner")
parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")
options = parser.parse_args()
return options
if __name__ == "__main__":
options = get_arguments()
## Extract the target and ports from the user's input
target_ip = options.target
ports_input = options.ports
print(f"[*] Target set to: {target_ip}")
print(f"[*] Port range set to: {ports_input}")
Enregistrez le fichier en appuyant sur Ctrl+O, puis Entrée. Si vous souhaitez passer directement à l'étape suivante, vous pouvez rester dans nano et continuer à modifier le même fichier. Utilisez Ctrl+X uniquement lorsque vous souhaitez revenir au terminal.
Maintenant, testez votre script en l'exécutant avec Python. Nous passerons l'adresse IP locale (127.0.0.1) et une petite plage de ports (7990-8010).
python3 scanner.py -t 127.0.0.1 -p 7990-8010
Vous devriez voir une sortie similaire à celle-ci :
[*] Target set to: 127.0.0.1
[*] Port range set to: 7990-8010
Si vous essayez d'exécuter le script sans l'argument -t, argparse générera automatiquement un message d'erreur et affichera l'utilisation correcte, rendant votre outil plus facile à utiliser depuis le terminal.
Implémenter la logique de connexion socket
Dans cette étape, vous allez implémenter la fonctionnalité principale du scanner de ports : la connexion socket.
Au niveau du protocole, un scan TCP de base est simple : essayez d'ouvrir une connexion TCP vers un port cible et observez si elle réussit. Cette idée suffit à expliquer le comportement d'une grande catégorie de scanners de ports.
Un socket est un point de terminaison pour envoyer ou recevoir des données sur un réseau informatique. Pour vérifier si un port est ouvert, votre scanner tentera d'établir une connexion TCP vers l'adresse IP cible sur un port spécifique. Si la connexion réussit, le port est ouvert ; si elle échoue ou expire, le port est fermé ou filtré.
Si scanner.py est toujours ouvert dans nano, continuez à le modifier. Sinon, rouvrez-le :
nano ~/project/scanner.py
Mettez à jour votre code pour inclure le module socket et ajouter la logique de scan. Remplacez tout le contenu du fichier par le code suivant :
import argparse
import sys
import socket
def get_arguments():
parser = argparse.ArgumentParser(description="A simple TCP port scanner")
parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")
return parser.parse_args()
def scan_port(ip, port):
## Create a new socket using the IPv4 address family and TCP protocol
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## Set a timeout of 1 second so the scanner doesn't hang on closed ports
sock.settimeout(1)
## connect_ex returns 0 if the connection was successful
result = sock.connect_ex((ip, port))
if result == 0:
print(f"[+] Port {port} is OPEN")
## Always close the socket after using it
sock.close()
if __name__ == "__main__":
options = get_arguments()
target_ip = options.target
## Parse the start and end ports from the input string
start_port, end_port = map(int, options.ports.split('-'))
print(f"[*] Scanning target {target_ip} from port {start_port} to {end_port}...\n")
## Loop through the specified range and scan each port sequentially
for port in range(start_port, end_port + 1):
scan_port(target_ip, port)
print("\n[*] Scan completed.")
Enregistrez le fichier avec Ctrl+O et Entrée. Vous pouvez rester dans nano et passer à l'étape suivante, car le script n'a pas besoin d'être testé immédiatement, sauf si vous souhaitez vérifier votre progression.
Dans ce code, socket.socket(socket.AF_INET, socket.SOCK_STREAM) crée un socket TCP. La méthode connect_ex() est particulièrement utile pour les scanners car elle renvoie un indicateur d'erreur (comme 0 pour le succès) au lieu de lever une exception lorsqu'une connexion échoue, ce qui garde notre code propre.
Exécutez votre script mis à jour sur votre machine locale. L'environnement de laboratoire dispose d'un serveur web caché fonctionnant sur le port 8000, scannons donc une plage qui l'inclut :
python3 scanner.py -t 127.0.0.1 -p 7990-8010
Vous devriez voir une sortie similaire à la suivante, identifiant avec succès le port 8000 comme ouvert :
[*] Scanning target 127.0.0.1 from port 7990 to 8010...
[+] Port 8000 is OPEN
[*] Scan completed.
Selon l'environnement de laboratoire, vous pourriez également voir d'autres ports ouverts dans la sortie. Le résultat important pour cette étape est que le port 8000 apparaisse comme ouvert.
Gérer les exceptions de connexion
Dans cette étape, vous allez rendre votre scanner plus robuste en ajoutant la gestion des exceptions.
Les outils de sécurité passent une grande partie de leur vie dans des conditions imparfaites : mauvaises entrées, hôtes inaccessibles, réseaux lents et exécutions interrompues. La gestion des exceptions n'est pas un simple décorum ; elle fait partie intégrante de la convivialité de l'outil lors d'opérations réelles.
Les opérations réseau sont imprévisibles. L'utilisateur peut fournir un nom d'hôte invalide, le réseau peut se couper, ou l'utilisateur peut vouloir annuler le scan en cours de route en appuyant sur Ctrl+C. Sans une gestion appropriée des exceptions, votre script Python plantera et affichera une trace d'erreur peu élégante à l'utilisateur.
Nous utiliserons des blocs try-except pour intercepter ces erreurs spécifiques et les gérer proprement.
Si vous modifiez toujours scanner.py, continuez à travailler dans la même session nano. Sinon, rouvrez-le :
nano ~/project/scanner.py
Mettez à jour votre code pour inclure la gestion des exceptions. Remplacez le contenu par la version mise à jour suivante :
import argparse
import sys
import socket
def get_arguments():
parser = argparse.ArgumentParser(description="A simple TCP port scanner")
parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")
return parser.parse_args()
def scan_port(ip, port):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
result = sock.connect_ex((ip, port))
if result == 0:
print(f"[+] Port {port} is OPEN")
sock.close()
except socket.gaierror:
## This exception is raised for address-related errors, like an invalid hostname
print("\n[-] Hostname could not be resolved.")
sys.exit(1)
except socket.error:
## This catches general socket errors
print("\n[-] Could not connect to server.")
sys.exit(1)
if __name__ == "__main__":
options = get_arguments()
target_ip = options.target
try:
start_port, end_port = map(int, options.ports.split('-'))
except ValueError:
print("[-] Invalid port format. Please use start-end (e.g., 1-1024)")
sys.exit(1)
print(f"[*] Scanning target {target_ip} from port {start_port} to {end_port}...\n")
try:
for port in range(start_port, end_port + 1):
scan_port(target_ip, port)
except KeyboardInterrupt:
## This catches the Ctrl+C signal from the user
print("\n[-] Scan canceled by user. Exiting...")
sys.exit(1)
print("\n[*] Scan completed.")
Enregistrez le fichier avec Ctrl+O et Entrée. Si vous souhaitez tester immédiatement la gestion des exceptions, quittez avec Ctrl+X et exécutez les commandes ci-dessous. Sinon, vous pouvez continuer directement vers l'étape finale et ne quitter nano que lorsque vous êtes prêt à exécuter le scanner terminé.
Maintenant, testons la gestion des exceptions. Essayez d'exécuter le script avec un format de port invalide pour déclencher le ValueError :
python3 scanner.py -t 127.0.0.1 -p 8000,8001
Vous devriez voir un message d'erreur propre au lieu d'un plantage Python :
[-] Invalid port format. Please use start-end (e.g., 1-1024)
Ensuite, lancez un scan sur une large plage de ports, et pendant qu'il s'exécute, appuyez sur Ctrl+C sur votre clavier pour l'annuler :
python3 scanner.py -t 127.0.0.1 -p 1-5000
Lorsque vous appuyez sur Ctrl+C, le script interceptera le KeyboardInterrupt et quittera proprement :
[*] Scanning target 127.0.0.1 from port 1 to 5000...
[-] Scan canceled by user. Exiting...
Si votre scan se termine avant que vous ne puissiez l'interrompre, relancez-le avec une plage plus large comme 1-9000, puis appuyez sur Ctrl+C pendant qu'il est encore en cours.
Ajouter le multithreading pour la vitesse
Dans cette étape finale, vous allez résoudre le problème majeur de votre scanner actuel : la vitesse.
Actuellement, votre scanner vérifie un port à la fois. Si un port est filtré et prend 1 seconde pour expirer, scanner 1 000 ports prendra 1 000 secondes. Pour corriger cela, nous utiliserons le multithreading. Le multithreading permet à votre programme d'exécuter plusieurs tâches simultanément. Au lieu d'attendre qu'une vérification de port se termine avant de commencer la suivante, nous lancerons un nouveau thread pour chaque port.
Si vous avez gardé scanner.py ouvert, continuez à le modifier. Sinon, rouvrez-le :
nano ~/project/scanner.py
Importez le module threading et mettez à jour la boucle principale pour utiliser des threads. Remplacez le contenu par cette version finale :
import argparse
import sys
import socket
import threading
def get_arguments():
parser = argparse.ArgumentParser(description="A simple TCP port scanner")
parser.add_argument("-t", "--target", dest="target", help="Target IP address", required=True)
parser.add_argument("-p", "--ports", dest="ports", help="Port range to scan (e.g., 1-1024)", default="1-1024")
return parser.parse_args()
def scan_port(ip, port):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
result = sock.connect_ex((ip, port))
if result == 0:
print(f"[+] Port {port} is OPEN")
sock.close()
except (socket.gaierror, socket.error):
pass
if __name__ == "__main__":
options = get_arguments()
target_ip = options.target
try:
start_port, end_port = map(int, options.ports.split('-'))
except ValueError:
print("[-] Invalid port format. Please use start-end (e.g., 1-1024)")
sys.exit(1)
print(f"[*] Scanning target {target_ip} from port {start_port} to {end_port}...\n")
try:
## Create a list to keep track of active threads
threads = []
for port in range(start_port, end_port + 1):
## Create a new thread for each port scan
thread = threading.Thread(target=scan_port, args=(target_ip, port))
threads.append(thread)
thread.start()
## Wait for all threads to complete before exiting
for thread in threads:
thread.join()
except KeyboardInterrupt:
print("\n[-] Scan canceled by user. Exiting...")
sys.exit(1)
print("\n[*] Scan completed.")
Enregistrez le fichier (Ctrl+O, Entrée) et quittez (Ctrl+X).
Remarquez comment nous avons ajouté threading.Thread(target=scan_port, args=(target_ip, port)). Cela indique à Python d'exécuter la fonction scan_port en arrière-plan. La boucle thread.join() garantit que le programme principal attend que tous les scans en arrière-plan soient terminés avant d'afficher "Scan completed."
Maintenant, lancez un scan sur une large plage de ports. Vous remarquerez qu'il se termine beaucoup plus rapidement que la version séquentielle précédente.
python3 scanner.py -t 127.0.0.1 -p 1-9000
La sortie ressemblera à ceci :
[*] Scanning target 127.0.0.1 from port 1 to 9000...
[+] Port 8000 is OPEN
[*] Scan completed.
Selon les services en cours d'exécution dans l'environnement de laboratoire, vous pourriez voir des ports ouverts supplémentaires en plus du 8000. C'est tout à fait normal.
Vous avez construit avec succès un scanner de ports TCP rapide et multithreadé en Python.
Résumé
Dans ce laboratoire, vous avez construit avec succès un scanner de ports TCP multithreadé personnalisé en partant de zéro avec Python. Vous avez commencé par apprendre à analyser les arguments de la ligne de commande à l'aide du module argparse pour rendre votre outil flexible. Vous vous êtes ensuite plongé dans la programmation réseau en implémentant des connexions socket pour détecter les ports ouverts. Pour garantir la robustesse de votre outil, vous avez ajouté une gestion des exceptions afin de gérer élégamment les erreurs réseau et les interruptions utilisateur. Enfin, vous avez implémenté le multithreading, transformant un script séquentiel lent en un scanner concurrent à haute vitesse.
Cette expérience pratique démystifie le fonctionnement interne des outils de scan réseau professionnels et fournit une base solide pour explorer davantage la sécurité réseau et le développement en Python.



