mirror of
https://github.com/Oxbian/SIDPS.git
synced 2025-05-17 14:08:14 +02:00
feat: config file + database connection / dockerfile
This commit is contained in:
parent
dbc65f13bc
commit
2d25387fde
5
Demo/Dockerfiles/Dockerfile.db
Normal file
5
Demo/Dockerfiles/Dockerfile.db
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
FROM mysql:latest
|
||||||
|
|
||||||
|
# Copier le script SQL dans l'image
|
||||||
|
COPY sql/init.sql /docker-entrypoint-initdb.d/
|
||||||
|
|
@ -3,7 +3,7 @@ FROM python:alpine3.20
|
|||||||
# Installation des paquets nécessaires pour scapy
|
# Installation des paquets nécessaires pour scapy
|
||||||
RUN apk -U upgrade && \
|
RUN apk -U upgrade && \
|
||||||
apk add --no-cache libpcap libpcap-dev gcc musl-dev libffi-dev
|
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
|
# Copier le script de l'idps
|
||||||
WORKDIR /app
|
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
|
# Copier le contenu du répertoire 'idps' du contexte de build vers '/app/idps' dans le conteneur
|
||||||
COPY idps /app/idps
|
COPY idps /app/idps
|
||||||
|
|
||||||
|
# Copie du fichier de configuration
|
||||||
|
COPY config.json /app/config.json
|
||||||
|
|
||||||
# Autres commandes nécessaires pour ton projet
|
# Autres commandes nécessaires pour ton projet
|
||||||
# Par exemple, pour installer des dépendances :
|
# Par exemple, pour installer des dépendances :
|
||||||
# RUN pip install -r /app/idps/requirements.txt (si applicable)
|
# RUN pip install -r /app/idps/requirements.txt (si applicable)
|
||||||
|
@ -3,10 +3,16 @@ FROM python:alpine3.20
|
|||||||
# Installation des paquets nécessaires pour scapy
|
# Installation des paquets nécessaires pour scapy
|
||||||
RUN apk -U upgrade && \
|
RUN apk -U upgrade && \
|
||||||
apk add --no-cache libpcap libpcap-dev gcc musl-dev libffi-dev
|
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
|
# Copier le script de l'idps
|
||||||
#COPY ids.py /ids.py
|
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
|
# Lancer le script de la sonde IDS
|
||||||
#CMD ["python", "/ids.py"]
|
CMD ["python3", "/app/ids/ids.py"]
|
||||||
|
@ -56,8 +56,8 @@ services:
|
|||||||
# Sonde IDS
|
# Sonde IDS
|
||||||
ids:
|
ids:
|
||||||
build:
|
build:
|
||||||
context: Dockerfiles/.
|
context: ..
|
||||||
dockerfile: Dockerfile.ids
|
dockerfile: Demo/Dockerfiles/Dockerfile.ids
|
||||||
container_name: ids
|
container_name: ids
|
||||||
command: sleep infinity
|
command: sleep infinity
|
||||||
cap_add:
|
cap_add:
|
||||||
@ -72,11 +72,14 @@ services:
|
|||||||
|
|
||||||
# BDD d'alertes
|
# BDD d'alertes
|
||||||
alert_db:
|
alert_db:
|
||||||
image: mysql:5.7
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: Demo/Dockerfiles/Dockerfile.db
|
||||||
container_name: alert_db
|
container_name: alert_db
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: root
|
MYSQL_ROOT_PASSWORD: root
|
||||||
MYSQL_DATABASE: alert_db
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
networks:
|
networks:
|
||||||
net_private:
|
net_private:
|
||||||
ipv4_address: 172.20.2.10
|
ipv4_address: 172.20.2.10
|
||||||
|
13
config.json
Normal file
13
config.json
Normal file
@ -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
|
||||||
|
}
|
64
idps/database.py
Normal file
64
idps/database.py
Normal file
@ -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)
|
65
idps/main.py
65
idps/main.py
@ -1,14 +1,19 @@
|
|||||||
from scapy.all import sniff, TCP, IP
|
from scapy.all import sniff, TCP, IP
|
||||||
from scapy.config import conf
|
from scapy.config import conf
|
||||||
conf.debug_dissector = 2
|
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 json
|
||||||
|
|
||||||
|
|
||||||
def load_rules(rules_dirpath = "/app/idps/rules"):
|
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):
|
if not os.path.exists(rules_dirpath):
|
||||||
raise ValueError(f"Le chemin spécifié n'existe pas: {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)
|
rules_functions[parent_dir].append(module.rule)
|
||||||
|
|
||||||
except PermissionError:
|
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:
|
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
|
return rules_functions
|
||||||
|
|
||||||
|
|
||||||
def check_frame_w_rules(packet, rules_functions, packets):
|
def check_frame_w_rules(packet, rules_functions, packets, db):
|
||||||
"""Appliquer chaque règle des fonctions au paquet capturé."""
|
"""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:
|
for rule_func in rules_functions:
|
||||||
try:
|
try:
|
||||||
rule_func(packet, packets)
|
rule_func(packet, packets, db)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erreur lors de l'exécution de la règle : {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)
|
#print(packet)
|
||||||
if IP in packet and TCP in 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())
|
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])
|
print(tcp_packets[packet[IP].src])
|
||||||
check_frame_w_rules(packet, rules_functions['TCP'], tcp_packets)
|
check_frame_w_rules(packet, rules_functions['TCP'], tcp_packets, db)
|
||||||
tcp_packets.clean_old_packets()
|
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"""
|
"""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...")
|
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"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
|
# 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)
|
# 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)
|
||||||
|
|
||||||
# Lancer scapy & envoyer le paquet à chaque règle de l'IDPS
|
# 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)
|
sniff(iface=config["ifaces"], prn=lambda packet: packet_callback(packet, rules_functions, tcp_packets, db), store=0)
|
||||||
#wrpcap("idps.pcap", capture)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print(f"Démarrage de l'IDPS")
|
print(f"Démarrage de l'IDPS")
|
||||||
start_idps()
|
start_idps()
|
||||||
print(f"IDPS opérationel")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
# Seuils
|
def rule(packet, tcp_packets, db):
|
||||||
TIME_WINDOW = 180
|
"""Règle SYNScan:
|
||||||
NB_SEUIL = 5
|
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) >= 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) >= NB_SEUIL:
|
|
||||||
print(f"Alerte, seuil dépassés, risque de SynScan")
|
print(f"Alerte, seuil dépassés, risque de SynScan")
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
# Seuils
|
def rule(packet, tcp_packets, db):
|
||||||
TIME_WINDOW = 180 # 180 secondes pour avoir X paquets
|
"""Règle TCPConnect Scan:
|
||||||
NB_SEUIL = 5
|
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)
|
||||||
|
|
||||||
|
if (tcp_packets.count_packet_of_type("A", time_window) + tcp_packets.count_packet_of_type("RA", time_window)) >= seuil:
|
||||||
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")
|
print(f"Alerte, seuils dépassés, risque de TCPConnectScan")
|
||||||
|
27
idps/tcp.py
27
idps/tcp.py
@ -17,18 +17,18 @@ class TCP:
|
|||||||
self.packets[ip_src] = []
|
self.packets[ip_src] = []
|
||||||
|
|
||||||
if flags == "S":
|
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
|
return
|
||||||
|
|
||||||
elif flags == "SA":
|
elif flags == "SA":
|
||||||
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S", True)
|
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S", True)
|
||||||
|
|
||||||
if i is not None:
|
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
|
self.packets[ip_dst][i][4] = timestamp
|
||||||
return
|
return
|
||||||
else:
|
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
|
return
|
||||||
|
|
||||||
elif flags == "A":
|
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)
|
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "R", True)
|
||||||
|
|
||||||
if i is not None:
|
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
|
self.packets[ip_src][i][4] = timestamp
|
||||||
return
|
return
|
||||||
else:
|
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
|
return
|
||||||
|
|
||||||
elif flags == "RA":
|
elif flags == "RA":
|
||||||
@ -51,11 +51,11 @@ class TCP:
|
|||||||
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
|
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
|
||||||
|
|
||||||
if i is not None:
|
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
|
self.packets[ip_src][i][4] = timestamp
|
||||||
return
|
return
|
||||||
else:
|
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
|
return
|
||||||
|
|
||||||
elif flags == "R":
|
elif flags == "R":
|
||||||
@ -65,11 +65,11 @@ class TCP:
|
|||||||
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
|
i = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
|
||||||
|
|
||||||
if i is not None:
|
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
|
self.packets[ip_src][i][4] = timestamp
|
||||||
return
|
return
|
||||||
else:
|
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
|
return
|
||||||
|
|
||||||
def find_packet_to_replace(self, ip_src, port_src, ip_dst, port_dst, flags, reverse=False):
|
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
|
ip_src, ip_dst = ip_dst, ip_src
|
||||||
port_src, port_dst = port_dst, port_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]):
|
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 i
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -110,9 +113,11 @@ class TCP:
|
|||||||
current_timestamp = time.time()
|
current_timestamp = time.time()
|
||||||
for ip in list(self.packets.keys()):
|
for ip in list(self.packets.keys()):
|
||||||
for packet in self.packets[ip]:
|
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
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def __getitem__(self, src_ip):
|
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)
|
return self.packets.get(src_ip, None)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
hiredis==3.0.0
|
mysql-connector-python==9.1.0
|
||||||
redis==5.2.0
|
scapy=2.6.1
|
||||||
|
5
sql/adduser.sql
Normal file
5
sql/adduser.sql
Normal file
@ -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;
|
156
sql/cef-generator.py
Normal file
156
sql/cef-generator.py
Normal file
@ -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()
|
@ -1,6 +1,16 @@
|
|||||||
|
#-----------------------------------------------
|
||||||
|
# Nettoyage des tables dans la base de données
|
||||||
|
#-----------------------------------------------
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS alertes;
|
||||||
|
|
||||||
|
#-----------------------------------------------
|
||||||
|
# Table: alertes
|
||||||
|
#----------------------------------------------
|
||||||
|
|
||||||
CREATE TABLE alertes (
|
CREATE TABLE alertes (
|
||||||
id SERIAL PRIMARY KEY, -- Identifiant unique pour chaque alerte
|
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
|
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
|
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_product VARCHAR(63), -- Nom du produit à l'origine de l'alerte
|
35
sql/init.sql
Normal file
35
sql/init.sql
Normal file
@ -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
|
||||||
|
);
|
@ -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()
|
|
Loading…
x
Reference in New Issue
Block a user