feat: idps + détection scan TCPConnect, SynScan

This commit is contained in:
2024-11-14 12:08:34 -05:00
parent e89442f538
commit 19d007dfff
7 changed files with 257 additions and 8 deletions

View File

@ -6,7 +6,14 @@ RUN apk -U upgrade && \
RUN pip install scapy
# Copier le script de l'idps
#COPY idps.py /idps.py
WORKDIR /app
# Lancer le script de détection
#CMD ["python", "/idps.py"]
# Copier le contenu du répertoire 'idps' du contexte de build vers '/app/idps' dans le conteneur
COPY idps /app/idps
# Autres commandes nécessaires pour ton projet
# Par exemple, pour installer des dépendances :
# RUN pip install -r /app/idps/requirements.txt (si applicable)
# Commande par défaut
CMD ["python", "/app/idps/main.py"]

View File

@ -3,8 +3,8 @@ services:
# Attaquant 1
atk1:
build:
context: Dockerfiles/.
dockerfile: Dockerfile.attaquant
context: ..
dockerfile: Demo/Dockerfiles/Dockerfile.attaquant
container_name: attaquant1
command: sleep infinity
networks:
@ -15,8 +15,8 @@ services:
# IDPS
idps:
build:
context: Dockerfiles/.
dockerfile: Dockerfile.idps
context: ..
dockerfile: Demo/Dockerfiles/Dockerfile.idps
container_name: idps
command: sleep infinity
cap_add:

View File

@ -62,4 +62,4 @@ docker compose up -d
- Noyau d'analyse de l'IDS
- Interface web pour visualiser les alertes / rechercher dedans
- Moteur de corrélation des alertes (récupération + renvoi dans MySQL).
- Moteur de corrélation des alertes (récupération + renvoi dans MySQL).

108
idps/main.py Normal file
View File

@ -0,0 +1,108 @@
from scapy.all import sniff, TCP, IP
from scapy.config import conf
conf.debug_dissector = 2
import importlib.util
import os
import time
import tcp
def load_rules(rules_dirpath = "/app/idps/rules"):
"""Charger les fonctions de règles du répertoire de règles et des sous répertoires"""
if not os.path.exists(rules_dirpath):
raise ValueError(f"Le chemin spécifié n'existe pas: {rules_dirpath}")
if not os.path.isdir(rules_dirpath):
raise ValueError(f"Le chemin spécifié n'est pas un répertoire: {rules_dirpath}")
rules_functions = {}
# Liste de répertoires à explorer
dirs_to_explore = [rules_dirpath]
# Explorer chaque répertoire / sous répertoire à la rechercher de fichier de règles
while dirs_to_explore:
current_dir = dirs_to_explore.pop()
try:
for entry in os.scandir(current_dir):
# Ignorer les liens symboliques
if entry.is_symlink():
continue
# Ajouter les répertoires dans la liste à explorer
if entry.is_dir():
dirs_to_explore.append(entry.path)
elif entry.is_file() and entry.name.endswith(".py"):
# Suppression de l'extension .py
module_name = entry.name[:-3]
# Déterminer le protocole à partir du répertoire parent
if "TCP" in entry.path:
parent_dir = "TCP"
else:
parent_dir = "WTF"
if parent_dir not in rules_functions:
rules_functions[parent_dir] = []
# Chargement du module
spec = importlib.util.spec_from_file_location(module_name, entry.path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Vérification que le module possède bien la fonction rule
if hasattr(module, "rule"):
rules_functions[parent_dir].append(module.rule)
except PermissionError:
print(f"Permission refusée pour accéder au répertoire: {current_dir}")
except OSError as e:
print(f"Erreur lors de l'accès au répertoire {current_dir}: {e}")
return rules_functions
def check_frame_w_rules(packet, rules_functions, packets):
"""Appliquer chaque règle des fonctions au paquet capturé."""
for rule_func in rules_functions:
try:
rule_func(packet, packets)
except Exception as e:
print(f"Erreur lors de l'exécution de la règle : {e}")
def packet_callback(packet, rules_functions, tcp_packets):
#print(packet)
if IP in packet and TCP in packet:
tcp_packets.add_packet(packet[IP].src, packet[TCP].sport, packet[IP].dst, packet[TCP].dport, packet[TCP].flags, time.time())
#print(tcp_packets[packet[IP].src])
check_frame_w_rules(packet, rules_functions['TCP'], tcp_packets)
tcp_packets.clean_old_packets()
def start_idps(IDS_IFACES = ["eth0","eth1"]):
"""Charge les règles et démarre l'IDPS"""
print(f"Chargement des règles...")
rules_functions = load_rules()
print(f"Les règles sont chargées")
# Opti possible: charger les règles par protocole, permettant des filtrages et donc optimiser
# le nombre de fonctions vérifiant le paquet (snort s'arrête à la première corrélation par exemple)
tcp_packets = tcp.TCP(300)
# Lancer scapy & envoyer le paquet à chaque règle de l'IDPS
sniff(iface=IDS_IFACES, prn=lambda packet: packet_callback(packet, rules_functions, tcp_packets), store=0)
#wrpcap("idps.pcap", capture)
def main():
print(f"Démarrage de l'IDPS")
start_idps()
print(f"IDPS opérationel")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,8 @@
# Seuils
TIME_WINDOW = 180
NB_SEUIL = 5
def rule(packet, tcp_packets):
if (tcp_packets.count_packet_of_type("RA", TIME_WINDOW) + tcp_packets.count_packet_of_type("SA", TIME_WINDOW)) + tcp_packets.count_packet_of_type("R", TIME_WINDOW) >= NB_SEUIL:
print(f"Alerte, seuil dépassés, risque de SynScan")

View File

@ -0,0 +1,8 @@
# Seuils
TIME_WINDOW = 180 # 180 secondes pour avoir X paquets
NB_SEUIL = 5
def rule(packet, tcp_packets):
if (tcp_packets.count_packet_of_type("A", TIME_WINDOW) + tcp_packets.count_packet_of_type("RA", TIME_WINDOW)) >= NB_SEUIL:
print(f"Alerte, seuils dépassés, risque de TCPConnectScan")

118
idps/tcp.py Normal file
View File

@ -0,0 +1,118 @@
import time
class TCP:
def __init__(self, clean_time=300):
"""Constructeur de la classe TCP
@param clean_time: temps avant qu'un paquet soit nettoyé"""
self.packets = {}
self.clean_time = clean_time
def add_packet(self, ip_src, port_src, ip_dst, port_dst, flags, timestamp):
"""Ajoute le suivi d'un paquet dans le dictionnaire"""
# Initialisation de la liste de paquets pour l'IP source
if ip_src not in self.packets:
self.packets[ip_src] = []
if flags == "S":
self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp])
return
elif flags == "SA":
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S", True)
if i is not None:
self.packets[ip_dst][i][3] = "SA"
self.packets[ip_dst][i][4] = timestamp
return
else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp])
return
elif flags == "A":
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "SA")
if i is None:
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "R", True)
if i is not None:
self.packets[ip_src][i][3] = "A"
self.packets[ip_src][i][4] = timestamp
return
else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp])
return
elif flags == "RA":
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A")
if i is None:
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
if i is not None:
self.packets[ip_src][i][3] = "RA"
self.packets[ip_src][i][4] = timestamp
return
else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp])
return
elif flags == "R":
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A")
if i is None:
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
if i is not None:
self.packets[ip_src][i][3] = "R"
self.packets[ip_src][i][4] = timestamp
return
else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp])
return
def find_packet_to_replace(self, ip_src, port_src, ip_dst, port_dst, flags, reverse=False):
"""Cherche l'indice du paquet dont le flag doit être remplacé"""
if reverse is True:
ip_src, ip_dst = ip_dst, ip_src
port_src, port_dst = port_dst, port_src
for i, [p_s, ip_d, p_d, f, stamp] in enumerate(self.packets[ip_src]):
if p_s == port_src and ip_d == ip_dst and p_d == port_dst and f == flags:
return i
return None
def clean_old_packets(self):
"""Supprime les paquets qui date de plus longtemps que le temps de clean"""
current_timestamp = time.time()
# Parcours chaque ip_source de la liste
for ip_src in list(self.packets.keys()):
# Vérification si le paquet doit être supprimé ou non
i = 0
while i < len(self.packets[ip_src]):
packet = self.packets[ip_src][i]
if packet[4] <= current_timestamp - self.clean_time:
del self.packets[ip_src][i]
else:
i += 1
# Suppression de la case de l'ip source si elle n'existe plus
if not self.packets[ip_src]:
del self.packets[ip_src]
def count_packet_of_type(self, flag, time_treshold):
"""Compte les paquets qui ont le flag choisi et qui sont dans la fenêtre de temps"""
count = 0
current_timestamp = time.time()
for ip in list(self.packets.keys()):
for packet in self.packets[ip]:
if packet[3] == flag and packet[4] >= current_timestamp - time_treshold:
count += 1
return count
def __getitem__(self, src_ip):
return self.packets.get(src_ip, None)