feat: final working demo Dockerfiles

This commit is contained in:
2024-11-21 11:49:21 -05:00
parent a1dcee53a1
commit dbad0e7b28
20 changed files with 232 additions and 40 deletions

View File

@ -2,8 +2,9 @@ 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 nmap iproute2
RUN pip install scapy
apk add --no-cache nmap iproute2
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
RUN apk -U add --no-cache hping3
COPY Demo/Dockerfiles/attaquant-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

View File

@ -1,15 +1,9 @@
FROM python:alpine3.20
FROM httpd:alpine
# Installation des paquets nécessaires pour scapy
RUN apk -U upgrade && \
apk add --no-cache libpcap libpcap-dev gcc musl-dev libffi-dev iproute2
RUN pip install scapy
# Installation des paquets nécessaire pour le routage
RUN apk -U upgrade && apk add --no-cache iproute2
COPY Demo/Dockerfiles/cible-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
# Copier le script de détection d'attaques
#COPY cible.py /cible.py
# Lancer le script de détection
#CMD ["python", "/cible.py"]
CMD ["httpd-foreground"]

View File

@ -16,7 +16,7 @@ WORKDIR /app
COPY idps /app/idps
# Copie du fichier de configuration
COPY config.json /app/config.json
COPY Demo/config/config-idps.json /app/config.json
# Utiliser le script comme point d'entrée
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -12,7 +12,7 @@ WORKDIR /app
COPY idps /app/ids
# Copie du fichier de configuration
COPY config.json /app/config.json
COPY Demo/config/config-ids.json /app/config.json
# Lancer le script de la sonde IDS
CMD ["python3", "/app/ids/ids.py"]
CMD ["python3", "/app/ids/main.py"]

View File

@ -0,0 +1,31 @@
{
"rules_dirpath": "/app/idps/rules",
"ifaces": ["eth1"],
"db_host": "172.20.3.10",
"db_database": "sidps",
"db_user": "sidps",
"db_password": "SUPERPASSWORD",
"db_port": "3306",
"cef_version": 1,
"device_product": "SIDPS",
"device_vendor": "ArKa",
"device_version": "vAlpha",
"synscan_time": 180,
"synscan_count": 5,
"tcpconnectscan_time": 180,
"tcpconnectscan_count": 5,
"ackscan_time": 180,
"ackscan_count": 5,
"finscan_time": 180,
"finscan_count": 5,
"nullscan_time": 180,
"nullscan_count": 5,
"xmasscan_time": 180,
"xmasscan_count": 5,
"synflood_time": 60,
"synflood_count": 100,
"tcpconnectflood_time": 60,
"tcpconnectflood_count": 100,
"syndos_time": 60,
"syndos_count": 100
}

View File

@ -0,0 +1,31 @@
{
"rules_dirpath": "/app/ids/rules",
"ifaces": ["br-c56b595383ad"],
"db_host": "172.20.3.10",
"db_database": "sidps",
"db_user": "sidps",
"db_password": "SUPERPASSWORD",
"db_port": "3306",
"cef_version": 1,
"device_product": "Sonde IDS",
"device_vendor": "ArKa",
"device_version": "vAlpha",
"synscan_time": 180,
"synscan_count": 5,
"tcpconnectscan_time": 180,
"tcpconnectscan_count": 5,
"ackscan_time": 180,
"ackscan_count": 5,
"finscan_time": 180,
"finscan_count": 5,
"nullscan_time": 180,
"nullscan_count": 5,
"xmasscan_time": 180,
"xmasscan_count": 5,
"synflood_time": 60,
"synflood_count": 100,
"tcpconnectflood_time": 60,
"tcpconnectflood_count": 100,
"syndos_time": 60,
"syndos_count": 100
}

View File

@ -20,7 +20,6 @@ services:
context: ..
dockerfile: Demo/Dockerfiles/Dockerfile.idps
container_name: idps
command: sleep infinity
cap_add:
- NET_ADMIN
- NET_RAW
@ -39,7 +38,6 @@ services:
context: ..
dockerfile: Demo/Dockerfiles/Dockerfile.cible
container_name: cible
command: sleep infinity
cap_add:
- NET_ADMIN
networks:
@ -67,7 +65,6 @@ services:
context: ..
dockerfile: Demo/Dockerfiles/Dockerfile.ids
container_name: ids
command: sleep infinity
cap_add:
- NET_ADMIN
- NET_RAW

View File

@ -55,11 +55,20 @@ Pour lancer cette démonstration, il faudra avoir `docker` & `docker compose` d'
```bash
cd Demo/
docker compose up -d
./deploy.sh
```
## TODO
La cible (172.20.2.3) héberge un serveur web apache avec la page par défaut sur son port 80.
Les conteneurs attaquants disposent tous les deux de nmap et de hping3 pour réaliser des scan et des floods / DOS.
Rappel des commandes pour flood avec et sans charge utile avec hping3:
```bash
hping3 -S --flood IP
hping3 -S --flood -d TAILLE IP
```
## TODO:
- Noyau d'analyse de l'IDS
- Interface web pour visualiser les alertes / rechercher dedans
- Moteur de corrélation des alertes (récupération + renvoi dans MySQL).
- Moteur de détection par comportement

View File

@ -21,5 +21,11 @@
"nullscan_time": 180,
"nullscan_count": 5,
"xmasscan_time": 180,
"xmasscan_count": 5
"xmasscan_count": 5,
"synflood_time": 60,
"synflood_count": 100,
"tcpconnectflood_time": 60,
"tcpconnectflood_count": 100,
"syndos_time": 60,
"syndos_count": 100
}

View File

@ -94,7 +94,7 @@ def packet_callback(packet, rules_functions, tcp_packets, db):
#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])
#print(tcp_packets[packet[IP].src])
check_frame_w_rules(packet, rules_functions['TCP'], tcp_packets, db)
tcp_packets.clean_old_packets()

View File

@ -0,0 +1,36 @@
from datetime import datetime
import time
def rule(packet, tcp_packets, db):
"""
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
- Si le port est ouvert, le serveur répond avec: SYN-ACK, puis le client RESET la connexion.
- Si le port est fermé, le serveur répond avec: RST-ACK.
"""
# Ne pas réagir si dans la période de cooldown
if (rule.cooldown + rule.time_window > time.time()):
return
# Initialisation des paramètres à partir de la configuration si nécessaire
if (rule.seuil == 0 and rule.time_window == 0):
rule.time_window = db.get_key("synsdos_time", 60)
rule.seuil = db.get_key("syndos_count", 100)
# Comptage des paquets TCP correspondant aux motifs spécifiques
syndeny_count = 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)
# 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):
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")
print("Alerte, seuil dépassé, risque de Syn DOS détecté.")
rule.cooldown = time.time()
# Variables statiques
rule.cooldown = 0
rule.time_window = 0
rule.seuil = 0

View File

@ -0,0 +1,36 @@
from datetime import datetime
import time
def rule(packet, tcp_packets, db):
"""
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
- Si le port est ouvert, le serveur répond avec: SYN-ACK, puis le client RESET la connexion.
- Si le port est fermé, le serveur répond avec: RST-ACK.
"""
# Ne pas réagir si dans la période de cooldown
if (rule.cooldown + rule.time_window > time.time()):
return
# Initialisation des paramètres à partir de la configuration si nécessaire
if (rule.seuil == 0 and rule.time_window == 0):
rule.time_window = db.get_key("synsflood_time", 60)
rule.seuil = db.get_key("synflood_count", 100)
# Comptage des paquets TCP correspondant aux motifs spécifiques
syndeny_count = 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)
# Détection si le seuil est dépassé
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")
print("Alerte, seuil dépassé, risque de Syn Flood détecté.")
rule.cooldown = time.time()
# Variables statiques
rule.cooldown = 0
rule.time_window = 0
rule.seuil = 0

View File

@ -0,0 +1,34 @@
from datetime import datetime
import time
def rule(packet, tcp_packets, db):
"""Règle TCPConnect Flood:
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
Sinon le port est fermé et le serveur refusera la connexion SYN -> Reset ACK
"""
if (rule.cooldown + rule.time_window > time.time()):
return
# Vérification si nécessaire de récupérer les variables depuis la config
if (rule.seuil == 0 and rule.time_window == 0):
rule.time_window = db.get_key("tcpconnectflood_time", 60)
rule.seuil = db.get_key("tcpconnectflood_count", 100)
# 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)
tcpconnectaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "A", "RA"], rule.time_window, True)
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")
print(f"Alerte, seuils dépassés, risque de TCPconnect Flood")
rule.cooldown = time.time()
# Variables statiques
rule.cooldown = 0
rule.time_window = 0
rule.seuil = 0

View File

@ -16,7 +16,11 @@ def rule(packet, tcp_packets, db):
rule.time_window = db.get_key("ackscan_time", 180)
rule.seuil = db.get_key("ackscan_count", 5)
if tcp_packets.count_packet_of_type(["A", "R"], rule.time_window, True) + tcp_packets.count_packet_of_type(["A"], rule.time_window, True) >= rule.seuil:
# Comptage nombre de scan ack acceptés et refusés
ackdeny_count = 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)
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")
print(f"Alerte, seuil dépassés, risque d'Ack scan")
rule.cooldown = time.time()

View File

@ -16,7 +16,11 @@ def rule(packet, tcp_packets, db):
rule.time_window = db.get_key("finscan_time", 180)
rule.seuil = db.get_key("finscan_count", 5)
if tcp_packets.count_packet_of_type(["F", "RA"], rule.time_window, True) + tcp_packets.count_packet_of_type(["F"], rule.time_window, True) >= rule.seuil:
# 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)
finaccept_count = tcp_packets.count_packet_of_type(["F"], rule.time_window, True)
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")
print(f"Alerte, seuil dépassés, risque de Fin Scan")
rule.cooldown = time.time()

View File

@ -16,7 +16,11 @@ def rule(packet, tcp_packets, db):
rule.time_window = db.get_key("nullscan_time", 180)
rule.seuil = db.get_key("nullscan_count", 5)
if tcp_packets.count_packet_of_type([""], rule.time_window, True) + tcp_packets.count_packet_of_type(["", "RA"], rule.time_window, True) >= rule.seuil:
# Comptage du nombre de scan null acceptés et refusés
nulldeny_count = tcp_packets.count_packet_of_type(["", "RA"], rule.time_window, True)
nullaccept_count = tcp_packets.count_packet_of_type([""], rule.time_window, True)
if (nulldeny_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")
print(f"Alerte, seuil dépassés, risque de Null Scan")
rule.cooldown = time.time()

View File

@ -3,7 +3,7 @@ import time
def rule(packet, tcp_packets, db):
"""Règle SYNScan:
"""Règle SYN Scan:
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épondra: Reset ACK
@ -16,7 +16,11 @@ def rule(packet, tcp_packets, db):
rule.time_window = db.get_key("synscan_time", 180)
rule.seuil = db.get_key("synscan_count", 5)
if tcp_packets.count_packet_of_type(["S", "RA"], rule.time_window, True) + tcp_packets.count_packet_of_type(["S", "SA", "R"], rule.time_window, True) >= rule.seuil:
# 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)
synaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "R"], rule.time_window, True)
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")
print(f"Alerte, seuil dépassés, risque de SynScan")
rule.cooldown = time.time()

View File

@ -17,7 +17,11 @@ def rule(packet, tcp_packets, db):
rule.time_window = db.get_key("tcpconnectscan_time", 180)
rule.seuil = db.get_key("tcpconnectscan_count", 5)
if tcp_packets.count_packet_of_type(["S", "SA", "A", "RA"], rule.time_window, True) + tcp_packets.count_packet_of_type(["S", "RA"], rule.time_window, True) >= rule.seuil:
# 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)
tcpconnectaccept_count = tcp_packets.count_packet_of_type(["S", "SA", "A", "RA"], rule.time_window, True)
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")
print(f"Alerte, seuils dépassés, risque de TCPConnectScan")
rule.cooldown = time.time()
@ -25,5 +29,5 @@ def rule(packet, tcp_packets, db):
# Variables statiques
rule.cooldown = 0
rule.time_window = 180
rule.seuil = 5
rule.time_window = 0
rule.seuil = 0

View File

@ -16,7 +16,11 @@ def rule(packet, tcp_packets, db):
rule.time_window = db.get_key("xmasscan_time", 180)
rule.seuil = db.get_key("xmasscan_count", 5)
if tcp_packets.count_packet_of_type(["FPU", "RA"], rule.time_window, True) + tcp_packets.count_packet_of_type(["FPU"], rule.time_window, True) >= rule.seuil:
# 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)
xmasaccept_count = tcp_packets.count_packet_of_type(["FPU"], rule.time_window, True)
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")
print(f"Alerte, seuil dépassés, risque de XMAS Scan")
rule.cooldown = time.time()

View File

@ -34,7 +34,6 @@ class TCP:
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
if i is not None:
print(f"i: {i}, {ip_src}:{port_src}->{ip_dst}:{port_dst}, paquets: \n{self.packets}")
self.packets[ip][i][3].append("SA")
self.packets[ip][i][4] = timestamp
return
@ -48,7 +47,6 @@ class TCP:
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "R")
if i is not None:
print(f"i: {i}, {ip_src}:{port_src}->{ip_dst}:{port_dst}, paquets: \n{self.packets}")
self.packets[ip][i][3].append("A")
self.packets[ip][i][4] = timestamp
return
@ -63,7 +61,6 @@ class TCP:
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
if i is not None:
print(f"i: {i}, {ip_src}:{port_src}->{ip_dst}:{port_dst}, paquets: \n{self.packets}")
self.packets[ip][i][3].append("RA")
self.packets[ip][i][4] = timestamp
return
@ -78,7 +75,6 @@ class TCP:
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "S")
if i is not None:
print(f"i: {i}, {ip_src}:{port_src}->{ip_dst}:{port_dst}, paquets: \n{self.packets}")
self.packets[ip][i][3].append("R")
self.packets[ip][i][4] = timestamp
return
@ -90,7 +86,6 @@ class TCP:
i, ip = self.find_packet_to_replace(ip_src, port_src, ip_dst, port_dst, "A")
if i is not None:
print(f"i: {i}, {ip_src}:{port_src}->{ip_dst}:{port_dst}, paquets: \n{self.packets}")
self.packets[ip][i][3].append("F")
self.packets[ip][i][4] = timestamp
return
@ -98,8 +93,6 @@ class TCP:
self.packets[ip_src].append([port_src, ip_dst, port_dst, ["F"], timestamp])
return
# TODO: ajout flag fin, none, fin urg push
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é"""