From 2d25387fde16dd905612c73cbc83ca293dee9870 Mon Sep 17 00:00:00 2001 From: Oxbian Date: Mon, 18 Nov 2024 17:59:10 -0500 Subject: [PATCH] feat: config file + database connection / dockerfile --- Demo/Dockerfiles/Dockerfile.db | 5 + Demo/Dockerfiles/Dockerfile.idps | 5 +- Demo/Dockerfiles/Dockerfile.ids | 14 ++- Demo/docker-compose.yml | 11 +- config.json | 13 +++ idps/database.py | 64 +++++++++++ idps/main.py | 65 ++++++++--- idps/rules/TCP/Scan/synscan.py | 14 ++- idps/rules/TCP/Scan/tcpconnectscan.py | 15 ++- idps/tcp.py | 27 +++-- requirements.txt | 4 +- sql/adduser.sql | 5 + sql/cef-generator.py | 156 ++++++++++++++++++++++++++ BDD/base.sql => sql/db-schema.sql | 12 +- sql/init.sql | 35 ++++++ tests/cef-generator.py | 102 ----------------- 16 files changed, 397 insertions(+), 150 deletions(-) create mode 100644 Demo/Dockerfiles/Dockerfile.db create mode 100644 config.json create mode 100644 idps/database.py create mode 100644 sql/adduser.sql create mode 100644 sql/cef-generator.py rename BDD/base.sql => sql/db-schema.sql (80%) create mode 100644 sql/init.sql delete mode 100644 tests/cef-generator.py diff --git a/Demo/Dockerfiles/Dockerfile.db b/Demo/Dockerfiles/Dockerfile.db new file mode 100644 index 0000000..27ab9f8 --- /dev/null +++ b/Demo/Dockerfiles/Dockerfile.db @@ -0,0 +1,5 @@ +FROM mysql:latest + +# Copier le script SQL dans l'image +COPY sql/init.sql /docker-entrypoint-initdb.d/ + diff --git a/Demo/Dockerfiles/Dockerfile.idps b/Demo/Dockerfiles/Dockerfile.idps index 84a1179..330bba4 100644 --- a/Demo/Dockerfiles/Dockerfile.idps +++ b/Demo/Dockerfiles/Dockerfile.idps @@ -3,7 +3,7 @@ FROM python:alpine3.20 # Installation des paquets nécessaires pour scapy RUN apk -U upgrade && \ apk add --no-cache libpcap libpcap-dev gcc musl-dev libffi-dev -RUN pip install scapy +RUN pip install scapy mysql-connector-python # Copier le script de l'idps WORKDIR /app @@ -11,6 +11,9 @@ WORKDIR /app # Copier le contenu du répertoire 'idps' du contexte de build vers '/app/idps' dans le conteneur COPY idps /app/idps +# Copie du fichier de configuration +COPY config.json /app/config.json + # Autres commandes nécessaires pour ton projet # Par exemple, pour installer des dépendances : # RUN pip install -r /app/idps/requirements.txt (si applicable) diff --git a/Demo/Dockerfiles/Dockerfile.ids b/Demo/Dockerfiles/Dockerfile.ids index 201b37f..2a91158 100644 --- a/Demo/Dockerfiles/Dockerfile.ids +++ b/Demo/Dockerfiles/Dockerfile.ids @@ -3,10 +3,16 @@ FROM python:alpine3.20 # Installation des paquets nécessaires pour scapy RUN apk -U upgrade && \ apk add --no-cache libpcap libpcap-dev gcc musl-dev libffi-dev -RUN pip install scapy +RUN pip install scapy mysql-connector-python -# Copier le script de la sonde IDS -#COPY ids.py /ids.py +# Copier le script de l'idps +WORKDIR /app + +# Copier le contenu du répertoire 'idps' du contexte de build vers '/app/idps' dans le conteneur +COPY idps /app/ids + +# Copie du fichier de configuration +COPY config.json /app/config.json # Lancer le script de la sonde IDS -#CMD ["python", "/ids.py"] +CMD ["python3", "/app/ids/ids.py"] diff --git a/Demo/docker-compose.yml b/Demo/docker-compose.yml index af62119..596459f 100644 --- a/Demo/docker-compose.yml +++ b/Demo/docker-compose.yml @@ -56,8 +56,8 @@ services: # Sonde IDS ids: build: - context: Dockerfiles/. - dockerfile: Dockerfile.ids + context: .. + dockerfile: Demo/Dockerfiles/Dockerfile.ids container_name: ids command: sleep infinity cap_add: @@ -72,11 +72,14 @@ services: # BDD d'alertes alert_db: - image: mysql:5.7 + build: + context: .. + dockerfile: Demo/Dockerfiles/Dockerfile.db container_name: alert_db environment: MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: alert_db + ports: + - "3306:3306" networks: net_private: ipv4_address: 172.20.2.10 diff --git a/config.json b/config.json new file mode 100644 index 0000000..10aac78 --- /dev/null +++ b/config.json @@ -0,0 +1,13 @@ +{ + "rules_dirpath": "/app/idps/rules", + "ifaces": ["eth0", "eth1"], + "db_host": "172.20.2.10", + "db_database": "sidps", + "db_user": "sidps", + "db_password": "SUPERPASSWORD", + "db_port": "3306", + "synscan_time": 180, + "synscan_count": 5, + "tcpconnectscan_time": 180, + "tcpconnectscan_count": 5 +} diff --git a/idps/database.py b/idps/database.py new file mode 100644 index 0000000..291c595 --- /dev/null +++ b/idps/database.py @@ -0,0 +1,64 @@ +import mysql.connector + + +class Database: + """Classe pour effectuer les actions liées à la base de données (envoi d'alertes...)""" + + def __init__(self, config): + """Connexion à la base de données à partir des identifiants dans la config""" + self.conn = mysql.connector.connect(host=config["db_host"], database=config["db_database"], user=config["db_user"], password=config["db_password"], port=config["db_port"]) + self.config = config + + def send_alert(self, alert): + """Ajoute une alerte dans la base de données + @param alert: Alerte à rajouter dans la BDD""" + + try: + cursor = self.conn.cursor() + sql_query = """ + INSERT INTO alertes ( + cef_version, date_alerte, event_gravite, device_product, + device_vendor, device_version, alerte_name, sourceAddress, + destinationAddress, destinationPort, sourcePort, protocol, + applicationProtocol, reason, action, commentaire + ) VALUES ( + %(cef_version)s, %(date_alerte)s, %(event_gravite)s, %(device_product)s, + %(device_vendor)s, %(device_version)s, %(alerte_name)s, %(src)s, + %(dst)s, %(destinationPort)s, %(sourcePort)s, %(protocol)s, + %(applicationProtocol)s, %(reason)s, %(action)s, %(commentaire)s + ); + """ + + # Paramètres pour la requête SQL + params = { + "cef_version": alert["CEF"], + "date_alerte": alert["datetime"], + "event_gravite": alert["agent_severity"], + "device_product": alert["Device Product"], + "device_vendor": alert["Device Vendor"], + "device_version": alert["Device Version"], + "alerte_name": alert["name"], + "src": alert["src"], + "dst": alert["dst"], + "destinationPort": alert["dstPort"], + "sourcePort": alert["srcPort"], + "protocol": alert["protocol"], + "applicationProtocol": alert["applicationProtocol"], + "reason": alert["reason"], + "action": alert["action"], + "commentaire": alert["commentaire"] + } + + # Exécution de la requête d'insertion + cursor.execute(sql_query, params) + self.conn.commit() + cursor.close() + except mysql.connector.Error as 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, None) diff --git a/idps/main.py b/idps/main.py index d5d1849..664bb47 100644 --- a/idps/main.py +++ b/idps/main.py @@ -1,14 +1,19 @@ 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 +import database +import json 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""" + """Charger les fonctions de règles du répertoire de règles et des sous répertoires + @param rules_dirpath: Répertoire contenant les fichiers python de règles + """ if not os.path.exists(rules_dirpath): raise ValueError(f"Le chemin spécifié n'existe pas: {rules_dirpath}") @@ -56,52 +61,84 @@ def load_rules(rules_dirpath = "/app/idps/rules"): rules_functions[parent_dir].append(module.rule) except PermissionError: - print(f"Permission refusée pour accéder au répertoire: {current_dir}") + raise PermissionError(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}") + raise OSError(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é.""" +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) + 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): +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) + print(tcp_packets[packet[IP].src]) + check_frame_w_rules(packet, rules_functions['TCP'], tcp_packets, db) tcp_packets.clean_old_packets() -def start_idps(IDS_IFACES = ["eth0","eth1"]): +def read_config(config_filepath='config.json'): + """Charge les configurations depuis le fichier de config""" + + try: + with open(config_filepath, 'r', encoding='utf-8') as file: + config = json.load(file) + return config + except FileNotFoundError: + raise FileNotFoundError(f"Le fichier JSON {config_filepath} n'a pas été trouvé.") + except json.JSONDecodeError: + raise json.JSONDecodeError("Erreur lors de la lecture du fichier JSON. Le format peut être incorrect.") + + +def start_idps(): """Charge les règles et démarre l'IDPS""" + + print(f"Récupération des configurations") + config = read_config() + print(f"Configurations chargées") + print(f"Chargement des règles...") - rules_functions = load_rules() + rules_functions = load_rules(config["rules_dirpath"]) print(f"Les règles sont chargées") + print(f"Connexion à la base de données") + db = database.Database(config) + print(f"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) # 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) + sniff(iface=config["ifaces"], prn=lambda packet: packet_callback(packet, rules_functions, tcp_packets, db), store=0) def main(): print(f"Démarrage de l'IDPS") start_idps() - print(f"IDPS opérationel") if __name__ == "__main__": diff --git a/idps/rules/TCP/Scan/synscan.py b/idps/rules/TCP/Scan/synscan.py index 7f3b45f..de69fc5 100644 --- a/idps/rules/TCP/Scan/synscan.py +++ b/idps/rules/TCP/Scan/synscan.py @@ -1,8 +1,12 @@ -# Seuils -TIME_WINDOW = 180 -NB_SEUIL = 5 +def rule(packet, tcp_packets, db): + """Règle SYNScan: + 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 + Sinon le port est fermé et le serveur répondera: Reset ACK + """ + time_window = db.get_key("synscan_time", 180) + seuil = db.get_key("synscan_count", 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: + 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) >= seuil: print(f"Alerte, seuil dépassés, risque de SynScan") diff --git a/idps/rules/TCP/Scan/tcpconnectscan.py b/idps/rules/TCP/Scan/tcpconnectscan.py index ddcba16..b21bffa 100644 --- a/idps/rules/TCP/Scan/tcpconnectscan.py +++ b/idps/rules/TCP/Scan/tcpconnectscan.py @@ -1,8 +1,11 @@ -# Seuils -TIME_WINDOW = 180 # 180 secondes pour avoir X paquets -NB_SEUIL = 5 +def rule(packet, tcp_packets, db): + """Règle TCPConnect Scan: + 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 + Sinon le port est fermé et le serveur refusera la connexion SYN -> Reset ACK + """ + time_window = db.get_key("tcpconnectscan_time", 180) + seuil = db.get_key("tcpconnectscan_count", 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: + if (tcp_packets.count_packet_of_type("A", time_window) + tcp_packets.count_packet_of_type("RA", time_window)) >= seuil: print(f"Alerte, seuils dépassés, risque de TCPConnectScan") diff --git a/idps/tcp.py b/idps/tcp.py index a2ea321..b113495 100644 --- a/idps/tcp.py +++ b/idps/tcp.py @@ -17,18 +17,18 @@ class TCP: self.packets[ip_src] = [] if flags == "S": - self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp]) + 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][3].append("SA") self.packets[ip_dst][i][4] = timestamp return else: - self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp]) + self.packets[ip_src].append([port_src, ip_dst, port_dst, [flags], timestamp]) return elif flags == "A": @@ -37,11 +37,11 @@ class TCP: 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][3].append("A") self.packets[ip_src][i][4] = timestamp return else: - self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp]) + self.packets[ip_src].append([port_src, ip_dst, port_dst, [flags], timestamp]) return elif flags == "RA": @@ -51,11 +51,11 @@ class TCP: 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][3].append("RA") self.packets[ip_src][i][4] = timestamp return else: - self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp]) + self.packets[ip_src].append([port_src, ip_dst, port_dst, [flags], timestamp]) return elif flags == "R": @@ -65,11 +65,11 @@ class TCP: 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][3].append("R") self.packets[ip_src][i][4] = timestamp return else: - self.packets[ip_src].append([port_src, ip_dst, port_dst, flags, timestamp]) + 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): @@ -78,8 +78,11 @@ class TCP: ip_src, ip_dst = ip_dst, ip_src port_src, port_dst = port_dst, port_src + if ip_src not in self.packets.keys(): + return None + 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: + if p_s == port_src and ip_d == ip_dst and p_d == port_dst and f in flags: return i return None @@ -110,9 +113,11 @@ class TCP: 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: + if flag in packet[3] and packet[4] >= current_timestamp - time_treshold: count += 1 return count def __getitem__(self, src_ip): + """Retourne la liste des paquets liés à une adresse IP, pour du déboggage""" + return self.packets.get(src_ip, None) diff --git a/requirements.txt b/requirements.txt index f6832c6..6429e50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -hiredis==3.0.0 -redis==5.2.0 +mysql-connector-python==9.1.0 +scapy=2.6.1 diff --git a/sql/adduser.sql b/sql/adduser.sql new file mode 100644 index 0000000..072ffed --- /dev/null +++ b/sql/adduser.sql @@ -0,0 +1,5 @@ +CREATE DATABASE sidps DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE USER 'sidps'@'localhost' IDENTIFIED BY 'SUPERPASSWORD'; +GRANT ALL PRIVILEGES ON sidps.* TO 'sidps'@'localhost'; +FLUSH PRIVILEGES; +use sidps; diff --git a/sql/cef-generator.py b/sql/cef-generator.py new file mode 100644 index 0000000..edd512b --- /dev/null +++ b/sql/cef-generator.py @@ -0,0 +1,156 @@ +# Générateur d'alertes CEF (Common Event Format) +# Pratique pour le moteur de corrélation et le site web + +# Une alerte CEF est formattée de cette façon: +# CEF:Version|Device Vendor|Device Product|Device Version|Device Event Class ID|Name|Severity|[Extension] + +import mysql.connector +import time +import random +from datetime import datetime + + +def generate_alert(alert_type): + # Dictionnaire pour différents types d'alertes réseau et fichiers + alert_templates = { + "network": { + "Syn Flood": { + "Device_event_class_id": "1001", + "name": "Syn Flood Detected", + "src": f"{generate_ip()}", + "dst": f"{generate_ip()}", + "agent_severity": "8" + }, + "Port Scanning": { + "Device_event_class_id": "1002", + "name": "Port Scanning Activity", + "src": f"{generate_ip()}", + "dst": f"{generate_ip()}", + "cs1": f"{generate_ports()}", + "agent_severity": "5" + } + }, + "file": { + "Suspicious File Creation": { + "Device_event_class_id": "2001", + "name": "Suspicious File Created", + "fname": f"{generate_filename()}", + "fsize": f"{random.randint(10, 1000)}kb", + "agent_severity": "7" + }, + "Critical File Deletion Attempt": { + "Device_event_class_id": "2002", + "name": "Critical File Deletion Attempt", + "fname": f"{generate_filename()}", + "agent_severity": "9" + } + } + } + + # Sélectionner le bon template en fonction du type d'alerte + category = "network" if alert_type in alert_templates["network"] else "file" + alert_info = alert_templates[category].get(alert_type, {}) + + if not alert_info: + raise ValueError(f"Unknown alert type: {alert_type}") + + return alert_info + +def generate_ip(): + # Générer une adresse IP aléatoire + return ".".join(str(random.randint(0, 255)) for _ in range(4)) + +def generate_ports(): + # Générer une liste de ports scannés + return ",".join(str(random.randint(20, 1024)) for _ in range(5)) + +def generate_filename(): + # Générer un nom de fichier aléatoire + filenames = ["config.txt", "database.db", "system32.dll", "passwd", "shadow", "sensitive_data.doc"] + return random.choice(filenames) + +def generate_alerts(conn, cursor, main_headers): + # Récupérer ces données depuis une fonction + alertes = ["Syn Flood", "Port Scanning", "Suspicious File Creation", "Critical File Deletion Attempt"] + + while True: + data = generate_alert(random.choice(alertes)) + merged = main_headers.copy() + merged.update(data) + + # Préparer la requête SQL d'insertion + sql_query = """ + INSERT INTO alertes ( + cef_version, date_alerte, event_gravite, device_product, + device_vendor, device_version, alerte_name, sourceAddress, + destinationAddress, destinationPort, sourcePort, protocol, + applicationProtocol, reason, action, commentaire + ) VALUES ( + %(cef_version)s, %(date_alerte)s, %(event_gravite)s, %(device_product)s, + %(device_vendor)s, %(device_version)s, %(alerte_name)s, %(src)s, + %(dst)s, %(destinationPort)s, %(sourcePort)s, %(protocol)s, + %(applicationProtocol)s, %(reason)s, %(action)s, %(commentaire)s + ); + """ + + # Paramètres pour la requête SQL + params = { + "cef_version": merged["CEF"], + "date_alerte": datetime.now(), + "event_gravite": int(merged["agent_severity"]), + "device_product": merged["Device Product"], + "device_vendor": merged["Device Vendor"], + "device_version": merged["Device Version"], + "alerte_name": merged["name"], + "src": merged["src"], + "dst": merged["dst"], + "destinationPort": None, # A définir si disponible + "sourcePort": None, # A définir si disponible + "protocol": "TCP", # Par défaut, à adapter si besoin + "applicationProtocol": "N/A", # À ajuster en fonction des besoins + "reason": "Suspicious activity detected", # Exemple, à adapter + "action": "Alerted", # Exemple d'action + "commentaire": "" # Optionnel + } + + # Exécution de la requête d'insertion + cursor.execute(sql_query, params) + conn.commit() + + # Attente avant de générer la prochaine alerte + time.sleep(random.randint(1, 10)) + +def main(): + # Connexion à la base de données MySQL/MariaDB + conn = mysql.connector.connect( + host="172.20.2.10", # À adapter selon votre configuration + database="sidps", # Nom de la base de données + user="sidps", # Nom d'utilisateur + password="SUPERPASSWORD", # Mot de passe + port=3306 # Port MySQL par défaut (peut être 3306 ou autre selon la configuration) + ) + + cursor = conn.cursor() + + # En-têtes généraux + CEF_version = 1 + Device_vendor = "ArKa" + Device_product = "SIDPS" + Device_version = "vAlpha" + + main_headers = { + "CEF": CEF_version, + "Device Vendor": Device_vendor, + "Device Product": Device_product, + "Device Version": Device_version + } + + # Lancer la génération d'alertes + generate_alerts(conn, cursor, main_headers) + + # Fermer la connexion à la base de données + cursor.close() + conn.close() + +if __name__ == "__main__": + main() diff --git a/BDD/base.sql b/sql/db-schema.sql similarity index 80% rename from BDD/base.sql rename to sql/db-schema.sql index ed72cee..e33ff05 100644 --- a/BDD/base.sql +++ b/sql/db-schema.sql @@ -1,6 +1,16 @@ +#----------------------------------------------- +# Nettoyage des tables dans la base de données +#----------------------------------------------- + +DROP TABLE IF EXISTS alertes; + +#----------------------------------------------- +# Table: alertes +#---------------------------------------------- + CREATE TABLE alertes ( id SERIAL PRIMARY KEY, -- Identifiant unique pour chaque alerte - cef_version VARCHAR(10) DEFAULT 'CEF:0', -- Version du format CEF utilisé + cef_version VARCHAR(10) DEFAULT 'CEF:1', -- Version du format CEF utilisé date_alerte TIMESTAMP(3) NOT NULL, -- Date et heure de l'alerte avec une précision de millisecondes event_gravite INT CHECK (event_gravite >= 0 AND event_gravite <= 10), -- Niveau de gravité de l'alerte sur une échelle de 0 à 10 device_product VARCHAR(63), -- Nom du produit à l'origine de l'alerte diff --git a/sql/init.sql b/sql/init.sql new file mode 100644 index 0000000..c9677cf --- /dev/null +++ b/sql/init.sql @@ -0,0 +1,35 @@ +CREATE DATABASE IF NOT EXISTS sidps DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE USER 'sidps'@'%' IDENTIFIED BY 'SUPERPASSWORD'; +GRANT ALL PRIVILEGES ON sidps.* TO 'sidps'@'%'; +FLUSH PRIVILEGES; +use sidps; + +#----------------------------------------------- +# Nettoyage des tables dans la base de données +#----------------------------------------------- + +DROP TABLE IF EXISTS alertes; + +#----------------------------------------------- +# Table: alertes +#---------------------------------------------- + +CREATE TABLE alertes ( + id SERIAL PRIMARY KEY, -- Identifiant unique pour chaque alerte + cef_version VARCHAR(10) DEFAULT 'CEF:1', -- Version du format CEF utilisé + date_alerte TIMESTAMP(3) NOT NULL, -- Date et heure de l'alerte avec une précision de millisecondes + event_gravite INT CHECK (event_gravite >= 0 AND event_gravite <= 10), -- Niveau de gravité de l'alerte sur une échelle de 0 à 10 + device_product VARCHAR(63), -- Nom du produit à l'origine de l'alerte + device_vendor VARCHAR(63), -- Nom du fournisseur ou fabricant du produit + device_version VARCHAR(31), -- Version du produit ou dispositif ayant généré l'alerte + alerte_name VARCHAR(512), -- Nom descriptif de l'alerte + destinationAddress VARCHAR(45), -- Adresse IP de destination impliquée dans l'alerte + sourceAddress VARCHAR(45), -- Adresse IP source impliquée dans l'alerte + destinationPort INT, -- Port de destination utilisé pour l'événement ou l'alerte + sourcePort INT, -- Port source de l'événement ou de l'alerte + protocol VARCHAR(10), -- Protocole réseau impliqué (ex : TCP, UDP) + applicationProtocol VARCHAR(20), -- Protocole applicatif impliqué (ex : HTTP, FTP) + reason TEXT, -- Description de la raison de l'alerte expliquant pourquoi elle a été générée + action VARCHAR(50), -- Action entreprise en réponse à l'alerte (ex : bloqué, alerté uniquement, ...) + commentaire TEXT -- Champ texte pour des notes ou commentaires additionnels concernant l'alerte +); diff --git a/tests/cef-generator.py b/tests/cef-generator.py deleted file mode 100644 index b228105..0000000 --- a/tests/cef-generator.py +++ /dev/null @@ -1,102 +0,0 @@ -# Générateur d'alertes CEF (Common Event Format) -# Pratique pour le moteur de corrélation et le site web - -# Une alerte CEF est formattée de cette façon: -# CEF:Version|Device Vendor|Device Product|Device Version|Device Event Class ID|Name|Severity|[Extension] - -import redis -import time -import random - -def generate_alert(alert_type): - # Dictionnaire pour différents types d'alertes réseau et fichiers - alert_templates = { - "network": { - "Syn Flood": { - "Device_event_class_id": "1001", - "name": "Syn Flood Detected", - "src" : f"{generate_ip()}", - "dst" : f"{generate_ip()}", - "agent_severity": "8" - }, - "Port Scanning": { - "Device_event_class_id": "1002", - "name": "Port Scanning Activity", - "src" : f"{generate_ip()}", - "dst" : f"{generate_ip()}", - "cs1" : f"{generate_ports()}", - "agent_severity": "5" - } - }, - "file": { - "Suspicious File Creation": { - "Device_event_class_id": "2001", - "name": "Suspicious File Created", - "fname" : f"{generate_filename()}", - "fsize" : f"{random.randint(10,1000)}kb", - "agent_severity": "7" - }, - "Critical File Deletion Attempt": { - "Device_event_class_id": "2002", - "name": "Critical File Deletion Attempt", - "fname" : f"{generate_filename()}", - "agent_severity": "9" - } - } - } - - # Sélectionner le bon template en fonction du type d'alerte - category = "network" if alert_type in alert_templates["network"] else "file" - alert_info = alert_templates[category].get(alert_type, {}) - - if not alert_info: - raise ValueError(f"Unknown alert type: {alert_type}") - - return alert_info - -def generate_ip(): - # Générer une adresse IP aléatoire - return ".".join(str(random.randint(0, 255)) for _ in range(4)) - -def generate_ports(): - # Générer une liste de ports scannés - return ",".join(str(random.randint(20, 1024)) for _ in range(5)) - -def generate_filename(): - # Générer un nom de fichier aléatoire - filenames = ["config.txt", "database.db", "system32.dll", "passwd", "shadow", "sensitive_data.doc"] - return random.choice(filenames) - -def generate_alerts(db, main_headers): - # Récupérer ces données depuis une fonction - alertes = ["Syn Flood", "Port Scanning", "Suspicious File Creation", "Critical File Deletion Attempt"] - - while (1): - data = generate_alert(random.choice(alertes)) - merged = main_headers.copy() - merged.update(data) - - # Ajout dans redis - response = db.xadd("logs:alertes", merged) - time.sleep(random.randint(1, 10)) - - -def main(): - - # Connexion à Redis (si besoin changer l'host et le port) - db = redis.Redis(host='localhost', port=6379, decode_responses=True) - - # Pour une db en production (https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/) - #db = redis.Redis(host="my-redis.cloud.redislabs.com", port=6379, username="default", password="secret", ssl=True, ssl_certfile="./redis_user.crt", ssl_keyfile="./redis_user_private.key", ssl_ca_certs="./redis_ca.pem") - - CEF_version=1 - Device_vendor="ArKa" - Device_product="SIDPS" - Device_version="vAlpha" - - main_headers = {"CEF": CEF_version, "Device Vendor" : Device_vendor, "Device Product" : Device_product, "Device_version" : Device_version} - - generate_alerts(db, main_headers) - -if __name__ == "__main__": - main()