feat: protection system for the idps

This commit is contained in:
Oxbian 2024-11-23 12:15:10 -05:00
parent 4b84c4ebb1
commit c4df869596
Signed by: Oxbian
GPG Key ID: 0E8F319FD43747E5
16 changed files with 259 additions and 145 deletions

View File

@ -6,26 +6,36 @@
"db_user": "sidps", "db_user": "sidps",
"db_password": "SUPERPASSWORD", "db_password": "SUPERPASSWORD",
"db_port": "3306", "db_port": "3306",
"protection": 1,
"cef_version": 1, "cef_version": 1,
"device_product": "SIDPS", "device_product": "SIDPS",
"device_vendor": "ArKa", "device_vendor": "ArKa",
"device_version": "vAlpha", "device_version": "vAlpha",
"synscan_time": 180, "synscan_time": 180,
"synscan_count": 5, "synscan_count": 5,
"synscan_bantime": 300,
"tcpconnectscan_time": 180, "tcpconnectscan_time": 180,
"tcpconnectscan_count": 5, "tcpconnectscan_count": 5,
"tcpconnectscan_bantime": 300,
"ackscan_time": 180, "ackscan_time": 180,
"ackscan_count": 5, "ackscan_count": 5,
"ackscan_bantime": 300,
"finscan_time": 180, "finscan_time": 180,
"finscan_count": 5, "finscan_count": 5,
"finscan_bantime": 300,
"nullscan_time": 180, "nullscan_time": 180,
"nullscan_count": 5, "nullscan_count": 5,
"nullscan_bantime": 300,
"xmasscan_time": 180, "xmasscan_time": 180,
"xmasscan_count": 5, "xmasscan_count": 5,
"xmasscan_bantime": 300,
"synflood_time": 60, "synflood_time": 60,
"synflood_count": 100, "synflood_count": 100,
"synflood_bantime": 300,
"tcpconnectflood_time": 60, "tcpconnectflood_time": 60,
"tcpconnectflood_count": 100, "tcpconnectflood_count": 100,
"tcpconnectflood_bantime": 300,
"syndos_time": 60, "syndos_time": 60,
"syndos_count": 100 "syndos_count": 100,
"syndos_bantime": 300
} }

View File

@ -6,26 +6,36 @@
"db_user": "sidps", "db_user": "sidps",
"db_password": "SUPERPASSWORD", "db_password": "SUPERPASSWORD",
"db_port": "3306", "db_port": "3306",
"protection": 0,
"cef_version": 1, "cef_version": 1,
"device_product": "Sonde IDS", "device_product": "Sonde IDS",
"device_vendor": "ArKa", "device_vendor": "ArKa",
"device_version": "vAlpha", "device_version": "vAlpha",
"synscan_time": 180, "synscan_time": 180,
"synscan_count": 5, "synscan_count": 5,
"synscan_bantime": 300,
"tcpconnectscan_time": 180, "tcpconnectscan_time": 180,
"tcpconnectscan_count": 5, "tcpconnectscan_count": 5,
"tcpconnectscan_bantime": 300,
"ackscan_time": 180, "ackscan_time": 180,
"ackscan_count": 5, "ackscan_count": 5,
"ackscan_bantime": 300,
"finscan_time": 180, "finscan_time": 180,
"finscan_count": 5, "finscan_count": 5,
"finscan_bantime": 300,
"nullscan_time": 180, "nullscan_time": 180,
"nullscan_count": 5, "nullscan_count": 5,
"nullscan_bantime": 300,
"xmasscan_time": 180, "xmasscan_time": 180,
"xmasscan_count": 5, "xmasscan_count": 5,
"xmasscan_bantime": 300,
"synflood_time": 60, "synflood_time": 60,
"synflood_count": 100, "synflood_count": 100,
"synflood_bantime": 300,
"tcpconnectflood_time": 60, "tcpconnectflood_time": 60,
"tcpconnectflood_count": 100, "tcpconnectflood_count": 100,
"tcpconnectflood_bantime": 300,
"syndos_time": 60, "syndos_time": 60,
"syndos_count": 100 "syndos_count": 100,
"syndos_bantime": 300
} }

View File

@ -12,20 +12,29 @@
"device_version": "vAlpha", "device_version": "vAlpha",
"synscan_time": 180, "synscan_time": 180,
"synscan_count": 5, "synscan_count": 5,
"synscan_bantime": 300,
"tcpconnectscan_time": 180, "tcpconnectscan_time": 180,
"tcpconnectscan_count": 5, "tcpconnectscan_count": 5,
"tcpconnectscan_bantime": 300,
"ackscan_time": 180, "ackscan_time": 180,
"ackscan_count": 5, "ackscan_count": 5,
"ackscan_bantime": 300,
"finscan_time": 180, "finscan_time": 180,
"finscan_count": 5, "finscan_count": 5,
"finscan_bantime": 300,
"nullscan_time": 180, "nullscan_time": 180,
"nullscan_count": 5, "nullscan_count": 5,
"nullscan_bantime": 300,
"xmasscan_time": 180, "xmasscan_time": 180,
"xmasscan_count": 5, "xmasscan_count": 5,
"xmasscan_bantime": 300,
"synflood_time": 60, "synflood_time": 60,
"synflood_count": 100, "synflood_count": 100,
"synflood_bantime": 300,
"tcpconnectflood_time": 60, "tcpconnectflood_time": 60,
"tcpconnectflood_count": 100, "tcpconnectflood_count": 100,
"tcpconnectflood_bantime": 300,
"syndos_time": 60, "syndos_time": 60,
"syndos_count": 100 "syndos_count": 100,
"syndos_bantime": 300
} }

View File

@ -46,13 +46,13 @@ class Database:
# Paramètres pour la requête SQL # Paramètres pour la requête SQL
params = { params = {
"cef_version": self.get_key("cef_version", 1), "cef_version": self.config.get("cef_version", 1),
"date_alerte": date_alert, "date_alerte": date_alert,
"agent_severity": agent_severity, "agent_severity": agent_severity,
"device_event_class_id": device_event_class_id, "device_event_class_id": device_event_class_id,
"device_product": self.get_key("device_product", "SIDPS"), "device_product": self.config.get("device_product", "SIDPS"),
"device_vendor": self.get_key("device_vendor", "ArKa"), "device_vendor": self.config.get("device_vendor", "ArKa"),
"device_version": self.get_key("device_version", "vAlpha"), "device_version": self.config.get("device_version", "vAlpha"),
"name": name, "name": name,
"src": src, "src": src,
"dst": dst, "dst": dst,
@ -72,10 +72,3 @@ class Database:
cursor.close() cursor.close()
except mysql.connector.Error as err: except mysql.connector.Error as err:
print("Erreur lors de l'envoi de l'alerte: {}".format(err)) print("Erreur lors de l'envoi de l'alerte: {}".format(err))
def get_key(self, key, default_val):
"""Donne le contenue d'un paramètre spécifique de la config
@param key: clé du paramètre souhaité
@param default_val: valeur par défaut si la clé n'existe pas"""
return self.config.get(key, default_val)

View File

@ -1,13 +1,48 @@
from scapy.all import sniff, TCP, IP
from scapy.config import conf
conf.debug_dissector = 2
import importlib.util import importlib.util
import os import os
import time import time
import tcp import tcp
import database import database
import json import json
import protection
from scapy.all import sniff, TCP, IP
from scapy.config import conf
conf.debug_dissector = 2
def check_frame_w_rules(packet, rules_functions, objets):
"""Appliquer chaque règle des fonctions au paquet capturé.
@param packet: Paquet actuel à analyser
@param rules_functions: liste de fonctions de règles
@param objets: Dictionnaire contenant le dictionnaire de config, la liste des paquets tcp précédents,
la db, et le gestionnaire de règles Iptables"""
for rule_func in rules_functions:
try:
rule_func(packet, objets)
except Exception as e:
print(f"Erreur lors de l'exécution de la règle : {e}")
def packet_callback(packet, rules_functions, objets):
"""Callback réception d'un paquet
@param packet: Paquet actuel à classer
@param rules_functions: liste des fonctions de règles
@param objets: Dictionnaire contenant le dictionnaire de config, la liste des paquets tcp précédents,
la db, et le gestionnaire de règles Iptables"""
# Nettoyage des règles et paquets TCP dépassé
objets["iptables_manager"].del_rules()
objets["tcp_packets"].clean_old_packets()
if IP in packet and TCP in packet:
packet_origin = objets["tcp_packets"].add_packet(packet[IP].src, packet[TCP].sport, packet[IP].dst, packet[TCP].dport, packet[TCP].flags, time.time())
# Stockage du paquet originel lié à ce paquet pour identifier la provenance de l'attaque
objets['pkt_origin'] = packet_origin
check_frame_w_rules(packet, rules_functions['TCP'], objets)
def load_rules(rules_dirpath = "/app/idps/rules"): def load_rules(rules_dirpath = "/app/idps/rules"):
@ -68,37 +103,6 @@ def load_rules(rules_dirpath = "/app/idps/rules"):
return rules_functions return rules_functions
def check_frame_w_rules(packet, rules_functions, packets, db):
"""Appliquer chaque règle des fonctions au paquet capturé.
@param packet: Paquet actuel à analyser
@param rules_functions: liste de fonctions de règles
@param packets: liste des paquets précédents (utile pour TCP)
@param db: Objet database pour envoyer les alertes à la BDD
"""
for rule_func in rules_functions:
try:
rule_func(packet, packets, db)
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, db):
"""Callback réception d'un paquet
@param packet: Paquet actuel à classer
@param rules_functions: liste des fonctions de règles
@param tcp_packets: Objet contenant une liste des paquets tcp précédents
@param db: Objet database pour envoyer des alertes à la BDD
"""
#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, db)
tcp_packets.clean_old_packets()
def read_config(config_filepath='config.json'): def read_config(config_filepath='config.json'):
"""Charge les configurations depuis le fichier de config""" """Charge les configurations depuis le fichier de config"""
@ -115,29 +119,29 @@ def read_config(config_filepath='config.json'):
def start_idps(): def start_idps():
"""Charge les règles et démarre l'IDPS""" """Charge les règles et démarre l'IDPS"""
print(f"Récupération des configurations") print("Récupération des configurations")
config = read_config() config = read_config()
print(f"Configurations chargées") print("Configurations chargées")
print(f"Chargement des règles...") print("Chargement des règles...")
rules_functions = load_rules(config["rules_dirpath"]) rules_functions = load_rules(config["rules_dirpath"])
print(f"Les règles sont chargées") print("Les règles sont chargées")
print(f"Connexion à la base de données") print("Connexion à la base de données")
db = database.Database(config) db = database.Database(config)
print(f"Connexion réussite à la base de données") print("Connexion réussite à la base de donné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) tcp_packets = tcp.TCP(300)
protection_system = protection.Protection(config["protection"])
objets = {"config": config, "database": db, "tcp_packets": tcp_packets, "iptables_manager": protection_system}
# Lancer scapy & envoyer le paquet à chaque règle de l'IDPS # Lancer scapy & envoyer le paquet à chaque règle de l'IDPS
sniff(iface=config["ifaces"], prn=lambda packet: packet_callback(packet, rules_functions, tcp_packets, db), store=0) sniff(iface=config["ifaces"], prn=lambda packet: packet_callback(packet, rules_functions, objets), store=0)
def main(): def main():
print(f"Démarrage de l'IDPS") print("Démarrage de l'IDPS")
start_idps() start_idps()

49
idps/protection.py Normal file
View File

@ -0,0 +1,49 @@
import subprocess
import time
class Protection:
"""Classe pour activer la protection du système avec iptables"""
def __init__(self, activate = 0):
"""Initialisation de la protection avec une liste pour stockées les règles créer"""
self.rules = []
self.activate = int(activate)
def add_rule(self, rule, duration):
"""Ajouter une règle dans iptables
@param rule: Règle à ajouter
@param duration: Durée d'execution de la règle"""
print(f"Rule: {rule}, {duration}, {self.activate}")
if self.activate == 0:
return
print("Rule run")
try:
subprocess.run(rule.split(' '), check=True)
print(f"[iptables] Règle ajouter {rule}")
self.rules.append([rule, time.time(), duration])
except subprocess.CalledProcessError as e:
print(f"[iptables] Erreur suppression de la règle {rule}: {e}")
def del_rules(self):
"""Supprimer les règles obsolètes de l'iptables"""
if self.activate is False:
return
curr = time.time()
for i, elt in enumerate(self.rules, 0):
if elt[1] + elt[2] <= curr:
self.del_rule(self, elt[0].replace("-I", "-D"), i)
def del_rule(self, rule, i):
"""Supprimer une règle dans iptables"""
try:
subprocess.run(rule.split(' '), check=True)
print("[iptables] Règle supprimer {rule}")
self.rules.pop(i)
except subprocess.CalledProcessError as e:
print(f"[iptables] Erreur suppression de la règle {rule}: {e}")

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
""" """
Règle SYN Dos: Règle SYN Dos:
Un SYN DOS va envoyer des requêtes TCP avec le flag SYN en très grand nombre afin de surcharger le serveur ou la cible avec des grosses quantités de données Un SYN DOS va envoyer des requêtes TCP avec le flag SYN en très grand nombre afin de surcharger le serveur ou la cible avec des grosses quantités de données
@ -15,17 +15,19 @@ def rule(packet, tcp_packets, db):
return return
# Initialisation des paramètres à partir de la configuration si nécessaire # Initialisation des paramètres à partir de la configuration si nécessaire
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("synsdos_time", 60) rule.time_window = objets["config"].get("synsdos_time", 60)
rule.seuil = db.get_key("syndos_count", 100) rule.seuil = objets["config"].get("syndos_count", 100)
rule.ban_time = objets["config"].get("syndos_bantime", 300)
# Comptage des paquets TCP correspondant aux motifs spécifiques # Comptage des paquets TCP correspondant aux motifs spécifiques
syndeny_count = tcp_packets.count_packet_of_type(["S", "RA"], rule.time_window, True) syndeny_count = objets["tcp_packets"].count_packet_of_type(["S", "RA"], rule.time_window, True)
synaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "R"], rule.time_window, True) synaccept_count = objets["tcp_packets"].count_packet_of_type(["S", "SA", "R"], rule.time_window, True)
# Détection si le seuil est dépassé et que la charge utile des paquets est non nulle # Détection si le seuil est dépassé et que la charge utile des paquets est non nulle
if (syndeny_count + synaccept_count >= rule.seuil and len(packet['TCP'].payload) > 0): if (syndeny_count + synaccept_count >= rule.seuil and len(packet['TCP'].payload) > 0):
db.send_alert(datetime.now(), 5, None, "Syn DOS", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->Reset et Syn->Reset ACK", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "Syn DOS", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->Reset et Syn->Reset ACK", act="Alerte")
objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuil dépassé, risque de Syn DOS détecté.") print("Alerte, seuil dépassé, risque de Syn DOS détecté.")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -34,3 +36,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
""" """
Règle SYN Flood: Règle SYN Flood:
Un SYN Flood va envoyer des requêtes TCP avec le flag SYN en très grand nombre afin de surcharger le serveur ou la cible Un SYN Flood va envoyer des requêtes TCP avec le flag SYN en très grand nombre afin de surcharger le serveur ou la cible
@ -15,17 +15,19 @@ def rule(packet, tcp_packets, db):
return return
# Initialisation des paramètres à partir de la configuration si nécessaire # Initialisation des paramètres à partir de la configuration si nécessaire
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("synsflood_time", 60) rule.time_window = objets["config"].get("synsflood_time", 60)
rule.seuil = db.get_key("synflood_count", 100) rule.seuil = objets["config"].get("synflood_count", 100)
rule.ban_time = objets["config"].get("synflood_bantime", 300)
# Comptage des paquets TCP correspondant aux motifs spécifiques # Comptage des paquets TCP correspondant aux motifs spécifiques
syndeny_count = tcp_packets.count_packet_of_type(["S", "RA"], rule.time_window, True) syndeny_count = objets["tcp_packets"].count_packet_of_type(["S", "RA"], rule.time_window, True)
synaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "R"], rule.time_window, True) synaccept_count = objets["tcp_packets"].count_packet_of_type(["S", "SA", "R"], rule.time_window, True)
# Détection si le seuil est dépassé # Détection si le seuil est dépassé
if (syndeny_count + synaccept_count >= rule.seuil): if (syndeny_count + synaccept_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "Syn flood", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->Reset et Syn->Reset ACK", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "Syn flood", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->Reset et Syn->Reset ACK", act="Alerte")
objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuil dépassé, risque de Syn Flood détecté.") print("Alerte, seuil dépassé, risque de Syn Flood détecté.")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -34,3 +36,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
"""Règle TCPConnect Flood: """Règle TCPConnect Flood:
Un flood TCP connect va effectuer une connexion TCP en très grand nombre Un flood TCP connect va effectuer une connexion TCP en très grand nombre
Si le port est ouvert le serveur acceptera la connexion SYN -> SYN ACK -> ACK -> Reset ACK Si le port est ouvert le serveur acceptera la connexion SYN -> SYN ACK -> ACK -> Reset ACK
@ -13,17 +13,19 @@ def rule(packet, tcp_packets, db):
return return
# Vérification si nécessaire de récupérer les variables depuis la config # Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("tcpconnectflood_time", 60) rule.time_window = objets["config"].get("tcpconnectflood_time", 60)
rule.seuil = db.get_key("tcpconnectflood_count", 100) rule.seuil = objets["config"].get("tcpconnectflood_count", 100)
rule.ban_time = objets["config"].get("tcpconnectflood_bantime", 300)
# Comptage du nombre de scan tcp connect acceptés et refusés # Comptage du nombre de scan tcp connect acceptés et refusés
tcpconnectdeny_count = tcp_packets.count_packet_of_type(["S", "RA"], rule.time_window, True) tcpconnectdeny_count = objets["tcp_packets"].count_packet_of_type(["S", "RA"], rule.time_window, True)
tcpconnectaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "A", "RA"], rule.time_window, True) tcpconnectaccept_count = objets["tcp_packets"].count_packet_of_type(["S", "SA", "A", "RA"], rule.time_window, True)
if (tcpconnectaccept_count + tcpconnectdeny_count >= rule.seuil): if (tcpconnectaccept_count + tcpconnectdeny_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "TCPConnect Flood", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->ACK->Reset ACK et Syn->Reset ACK", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "TCPConnect Flood", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->ACK->Reset ACK et Syn->Reset ACK", act="Alerte")
print(f"Alerte, seuils dépassés, risque de TCPconnect Flood") objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuils dépassés, risque de TCPconnect Flood")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -31,4 +33,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
"""Règle ACK Scan: """Règle ACK Scan:
Un ACK Scan va envoyer des requêtes TCP avec le flag ACK Un ACK Scan va envoyer des requêtes TCP avec le flag ACK
Si le firewall ne bloque pas, alors il répond avec le flag Reset Si le firewall ne bloque pas, alors il répond avec le flag Reset
@ -12,17 +12,19 @@ def rule(packet, tcp_packets, db):
return return
# Vérification si nécessaire de récupérer les variables depuis la config # Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("ackscan_time", 180) rule.time_window = objets["config"].get("ackscan_time", 180)
rule.seuil = db.get_key("ackscan_count", 5) rule.seuil = objets["config"].get("ackscan_count", 5)
rule.ban_time = objets["config"].get("ackscan_bantime", 300)
# Comptage nombre de scan ack acceptés et refusés # Comptage nombre de scan ack acceptés et refusés
ackdeny_count = tcp_packets.count_packet_of_type(["A", "R"], rule.time_window, True) ackdeny_count = objets["tcp_packets"].count_packet_of_type(["A", "R"], rule.time_window, True)
ackaccept_count = tcp_packets.count_packet_of_type(["A"], rule.time_window, True) ackaccept_count = objets["tcp_packets"].count_packet_of_type(["A"], rule.time_window, True)
if (ackaccept_count + ackdeny_count >= rule.seuil): if (ackaccept_count + ackdeny_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "ACK scan", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Ack->Reset et Ack pas de réponse", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "ACK scan", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Ack->Reset et Ack pas de réponse", act="Alerte")
print(f"Alerte, seuil dépassés, risque d'Ack scan") print("Alerte, seuil dépassés, risque d'Ack scan")
objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
rule.cooldown = time.time() rule.cooldown = time.time()
@ -30,3 +32,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,27 +2,30 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
"""Règle Fin Scan: """Règle Fin Scan:
Un Fin Scan va envoyer des requêtes TCP avec le flag Fin Un Fin Scan va envoyer des requêtes TCP avec le flag Fin
Si le port est ouvert alors le serveur répondra pas Si le port est ouvert alors le serveur répondra pas
Sinon le port est fermé et le serveur répondra: Reset ACK Sinon le port est fermé et le serveur répondra: Reset ACK
""" """
if (rule.cooldown + rule.time_window > time.time()): if (rule.cooldown + rule.time_window > time.time()):
return return
# Vérification si nécessaire de récupérer les variables depuis la config # Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("finscan_time", 180) rule.time_window = objets["config"].get("finscan_time", 180)
rule.seuil = db.get_key("finscan_count", 5) rule.seuil = objets["config"].get("finscan_count", 5)
rule.ban_time = objets["config"].get("finscan_bantime", 300)
# Comptage du nombre de scan fin acceptés et refusés # Comptage du nombre de scan fin acceptés et refusés
findeny_count = tcp_packets.count_packet_of_type(["F", "RA"], rule.time_window, True) findeny_count = objets["tcp_packets"].count_packet_of_type(["F", "RA"], rule.time_window, True)
finaccept_count = tcp_packets.count_packet_of_type(["F"], rule.time_window, True) finaccept_count = objets["tcp_packets"].count_packet_of_type(["F"], rule.time_window, True)
if (findeny_count + finaccept_count >= rule.seuil): if (findeny_count + finaccept_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "Fin scan", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Fin->Reset Ack et Fin->rien", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "Fin scan", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Fin->Reset Ack et Fin->rien", act="Alerte")
print(f"Alerte, seuil dépassés, risque de Fin Scan") objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuil dépassés, risque de Fin Scan")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -30,3 +33,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
"""Règle Null Scan: """Règle Null Scan:
Un Null Scan va envoyer des requêtes TCP avec aucun flag d'actif Un Null Scan va envoyer des requêtes TCP avec aucun flag d'actif
Si le port est ouvert alors le serveur ne répondra pas Si le port est ouvert alors le serveur ne répondra pas
@ -12,17 +12,19 @@ def rule(packet, tcp_packets, db):
return return
# Vérification si nécessaire de récupérer les variables depuis la config # Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("nullscan_time", 180) rule.time_window = objets["config"].get("nullscan_time", 180)
rule.seuil = db.get_key("nullscan_count", 5) rule.seuil = objets["config"].get("nullscan_count", 5)
rule.ban_time = objets["config"].get("nullscan_bantime", 300)
# Comptage du nombre de scan null acceptés et refusés # Comptage du nombre de scan null acceptés et refusés
nulldeny_count = tcp_packets.count_packet_of_type(["", "RA"], rule.time_window, True) nulldeny_count = objets["tcp_packets"].count_packet_of_type(["", "RA"], rule.time_window, True)
nullaccept_count = tcp_packets.count_packet_of_type([""], rule.time_window, True) nullaccept_count = objets["tcp_packets"].count_packet_of_type([""], rule.time_window, True)
if (nulldeny_count + nulldeny_count >= rule.seuil): if (nullaccept_count + nulldeny_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "Null scan", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de None->Reset Ack et None -> rien", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "Null scan", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de None->Reset Ack et None -> rien", act="Alerte")
print(f"Alerte, seuil dépassés, risque de Null Scan") objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuil dépassés, risque de Null Scan")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -30,3 +32,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
"""Règle SYN Scan: """Règle SYN Scan:
Un SYNScan va envoyer des requêtes TCP avec le flag SYN Un SYNScan va envoyer des requêtes TCP avec le flag SYN
Si le port est ouvert alors le serveur répondra: Syn ACK, puis le client Reset la connexion Si le port est ouvert alors le serveur répondra: Syn ACK, puis le client Reset la connexion
@ -12,17 +12,19 @@ def rule(packet, tcp_packets, db):
return return
# Vérification si nécessaire de récupérer les variables depuis la config # Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("synscan_time", 180) rule.time_window = objets["config"].get("synscan_time", 180)
rule.seuil = db.get_key("synscan_count", 5) rule.seuil = objets["config"].get("synscan_count", 5)
rule.ban_time = objets["config"].get("synscan_bantime", 300)
# Comptage du nombre de scan syn acceptés et refusés # Comptage du nombre de scan syn acceptés et refusés
syndeny_count = tcp_packets.count_packet_of_type(["S", "RA"], rule.time_window, True) syndeny_count = objets["tcp_packets"].count_packet_of_type(["S", "RA"], rule.time_window, True)
synaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "R"], rule.time_window, True) synaccept_count = objets["tcp_packets"].count_packet_of_type(["S", "SA", "R"], rule.time_window, True)
if (synaccept_count + syndeny_count >= rule.seuil): if (synaccept_count + syndeny_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "Syn scan", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->Reset et Syn->Reset ACK", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "Syn scan", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->Reset et Syn->Reset ACK", act="Alerte")
print(f"Alerte, seuil dépassés, risque de SynScan") objets["iptables_manager"].add_rule("/sbin/iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuil dépassés, risque de SynScan")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -30,3 +32,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
"""Règle TCPConnect Scan: """Règle TCPConnect Scan:
Un scan TCP connect va effectuer une connexion TCP en entier sur chaque port scanné. Un scan TCP connect va effectuer une connexion TCP en entier sur chaque port scanné.
Si le port est ouvert le serveur acceptera la connexion SYN -> SYN ACK -> ACK -> Reset ACK Si le port est ouvert le serveur acceptera la connexion SYN -> SYN ACK -> ACK -> Reset ACK
@ -13,17 +13,19 @@ def rule(packet, tcp_packets, db):
return return
# Vérification si nécessaire de récupérer les variables depuis la config # Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("tcpconnectscan_time", 180) rule.time_window = objets["config"].get("tcpconnectscan_time", 180)
rule.seuil = db.get_key("tcpconnectscan_count", 5) rule.seuil = objets["config"].get("tcpconnectscan_count", 5)
rule.ban_time = objets["config"].get("tcpconnectscan_bantime", 300)
# Comptage du nombre de scan tcp connect acceptés et refusés # Comptage du nombre de scan tcp connect acceptés et refusés
tcpconnectdeny_count = tcp_packets.count_packet_of_type(["S", "RA"], rule.time_window, True) tcpconnectdeny_count = objets["tcp_packets"].count_packet_of_type(["S", "RA"], rule.time_window, True)
tcpconnectaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "A", "RA"], rule.time_window, True) tcpconnectaccept_count = objets["tcp_packets"].count_packet_of_type(["S", "SA", "A", "RA"], rule.time_window, True)
if (tcpconnectaccept_count + tcpconnectdeny_count >= rule.seuil): if (tcpconnectaccept_count + tcpconnectdeny_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "TCPConnect Scan", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->ACK->Reset ACK et Syn->Reset ACK", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "TCPConnect Scan", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Syn->SynACK->ACK->Reset ACK et Syn->Reset ACK", act="Alerte")
print(f"Alerte, seuils dépassés, risque de TCPConnectScan") objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuils dépassés, risque de TCPConnectScan")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -31,3 +33,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -2,7 +2,7 @@ from datetime import datetime
import time import time
def rule(packet, tcp_packets, db): def rule(packet, objets):
"""Règle XMAS Scan: """Règle XMAS Scan:
Un XMAS Scan va envoyer des requêtes TCP avec le flag Fin, Push, Urg Un XMAS Scan va envoyer des requêtes TCP avec le flag Fin, Push, Urg
Si le port est ouvert alors le serveur répondra pas Si le port est ouvert alors le serveur répondra pas
@ -12,17 +12,19 @@ def rule(packet, tcp_packets, db):
return return
# Vérification si nécessaire de récupérer les variables depuis la config # Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0): if (rule.seuil == 0 and rule.time_window == 0 and rule.ban_time == 0):
rule.time_window = db.get_key("xmasscan_time", 180) rule.time_window = objets["config"].get("xmasscan_time", 180)
rule.seuil = db.get_key("xmasscan_count", 5) rule.seuil = objets["config"].get("xmasscan_count", 5)
rule.ban_time = objets["config"].get("xmasscan_bantime", 300)
# Comptage du nombre de scan XMAS acceptés et refusés # Comptage du nombre de scan XMAS acceptés et refusés
xmasdeny_count = tcp_packets.count_packet_of_type(["FPU", "RA"], rule.time_window, True) xmasdeny_count = objets["tcp_packets"].count_packet_of_type(["FPU", "RA"], rule.time_window, True)
xmasaccept_count = tcp_packets.count_packet_of_type(["FPU"], rule.time_window, True) xmasaccept_count = objets["tcp_packets"].count_packet_of_type(["FPU"], rule.time_window, True)
if (xmasaccept_count + xmasdeny_count >= rule.seuil): if (xmasaccept_count + xmasdeny_count >= rule.seuil):
db.send_alert(datetime.now(), 5, None, "XMAS scan", packet['IP'].src, packet['IP'].dst, proto="TCP", reason="Détection de nombreux patterns de Fin Push Urg -> rien et Fin Push Urg->Reset ACK", act="Alerte") objets["database"].send_alert(datetime.now(), 5, None, "XMAS scan", objets["pkt_origin"][0], objets["pkt_origin"][2], proto="TCP", reason="Détection de nombreux patterns de Fin Push Urg -> rien et Fin Push Urg->Reset ACK", act="Alerte")
print(f"Alerte, seuil dépassés, risque de XMAS Scan") objets["iptables_manager"].add_rule("iptables -I FORWARD -s " + objets["pkt_origin"][0] + " -j DROP", rule.ban_time)
print("Alerte, seuil dépassés, risque de XMAS Scan")
rule.cooldown = time.time() rule.cooldown = time.time()
@ -30,3 +32,4 @@ def rule(packet, tcp_packets, db):
rule.cooldown = 0 rule.cooldown = 0
rule.time_window = 0 rule.time_window = 0
rule.seuil = 0 rule.seuil = 0
rule.ban_time = 0

View File

@ -20,15 +20,15 @@ class TCP:
if flags == "S": if flags == "S":
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["S"], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, ["S"], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
elif flags is None: elif flags is None:
self.packets[ip_src].append([port_src, ip_dst, port_dst, [""], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, [""], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
elif flags == "FPU": elif flags == "FPU":
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["FPU"], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, ["FPU"], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
elif flags == "SA": elif flags == "SA":
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S") i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
@ -36,10 +36,10 @@ class TCP:
if i is not None: if i is not None:
self.packets[ip][i][3].append("SA") self.packets[ip][i][3].append("SA")
self.packets[ip][i][4] = timestamp self.packets[ip][i][4] = timestamp
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst, ip)
else: else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["SA"], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, ["SA"], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
elif flags == "A": elif flags == "A":
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "SA") i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "SA")
@ -49,10 +49,10 @@ class TCP:
if i is not None: if i is not None:
self.packets[ip][i][3].append("A") self.packets[ip][i][3].append("A")
self.packets[ip][i][4] = timestamp self.packets[ip][i][4] = timestamp
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst, ip)
else: else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["A"], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, ["A"], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
elif flags == "RA": elif flags == "RA":
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A") i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A")
@ -63,10 +63,10 @@ class TCP:
if i is not None: if i is not None:
self.packets[ip][i][3].append("RA") self.packets[ip][i][3].append("RA")
self.packets[ip][i][4] = timestamp self.packets[ip][i][4] = timestamp
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst, ip)
else: else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["RA"], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, ["RA"], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
elif flags == "R": elif flags == "R":
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A") i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A")
@ -77,10 +77,10 @@ class TCP:
if i is not None: if i is not None:
self.packets[ip][i][3].append("R") self.packets[ip][i][3].append("R")
self.packets[ip][i][4] = timestamp self.packets[ip][i][4] = timestamp
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst, ip)
else: else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["R"], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, ["R"], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
elif flags == "F": elif flags == "F":
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A") i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A")
@ -88,10 +88,10 @@ class TCP:
if i is not None: if i is not None:
self.packets[ip][i][3].append("F") self.packets[ip][i][3].append("F")
self.packets[ip][i][4] = timestamp self.packets[ip][i][4] = timestamp
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst, ip)
else: else:
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["F"], timestamp]) self.packets[ip_src].append([port_src, ip_dst, port_dst, ["F"], timestamp])
return return self.return_origin_packet(ip_src, port_src, ip_dst, port_dst)
def find_packet_to_replace(self, ip_src, port_src, ip_dst, port_dst, flags): def find_packet_to_replace(self, ip_src, port_src, ip_dst, port_dst, flags):
"""Cherche l'indice et le port de source du paquet dont le flag doit être remplacé""" """Cherche l'indice et le port de source du paquet dont le flag doit être remplacé"""
@ -147,3 +147,15 @@ class TCP:
"""Retourne la liste des paquets liés à une adresse IP, pour du déboggage""" """Retourne la liste des paquets liés à une adresse IP, pour du déboggage"""
return self.packets.get(src_ip, None) return self.packets.get(src_ip, None)
def return_origin_packet(self, ip_src, port_src, ip_dst, port_dst, ip = None):
"""Retourne le paquet d'origine par rapport à l'ip de référence"""
if ip is None:
return [ip_src, port_src, ip_dst, port_dst]
else:
if ip == ip_src:
return [ip_src, port_src, ip_dst, port_dst]
elif ip == ip_dst:
return [ip_dst, port_dst, ip_src, port_src]