Comment construire un pot de miel dans Python: un guide pratique de la tromperie de sécurité
En cybersécurité, un pot de miel est un système de leurre conçu pour attirer puis détecter les attaquants potentiels qui tentent de compromettre le système. Tout comme un pot de miel assis en plein air attirerait les mouches.
Considérez ces pots de miel comme des caméras de sécurité pour votre système. Tout comme une caméra de sécurité nous aide à comprendre qui essaie de pénétrer dans un bâtiment et comment ils le font, ces pots de miel vous aideront à comprendre qui essaie d'attaquer votre système et quelles techniques ils utilisent.
À la fin de ce didacticiel, vous serez en mesure d'écrire un pot de miel de démonstration en Python et de comprendre comment fonctionnent les pots de miel.
Table des matières
Comprendre les types de pots de miel
-
Comment configurer votre environnement de développement
Comment construire le pot de miel principal
Implémentez les auditeurs de réseau
Exécutez le pot de miel
Écrire le simulateur d'attaque Honeypot
Comment analyser les données du pot de miel
Considérations de sécurité
Conclusion
Comprendre les types de pots de miel
Avant de commencer à concevoir notre propre pot de miel, comprenons rapidement leurs différents types:
Pots de miel de production : ces types de pots de miel sont placés dans un environnement de production réel et sont utilisés pour détecter de véritables attaques de sécurité. Ils sont généralement de conception simple, faciles à entretenir et à déployer, et offrent une interaction limitée pour réduire les risques.
-
Recherchez des pots de miel: ce sont des systèmes plus complexes mis en place par des chercheurs en sécurité pour étudier les modèles d'attaque, effectuer une analyse empirique sur ces modèles, collecter des échantillons de logiciels malveillants et comprendre de nouvelles techniques d'attaque qui ne sont pas découvertes auparavant. Ils imitent souvent des systèmes d'exploitation entiers ou des réseaux plutôt que de se comporter comme une application dans l'environnement de production.
Pour ce tutoriel, nous construisons un pot de miel à interaction moyenne qui enregistre les tentatives de connexion et le comportement de base de l'attaquant.
Comment configurer votre environnement de développement
Commençons par configurer votre environnement de développement en Python. Exécutez les commandes suivantes :
import socket
import sys
import datetime
import json
import threading
from pathlib import Path
# Configure logging directory
LOG_DIR = Path("honeypot_logs")
LOG_DIR.mkdir(exist_ok=True)
Nous nous en tiendrons aux bibliothèques intégrées, nous n'aurons donc pas besoin d'installer de dépendances externes. Nous stockons nos journaux dans le répertoire honeypot_logs
.
Comment construire le pot de miel principal
Notre pot de miel de base sera composé de trois éléments :
Un écouteur de réseau qui accepte les connexions
Un système de journalisation pour enregistrer les activités
Un service d'émulation de base pour interagir avec les attaquants
Commençons maintenant par initialiser la classe de pot de miel de base:
class Honeypot:
def __init__(self, bind_ip="0.0.0.0", ports=None):
self.bind_ip = bind_ip
self.ports = ports or [21, 22, 80, 443] # Default ports to monitor
self.active_connections = {}
self.log_file = LOG_DIR / f"honeypot_{datetime.datetime.now().strftime('%Y%m%d')}.json"
def log_activity(self, port, remote_ip, data):
"""Log suspicious activity with timestamp and details"""
activity = {
"timestamp": datetime.datetime.now().isoformat(),
"remote_ip": remote_ip,
"port": port,
"data": data.decode('utf-8', errors='ignore')
}
with open(self.log_file, 'a') as f:
json.dump(activity, f)
f.write('\n')
def handle_connection(self, client_socket, remote_ip, port):
"""Handle individual connections and emulate services"""
service_banners = {
21: "220 FTP server ready\r\n",
22: "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n",
80: "HTTP/1.1 200 OK\r\nServer: Apache/2.4.41 (Ubuntu)\r\n\r\n",
443: "HTTP/1.1 200 OK\r\nServer: Apache/2.4.41 (Ubuntu)\r\n\r\n"
}
try:
# Send appropriate banner for the service
if port in service_banners:
client_socket.send(service_banners[port].encode())
# Receive data from attacker
while True:
data = client_socket.recv(1024)
if not data:
break
self.log_activity(port, remote_ip, data)
# Send fake response
client_socket.send(b"Command not recognized.\r\n")
except Exception as e:
print(f"Error handling connection: {e}")
finally:
client_socket.close()
Cette classe contient de nombreuses informations importantes, passons donc en revue chaque fonction une par une.
La fonction __ init __
enregistre les numéros IP et de port sur lesquels nous hébergerons le pot de miel, ainsi que le nom/nom de fichier du fichier journal. Nous maintiendrons également un enregistrement du nombre total de connexions actives que nous avons avec le pot de miel.
La fonction log_activity
va recevoir les informations sur l'IP, les données et le port auquel l'IP a tenté de se connecter. Nous ajouterons ensuite ces informations à notre fichier journal au format JSON.
La fonction handle_connection
va imiter ces services qui s'exécuteront sur les différents ports dont nous disposons. Le pot de miel fonctionnera sur les ports 21, 22, 80 et 443. Ces services concernent respectivement FTP, SSH, HTTP et HTTPS. Ainsi, tout attaquant tentant d’interagir avec le pot de miel doit s’attendre à ces services sur ces ports.
Pour imiter le comportement de ces services, nous utiliserons les bannières de service qu'ils utilisent dans la réalité. Cette fonction enverra d'abord la bannière appropriée lorsque l'attaquant se connectera, puis recevra les données et les enregistrera. Le pot de miel enverra également une fausse réponse « Commande non reconnue » à l'attaquant.
Implémenter les écouteurs réseau
Implémentez maintenant les écouteurs de réseau qui géreront les connexions entrantes. Pour cela, nous utiliserons une programmation de socket simple. Si vous ne savez pas comment fonctionne la programmation Socket, consultez cet article qui explique certains concepts qui y sont liés.
def start_listener(self, port):
"""Start a listener on specified port"""
try:
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((self.bind_ip, port))
server.listen(5)
print(f"[*] Listening on {self.bind_ip}:{port}")
while True:
client, addr = server.accept()
print(f"[*] Accepted connection from {addr[0]}:{addr[1]}")
# Handle connection in separate thread
client_handler = threading.Thread(
target=self.handle_connection,
args=(client, addr[0], port)
)
client_handler.start()
except Exception as e:
print(f"Error starting listener on port {port}: {e}")
La fonction start_listener
démarrera le serveur et écoutera sur le port fourni. Le bind_ip
pour nous sera 0.0.0.0
, ce qui indique que le serveur écoutera sur toutes les interfaces réseau.
Désormais, nous allons gérer chaque nouvelle connexion dans un fil de discussion distinct, car il peut y avoir des cas où plusieurs attaquants tentent d'interagir avec le pot de miel ou où un script ou un outil attaquant analyse le pot de miel. Si vous ne savez pas comment fonctionne le threading, vous pouvez consulter cet article qui explique le threading et la concurrence en Python.
Assurez-vous également de mettre cette fonction dans la classe principale Honeypot
.
Exécutez le pot de miel
Créons maintenant la fonction main
qui démarrera notre pot de miel.
def main():
honeypot = Honeypot()
# Start listeners for each port in separate threads
for port in honeypot.ports:
listener_thread = threading.Thread(
target=honeypot.start_listener,
args=(port,)
)
listener_thread.daemon = True
listener_thread.start()
try:
# Keep main thread alive
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n[*] Shutting down honeypot...")
sys.exit(0)
if __name__ == "__main__":
main()
Cette fonction instancie la classe Honeypot
et démarre les écouteurs pour chacun de nos ports définis (21,22,80,443) en tant que thread distinct. Maintenant, nous allons garder vivant notre thread principal qui exécute notre programme actuel en le mettant dans une boucle infinie. Rassemblez tout cela dans un script et exécutez-le.
Écrivez le simulateur d'attaque de pot de miel
Essayons maintenant de simuler certains scénarios d'attaque et de cibler notre pot de miel afin que nous puissions collecter certaines données dans notre fichier journal JSON.
Ce simulateur nous aidera à démontrer quelques aspects importants sur les points de miel:
Modèles d'attaque réalistes : le simulateur simulera des modèles d'attaque courants tels que l'analyse des ports, les tentatives de force brute et les exploits spécifiques au service.
Intensité variable : le simulateur ajustera l'intensité de la simulation pour tester la façon dont votre pot de miel gère différentes charges.
Plusieurs types d'attaques: il démontrera différents types d'attaques que les vrais attaquants pourraient essayer, vous aidant à comprendre comment votre pot de miel réagit à chacun.
Connexions simultanées: le simulateur utilisera le filetage pour tester comment votre pot de miel gère plusieurs connexions simultanées.
# honeypot_simulator.py
import socket
import time
import random
import threading
from concurrent.futures import ThreadPoolExecutor
import argparse
class HoneypotSimulator:
"""
A class to simulate different types of connections and attacks against our honeypot.
This helps in testing the honeypot's logging and response capabilities.
"""
def __init__(self, target_ip="127.0.0.1", intensity="medium"):
# Configuration for the simulator
self.target_ip = target_ip
self.intensity = intensity
# Common ports that attackers often probe
self.target_ports = [21, 22, 23, 25, 80, 443, 3306, 5432]
# Dictionary of common commands used by attackers for different services
self.attack_patterns = {
21: [ # FTP commands
"USER admin\r\n",
"PASS admin123\r\n",
"LIST\r\n",
"STOR malware.exe\r\n"
],
22: [ # SSH attempts
"SSH-2.0-OpenSSH_7.9\r\n",
"admin:password123\n",
"root:toor\n"
],
80: [ # HTTP requests
"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n",
"POST /admin HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n",
"GET /wp-admin HTTP/1.1\r\nHost: localhost\r\n\r\n"
]
}
# Intensity settings affect the frequency and volume of simulated attacks
self.intensity_settings = {
"low": {"max_threads": 2, "delay_range": (1, 3)},
"medium": {"max_threads": 5, "delay_range": (0.5, 1.5)},
"high": {"max_threads": 10, "delay_range": (0.1, 0.5)}
}
def simulate_connection(self, port):
"""
Simulates a connection attempt to a specific port with realistic attack patterns
"""
try:
# Create a new socket connection
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
print(f"[*] Attempting connection to {self.target_ip}:{port}")
sock.connect((self.target_ip, port))
# Get banner if any
banner = sock.recv(1024)
print(f"[+] Received banner from port {port}: {banner.decode('utf-8', 'ignore').strip()}")
# Send attack patterns based on the port
if port in self.attack_patterns:
for command in self.attack_patterns[port]:
print(f"[*] Sending command to port {port}: {command.strip()}")
sock.send(command.encode())
# Wait for response
try:
response = sock.recv(1024)
print(f"[+] Received response: {response.decode('utf-8', 'ignore').strip()}")
except socket.timeout:
print(f"[-] No response received from port {port}")
# Add realistic delay between commands
time.sleep(random.uniform(*self.intensity_settings[self.intensity]["delay_range"]))
sock.close()
except ConnectionRefusedError:
print(f"[-] Connection refused on port {port}")
except socket.timeout:
print(f"[-] Connection timeout on port {port}")
except Exception as e:
print(f"[-] Error connecting to port {port}: {e}")
def simulate_port_scan(self):
"""
Simulates a basic port scan across common ports
"""
print(f"\n[*] Starting port scan simulation against {self.target_ip}")
for port in self.target_ports:
self.simulate_connection(port)
time.sleep(random.uniform(0.1, 0.3))
def simulate_brute_force(self, port):
"""
Simulates a brute force attack against a specific service
"""
common_usernames = ["admin", "root", "user", "test"]
common_passwords = ["password123", "admin123", "123456", "root"]
print(f"\n[*] Starting brute force simulation against port {port}")
for username in common_usernames:
for password in common_passwords:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
sock.connect((self.target_ip, port))
if port == 21: # FTP
sock.send(f"USER {username}\r\n".encode())
sock.recv(1024)
sock.send(f"PASS {password}\r\n".encode())
elif port == 22: # SSH
sock.send(f"{username}:{password}\n".encode())
sock.close()
time.sleep(random.uniform(0.1, 0.3))
except Exception as e:
print(f"[-] Error in brute force attempt: {e}")
def run_continuous_simulation(self, duration=300):
"""
Runs a continuous simulation for a specified duration
"""
print(f"\n[*] Starting continuous simulation for {duration} seconds")
print(f"[*] Intensity level: {self.intensity}")
end_time = time.time() + duration
with ThreadPoolExecutor(
max_workers=self.intensity_settings[self.intensity]["max_threads"]
) as executor:
while time.time() < end_time:
# Mix of different attack patterns
simulation_choices = [
lambda: self.simulate_port_scan(),
lambda: self.simulate_brute_force(21),
lambda: self.simulate_brute_force(22),
lambda: self.simulate_connection(80)
]
# Randomly choose and execute an attack pattern
executor.submit(random.choice(simulation_choices))
time.sleep(random.uniform(*self.intensity_settings[self.intensity]["delay_range"]))
def main():
"""
Main function to run the honeypot simulator with command-line arguments
"""
parser = argparse.ArgumentParser(description="Honeypot Attack Simulator")
parser.add_argument("--target", default="127.0.0.1", help="Target IP address")
parser.add_argument(
"--intensity",
choices=["low", "medium", "high"],
default="medium",
help="Simulation intensity level"
)
parser.add_argument(
"--duration",
type=int,
default=300,
help="Simulation duration in seconds"
)
args = parser.parse_args()
simulator = HoneypotSimulator(args.target, args.intensity)
try:
simulator.run_continuous_simulation(args.duration)
except KeyboardInterrupt:
print("\n[*] Simulation interrupted by user")
except Exception as e:
print(f"[-] Simulation error: {e}")
finally:
print("\n[*] Simulation complete")
if __name__ == "__main__":
main()
Nous avons beaucoup de choses dans ce script de simulation, alors décomposons-le un par un. J'ai également ajouté des commentaires pour chaque fonction et opération pour rendre cela un peu plus lisible dans le code.
Nous avons d'abord notre classe d'utilité appelée honeypotsimulator
. Dans cette classe, nous avons la fonction __ init __
qui configure la configuration de base de notre simulateur. Il faut deux paramètres: une adresse IP cible (défaut pour localhost) et un niveau d'intensité (défautant vers "Medium").
Nous définissons également trois composants importants : les ports cibles à sonder (services communs comme FTP, SSH, HTTP), les modèles d'attaque spécifiques à chaque service (comme les tentatives de connexion et les commandes) et les paramètres d'intensité qui contrôlent le degré d'agressivité de notre simulation via le thread. décomptes et délais de synchronisation.
La fonction simulate_connection
gère les tentatives de connexion individuelles à un port spécifique. Il crée une connexion socket, essaie d'obtenir toutes les bannières de service (comme les informations de version SSH), puis envoie les commandes d'attaque appropriées en fonction du type de service. Nous avons ajouté une gestion des erreurs pour les problèmes de réseau courants et également ajouté des délais réalistes entre les commandes pour imiter l'interaction humaine.
Notre fonction simulate_port_scan
agit comme un outil de reconnaissance, qui vérifiera systématiquement chaque port de notre liste cible. C'est similaire à la façon dont des outils comme nmap
fonctionnent - passer par les ports un par un pour voir quels services sont disponibles. Pour chaque port, il appelle la fonction simulate_connection
et ajoute de petits retards aléatoires pour rendre le modèle de balayage plus naturel.
La fonction simulate_brute_force
maintient des listes de noms d'utilisateur et de mots de passe courants, en essayant différentes combinaisons avec des services comme FTP et SSH. Pour chaque tentative, il crée une nouvelle connexion, envoie les informations de connexion au format correct pour ce service, puis ferme la connexion. Cela nous aide à tester dans quelle mesure le pot de miel détecte et enregistre les attaques de credential stuffing.
La fonction run_continuous_simulation
s'exécute pour une durée spécifiée, en choisissant au hasard entre différents types d'attaques comme la numérisation de port, la force brute ou des attaques de service spécifiques. Il utilise witerpoolExecutor
de Python pour exécuter plusieurs attaques simultanément en fonction du niveau d'intensité spécifié.
Enfin, nous avons la fonction Main
qui fournit l'interface de ligne de commande pour le simulateur. Il utilise argParse
pour gérer les arguments de ligne de commande, permettant aux utilisateurs de spécifier l'IP cible, le niveau d'intensité et la durée de la simulation. Il crée une instance de la classe honeypotsimulator
et gère l'exécution globale, y compris une gestion appropriée des interruptions et des erreurs utilisateur.
Après avoir placé le code du simulateur dans un script séparé, exécutez-le avec la commande suivante :
# Run with default settings (medium intensity, localhost, 5 minutes)
python honeypot_simulator.py
# Run with custom settings
python honeypot_simulator.py --target 192.168.1.100 --intensity high --duration 600
Étant donné que nous exécutons le pot de miel ainsi que le simulateur sur la même machine localement, la cible sera localhost
. Mais cela peut être autre chose dans un scénario réel ou si vous exécutez le pot de miel dans une machine virtuelle ou une machine différente - alors assurez-vous de confirmer l'IP avant d'exécuter le simulateur.
Comment analyser les données du pot de miel
Écrivons rapidement une fonction d'assistance qui nous permettra d'analyser toutes les données collectées par le pot de miel. Depuis que nous l'avons stocké dans un fichier journal JSON, nous pouvons l'analyser facilement en utilisant le package JSON intégré.
import datetime
import json
def analyze_logs(log_file):
"""Enhanced honeypot log analysis with temporal and behavioral patterns"""
ip_analysis = {}
port_analysis = {}
hourly_attacks = {}
data_patterns = {}
# Track session patterns
ip_sessions = {}
attack_timeline = []
with open(log_file, 'r') as f:
for line in f:
try:
activity = json.loads(line)
timestamp = datetime.datetime.fromisoformat(activity['timestamp'])
ip = activity['remote_ip']
port = activity['port']
data = activity['data']
# Initialize IP tracking if new
if ip not in ip_analysis:
ip_analysis[ip] = {
'total_attempts': 0,
'first_seen': timestamp,
'last_seen': timestamp,
'targeted_ports': set(),
'unique_payloads': set(),
'session_count': 0
}
# Update IP statistics
ip_analysis[ip]['total_attempts'] += 1
ip_analysis[ip]['last_seen'] = timestamp
ip_analysis[ip]['targeted_ports'].add(port)
ip_analysis[ip]['unique_payloads'].add(data.strip())
# Track hourly patterns
hour = timestamp.hour
hourly_attacks[hour] = hourly_attacks.get(hour, 0) + 1
# Analyze port targeting patterns
if port not in port_analysis:
port_analysis[port] = {
'total_attempts': 0,
'unique_ips': set(),
'unique_payloads': set()
}
port_analysis[port]['total_attempts'] += 1
port_analysis[port]['unique_ips'].add(ip)
port_analysis[port]['unique_payloads'].add(data.strip())
# Track payload patterns
if data.strip():
data_patterns[data.strip()] = data_patterns.get(data.strip(), 0) + 1
# Track attack timeline
attack_timeline.append({
'timestamp': timestamp,
'ip': ip,
'port': port
})
except (json.JSONDecodeError, KeyError) as e:
continue
# Analysis Report Generation
print("\n=== Honeypot Analysis Report ===")
# 1. IP-based Analysis
print("\nTop 10 Most Active IPs:")
sorted_ips = sorted(ip_analysis.items(),
key=lambda x: x[1]['total_attempts'],
reverse=True)[:10]
for ip, stats in sorted_ips:
duration = stats['last_seen'] - stats['first_seen']
print(f"\nIP: {ip}")
print(f"Total Attempts: {stats['total_attempts']}")
print(f"Active Duration: {duration}")
print(f"Unique Ports Targeted: {len(stats['targeted_ports'])}")
print(f"Unique Payloads: {len(stats['unique_payloads'])}")
# 2. Port Analysis
print("\nPort Targeting Analysis:")
sorted_ports = sorted(port_analysis.items(),
key=lambda x: x[1]['total_attempts'],
reverse=True)
for port, stats in sorted_ports:
print(f"\nPort {port}:")
print(f"Total Attempts: {stats['total_attempts']}")
print(f"Unique Attackers: {len(stats['unique_ips'])}")
print(f"Unique Payloads: {len(stats['unique_payloads'])}")
# 3. Temporal Analysis
print("\nHourly Attack Distribution:")
for hour in sorted(hourly_attacks.keys()):
print(f"Hour {hour:02d}: {hourly_attacks[hour]} attempts")
# 4. Attack Sophistication Analysis
print("\nAttacker Sophistication Analysis:")
for ip, stats in sorted_ips:
sophistication_score = (
len(stats['targeted_ports']) * 0.4 + # Port diversity
len(stats['unique_payloads']) * 0.6 # Payload diversity
)
print(f"IP {ip}: Sophistication Score {sophistication_score:.2f}")
# 5. Common Payload Patterns
print("\nTop 10 Most Common Payloads:")
sorted_payloads = sorted(data_patterns.items(),
key=lambda x: x[1],
reverse=True)[:10]
for payload, count in sorted_payloads:
if len(payload) > 50: # Truncate long payloads
payload = payload[:50] + "..."
print(f"Count {count}: {payload}")
Vous pouvez le placer dans un fichier de script séparé et appeler la fonction sur les journaux JSON. Cette fonction nous fournira des informations complètes à partir du fichier JSON en fonction des données collectées.
Notre analyse commence par regrouper les données en plusieurs catégories telles que les statistiques basées sur IP, les modèles de ciblage des ports, les distributions d'attaques horaires et les caractéristiques de la charge utile. Pour chaque IP, nous suivons les tentatives totales, les premiers et derniers temps vues, les ports ciblés et les charges utiles uniques. Cela nous aidera à construire des profils uniques pour les attaquants.
Nous examinons également ici des modèles d'attaque à base de port qui surveillent les ports les plus fréquemment ciblés et par le nombre d'attaquants uniques. Nous effectuons également une analyse de sophistication d'attaque qui nous aide à identifier les attaquants ciblés, en considérant des facteurs tels que les ports ciblés et les charges utiles uniques utilisées. Cette analyse est utilisée pour séparer les activités de balayage simples et les attaques sophistiquées.
L'analyse temporelle nous aide à identifier des modèles de tentatives d'attaque horaires, révélant des modèles de timing d'attaque et d'éventuelles campagnes de ciblage automatisées. Enfin, nous publions les charges utiles fréquemment vues pour identifier les chaînes d’attaque ou les commandes fréquemment vues.
Considérations de sécurité
Lors du déploiement de ce pot de miel, assurez-vous de considérer les mesures de sécurité suivantes:
Exécutez votre pot de miel dans un environnement isolé. Généralement à l'intérieur d'une machine virtuelle, ou sur votre machine locale qui est derrière un NAT et un pare-feu.
Exécutez le pot de miel avec un minimum de privilèges de système (généralement pas comme racine) pour réduire le risque s'il est compromis.
Soyez prudent avec les données collectées si vous envisagez de les déployer en tant que pot de miel de production ou de recherche, car elles peuvent contenir des logiciels malveillants ou des informations sensibles.
Mettez en œuvre des mécanismes de surveillance robustes pour détecter les tentatives pour sortir de l'environnement du pot de miel.
Conclusion
Avec cela, nous avons construit notre pot de miel, écrit un simulateur pour simuler des attaques pour notre pot de miel et analysé les données de nos journaux de pot de miel pour faire quelques inférences simples. C'est un excellent moyen de comprendre les concepts de sécurité offensifs et défensifs. Vous pouvez envisager de vous appuyer sur cela pour créer des systèmes de détection plus complexes et penser à ajouter des fonctionnalités telles que :
Émulation de service dynamique basée sur le comportement d'attaque
Intégration avec des systèmes d'intelligence de menace qui effectueront une meilleure analyse d'inférence de ces journaux collectés
Rassemblez des journaux même complets au-delà des données IP, port et réseau grâce à des mécanismes de journalisation avancés
Ajouter des capacités d'apprentissage automatique pour détecter les modèles d'attaque
N'oubliez pas que même si les pots de miel sont de puissants outils de sécurité, ils devraient faire partie d'une stratégie de sécurité défensive complète, pas la seule ligne de défense.
J'espère que vous avez appris sur le fonctionnement des points de miel, quel est également leur objectif ainsi qu'un peu de programmation Python!