Lab-Bericht — Pentest gegen das eigene IDS

Stand 2026-04-30. Kali Linux (192.168.1.85) feuert über eine spiegelnde Tap-VM (192.168.1.95) gegen einen Linuxhost (192.168.1.80); das Master-IDS (192.168.1.81) wertet aus. Sechs Phasen: Heuristik-Validierung, ML-Engine-Tuning, Backpressure-Fix, DNS-Vektoren, Suppression-Lebenszyklus, rule-tuner Feedback-Loop. Jeder Test ist als Angriffsmuster → IDS-Antwort → Bewertung dokumentiert; gefundene Bugs wurden im Repo gefixt.

Vollständige Markdown-Quelle: CyjanKali.md.

Setup

RolleIPUserNotizen
Kali (Angreifer)192.168.1.85janProxmox-VM
Linuxhost (Ziel)192.168.1.80janDebian Desktop, lauscht auf 22, 3389
Tap (Sniffer)192.168.1.95idssniffert ens19, gepairt mit Master
Master-IDS192.168.1.81idsvolle Pipeline + Frontend

Passwörter sind bewusst nicht im Lab-Bericht dokumentiert. Setup-Credentials werden außerhalb dieses Repos gepflegt.

Test-Übersicht

IDPhaseAngriffsmustersigsurimlcorrBewertung
T11nmap Top-1000 Stealth-Scan2000sig ✓, ml stumm
T21nmap Full-Port -sT -sV7000sig ✓, ml stumm
T31hping3 SYN-Flood (--flood -c 20000)2000sig ✓, löst Tap-Backpressure aus
T41-DThreshold-Diagnose 0.402040Modell unterscheidet Mini-Flows nicht
T92nmap Top-1000 nach Tap-Restart2100sig ✓ wieder normal
T102nmap Top-1000 + Threshold 0.402010ml fängt nicht-Kali-Anomalie
T112hping3 SYN-Flood -i u200 -c 100003 (incl. critical)100sig ✓ critical, ml stumm (erwartet)
BT3Burst-Test: hping3 + sofort nmap5001Backpressure-Fix verifiziert: kafka_drop=0
D14DNS-Flood (1000 q @ 300 pps)2000TUNNEL_001 + FRAGMENT_001
D24DNS-Tunnel-Pattern (lange Subdomains)(eng)000TUNNEL im engine-log, dedup unterdrückt DB
D34DGA-Pattern (uniform IAT)0000IAT-Entropy 1.28 unter Schwelle 2.5
D44DNS-Flood gegen non-listening Port3000AMP_001 + TUNNEL_001 + FRAGMENT_001
D54DGA-Pattern (bimodal IAT)0000IAT 1.34 — Schwelle gesenkt 2.5 → 1.5
D64DGA bimodal nach Schwellen-Anpassung3000fängt parallel echten Subnet-Host (.36)
D74DGA trimodal IAT2000DGA_001 high score 0.80
S15Suppression: Single-Alert mit Baseline(1)000severity → low + tag ml-suppressed
S3560-Alert-Burst (cache cold)(60)000alle suppressed (Cache lag bei Z=0)
S45Alert nach Spike-Durchbruch(1)000severity medium, keine Tags — Z=54 ✓
RT16rule-tuner: 5 min Training + PentestsSCAN_001 internal 8 → 1049 ✓
RT26rule-tuner: Floor-ConstraintSCAN_004 internal 8 → 9 ✓
RT36rule-tuner: FP/TP-KonfliktBug entdeckt → fix → bewahrt ✓

Phase 1 — Heuristik-Validierung gegen das ungetunte System (T1–T3)

Erste Pentest-Reihe gegen die signature-engine. ML-Engine im Default-Stand (threshold 0,65, 14 Features) — Ausgangslage „letzte 24 h: 0 ML-Alerts".

Test 1 — nmap Top-1000 Stealth-Scan

Angriffsmuster: Klassischer SYN-Stealth-Scan auf die 1 000 häufigsten Ports. Erwartet: SCAN_001, DOS_CONN_001.

nmap -sS -T4 -Pn --top-ports 1000 192.168.1.80
IDS-Antwort (Dauer 0,74 s)
tssourceseverityrule_idportscore
16:18:38.529signaturehighSCAN_0011430.80
16:18:38.547signaturehighDOS_CONN_001179880.80

Bewertung: Heuristik nagelt es korrekt fest. ML feuert nicht — wie in der 24-h-Beobachtung.

Test 2 — nmap Full-Port mit Service-Detection

Angriffsmuster: Vollständiger TCP-Connect-Scan über alle 65 535 Ports plus Service-Banner-Probes (~3 300 pps).

nmap -sT -sV -T4 -Pn -p- 192.168.1.80
IDS-Antwort (Dauer 19,5 s) — signature=7 ml=0
severityrule_idBemerkung
highDOS_CONN_001Verbindungsflut Kali → 80
criticalDOS_SYN_001SYN-Burst
highSCAN_001Portscan
lowRECON_003RST-Pattern
highSCAN_001 (reverse)80 → 85 — Antwort-Pattern triggert in Gegenrichtung

Bewertung: Heuristik feuert breit und korrekt-priorisiert. ML weiterhin still.

Test 3 — hping3 SYN-Flood

Angriffsmuster: Dauer-SYN-Flood gegen Port 80 mit hoher Paketrate. Klassischer DoS-Versuch.

sudo hping3 -S -p 80 --flood -c 20000 192.168.1.80
IDS-Antwort (Dauer ~4 min) — signature=2 ml=0
  • DOS_CONN_001 (high)
  • DOS_SYN_001 (critical)

Bewertung: Heuristik korrekt critical, ML auch hier still bei einem SYN-Flood — substanziell unterempfindlich.

Nebeneffekt: dieser 4-min-Flood stopfte den Tap-Kafka-Producer-Buffer. Folge-Tests T5–T8 sahen über 5 min lang fast keinen Verkehr. Daraus folgt der Backpressure-Fix in Phase 3.

Diagnose T4 — Threshold-Probe 0.65 → 0.40

Threshold zur Laufzeit über /models/ml_config.json auf 0,40 abgesenkt. Score-Verteilung der nächsten 5 min:

Score-BucketAlerts
0.448
0.53
≥ 0.60

Top-Scorer waren mDNS-Discovery, MQTT, normaler SSH-Verkehr — die Kali- Tests tauchten unter den Top-Scorern überhaupt nicht auf. Daraus folgt: das Problem ist nicht der Threshold, das Problem ist das Modell — 14 sehr basale Flow-Statistiken reichen nicht aus.

Phase 2 — Tests nach ML-Re-Training (T9–T11)

Code-Änderungen vor Phase 2 (Commits ee26503 + 1496fc0): 4 zusätzliche Features (FEATURE_DIM 14 → 18), bereinigtes Bootstrap-SQL (JSONB-Pfad, ≥2 h alt, ohne Alert-Verknüpfung), contamination 0,01 → 0,005, n_estimators 100 → 200, training-loop synchronisiert auf 18 Features, Compose-Env-Bug gefixt.

Test 9 — nmap Top-1000 mit Modell v2

nmap -sS -T4 -Pn --top-ports 1000 192.168.1.80
IDS-Antwort — signature=2 suricata=1 ml=0
tssourceseverityrule_idport
16:51:15.267signaturehighSCAN_001445
16:51:15.285signaturehighDOS_CONN_0013766
16:51:33.073suricatamediumSURICATA:1:2210016:280

Test 10 — nmap Top-1000 mit Threshold 0.40

IDS-Antwort
tssourceseverityrule_iddst_ipscore
16:57:49.493signaturehighSCAN_001192.168.1.800.80
16:57:49.512signaturehighDOS_CONN_001192.168.1.800.80
16:57:56.785mllowML_ANOMALY160.79.104.100.43

Bewertung: ML feuert auf einen anderen Flow (Linuxhost zu CDN) — die „ehrliche Lieferung" des Modells. Es markiert ungewöhnliche Single-Flow-Bandbreitenprofile, nicht den Scan selbst. Top-Score normaler Subnet-Verkehr klettert von 0,46 (Modell v1) auf 0,55 (Modell v2).

Test 11 — hping3 SYN-Flood, kontrolliert

sudo hping3 -S -p 80 -i u200 -c 10000 192.168.1.80
IDS-Antwort — signature=3 suricata=1 ml=0
  • DOS_CONN_001 high
  • DOS_SYN_001 critical
  • RECON_003 low
  • SURICATA:1:2210016:2 medium

Bewertung: Heuristik korrekt + sofort. ML stumm — strukturell richtig: hping3 randomisiert src_port pro Paket, daher landen 10 000 Mini-Flows mit je 1 SYN-Paket im Aggregator. IsolationForest auf Single-Flow-Features kann das prinzipiell nicht detektieren — diese Multi-Flow-Aggregation leistet die signature-engine über ctx.flow_rate(src_ip, window_s).

Phase 3 — Burst-Test nach Backpressure-Fix (BT)

Commit 2c957bb: sniffer queue.buffering.max.messages 100k → 500k, channel_capacity 10k → 100k, größere Batches; flow-aggregator 50k → 500k Producer-Puffer, sauberer Drop statt blocking-Retry (5-s-rate-limited Warning, neuer kafka_dropped-Counter).

Test BT — Burst + sofort nmap

Worst-Case-Folge: 5-kpps-SYN-Flood, direkt danach nmap-Scan. Vor dem Fix hätte der nmap nichts mehr durchbekommen — der Tap-Buffer wäre 5+ Minuten am Aufholen.

sudo hping3 -S -p 80 -i u200 -c 10000 192.168.1.80   # 5 kpps × 2 s
nmap -sS -T4 -Pn --top-ports 1000 192.168.1.80       # direkt im Anschluss
IDS-Antwort — signature=5 correlation=1
tssourceseverityrule_idPhase
17:29:19.162signaturehighDOS_CONN_001hping3
17:29:19.570correlationlowUNKNOWN_HOST_001hping3
17:29:19.924signaturecriticalDOS_SYN_001hping3
17:29:22.425signaturelowRECON_003hping3
17:29:24.493signaturehighSCAN_001nmap
17:29:24.494signaturemediumRECON_001nmap

Backpressure-Metriken

MetrikTap (95)Master (81)
kafka_drop während Burst00
Sniffer drop_pct0,00 %0,00 %
kafka_ok-Aufholzeit nach Burst~30 s~30 s
nmap nach Burst durchgekommen?

Bewertung: Vor Fix: 5+ min Pipeline-Stillstand. Jetzt: 30 s Aufholzeit, danach sofort einsatzbereit. Operator-Diagnose: docker logs --tail 5 cyjan-tap-flow-aggregator | grep kafka_drop.

Phase 4 — DNS-Angriffsmuster (D1–D7)

Vier DNS-Heuristiken in signature-engine/rules/dns.yml: DNS_AMP_001, DNS_TUNNEL_001, DNS_DGA_001, DNS_NONSTANDARD_001. Generator: Python-Skript mit einzelnem UDP-Socket → ein Flow im IDS.

Test D1 — DNS-Flood mit Amplification-Profil

python3 /tmp/dnsattack.py --mode amp --count 1000 --rate 300
# → sent=1000 pps=300.3 avg_pkt=33.9 byte_out=33.9 KB
Flow-Profil + IDS-Antwort
  • Flow: pkt_count=2 000 (bidirektional), byte_count=204 468, pps=600,5, pkt_size_mean=102,2
  • DNS_TUNNEL_001 (high, 0.80) ✓ — byte_count > 50 k
  • ANOMALY_FRAGMENT_001 (low, 0.20) ✓ — DNS-Replies fragmentiert

Bewertung: TUNNEL trifft sauber. AMP_001 trifft nicht — pkt_size_mean 102 liegt knapp über der 100-Byte-Schwelle, weil DNS-Antworten den Mean hochziehen (bidirektionale Aggregation).

Test D4 — DNS-Flood gegen non-listening Port (Reflection-Source-Profil)

Angriffsmuster: Hochfrequente DNS-Queries gegen den Master auf Port 53 — der dort nicht lauscht. Damit geht der Flow rein outgoing: keine DNS-Replies, nur ICMP-Port-Unreachable. Entspricht exakt dem Profil eines echten Spoofed-Reflection-Angriffs.

python3 /tmp/dnsattack_v2.py --server 192.168.1.81 --mode amp --count 1000 --rate 300
# → sent=991 pps=297.6 avg_pkt=33.9
IDS-Antwort — Volltreffer auf 3 Rules
tsseverityrule_idscore
18:14:31.771mediumDNS_AMP_0010.50
18:14:31.771highDNS_TUNNEL_0010.80
18:14:31.771lowANOMALY_FRAGMENT_0010.20

Bewertung: Default-Schwelle pkt_size_mean < 100 ist richtig kalibriert für echte Reflection-Angriffe — sie verfehlt nur Tests, in denen der Lab-Resolver tatsächlich antwortet (D1).

Tests D3, D5, D7 — DGA-Pattern + Schwellen-Anpassung

Drei IAT-Verteilungen gegen DNS_DGA_001 (Default-Schwelle entropy_iat > 2.5, gesenkt auf 1.5 in Commit 48659ff):

TestIAT-PatternIAT-EntropieSchwelle 2.5 (alt)Schwelle 1.5 (neu)
D3uniform random sleep1,28neinnein
D5bimodal 80/201,34neinnein
D6 (Subnet-FP)real chaotic resolver~1,5+neinja
D7trimodal 2ms / 30ms / 225ms1,84neinja (high, score 0.80)

Bewertung: 1,5 markiert die richtige Grenze: chaotisches Multi-Modal-Timing löst aus, monomodales/bimodales Idle-Verhalten nicht. Operator kann via Settings → Rule Adjustments → DNS_DGA_001 → entropy_iat jederzeit weiter anpassen.

Befund Phase 4 — DNS-Detektion komplett funktional

RuleDefault-ParameterLab-TrefferBemerkung
DNS_AMP_001pps>100, pkt_size_mean<100✓ in D4Default richtig kalibriert für Spoofed-Reflection
DNS_TUNNEL_001byte_count>50 000✓ in D1, D2, D4Robust auf Volumen-Pattern
DNS_DGA_001entropy_iat>1.5 (gesenkt von 2.5)✓ in D7 + Real-Hit auf .36Schwelle nach D5-Befund auf 1,5 angepasst
DNS_NONSTANDARD_001pkt_size_mean>512 für TCP/53nicht getestetDNS-over-TCP im Lab-Subnet selten

Phase 5 — ML-Suppression-Lebenszyklus (S1–S4)

Zwei Suppression-Schichten im alert-manager: Manual-FP (auto-suppressed) und ML-Adaptive (ml-suppressed) mit Z-Score-Spike-Durchbruch. Pre-Test-Status: 73 ML-Learned + 29 Manual-FP Patterns aktiv, 530 ml-suppressed Hits / 24 h, 1 Spike-Durchbruch.

Test S1 — Single-Alert mit aktiver Baseline

Synthetische Baseline injiziert: 50 Alerts SUPPRESSION_TEST_001 über 11 Stunden (mean=4,55/h, std=1,04). Cache nahm Pattern auf: 73 → 74 ML-Learned.

1 Alert via Kafka mit severity=medium
tsseveritytags
18:37:29low{ml-suppressed}

Suppression greift wie spec: Severity medium → low, Tag gesetzt. ✓

Tests S3 + S4 — Spike-Durchbruch

S3: 60 Alerts in <1 s mit gleichem rule+ip aber varying dst_port (umgeht 5-min-Dedup). Alle 60 wurden suppressed — der Cache hatte recent_1h aus dem letzten Refresh (= 0) noch im Speicher. Refresh-Latenz sichtbar.

Cache-Refresh nach 60 s:

recent_1hmeanstdz_score
614,551,0454,28

Cache-Stats jetzt: "73 aktiv suppressed, 1 Spike-Durchbruch".

S4: 1 neuer Alert nach Refresh:

tsseveritytags
18:40:09medium{}

Bewertung: Severity bleibt original, kein Tag. Spike- Durchbruch wirkt — gesamter Lebenszyklus verifiziert.

Maßnahme — Cache-Refresh-Intervall (Commit b58c208)

REFRESH_INTERVAL_S 60 → 30 s + ENV-tunbar (SUPPRESSION_REFRESH_INTERVAL_S). Halbiert die Worst-Case- Latenz vom Spike-Beginn bis Durchbruch (~2 min → ~1 min). DB-Last bleibt unter 100 ms (indizierte Felder).

Defaults LEARN_WINDOW_D=14, MIN_HOURS=8, Z_THRESHOLD=2.0 bleiben unverändert — gut kalibriert.

Phase 6 — rule-tuner Feedback-Loop (RT1–RT3)

Der rule-tuner-Service lernt per-Rule-Param-Quantile aus dem Topic rule-metrics und schreibt sie als ML-getunte Werte in _overrides.json. Default: RESERVOIR_SIZE=10 000, quantile=0,995, SAFETY_MARGIN=1,05, max_change_per_cycle=0,20, neu seit Commit c776cc0: floor_factor=0,3.

Test RT1 — Frischer Trainingslauf mit parallelen Pentests

State → training, training_until = now() + 5 min. Parallel auf Kali: 3× nmap top-1000, 1× hping3 SYN-Burst, 1× DNS-Flood.

Override-Werte vorher/nachher (first_apply, ohne max_change-Klemme)
Rule.Param.scopeVorher (13:28)Nachher (18:52)p995_intBewertung
RECON_001.flow_count.internal9 3341 9771 883↓ realistischer
RECON_002.ip_count.internal41411FP-Bound, unverändert
RECON_003.flow_count.internal9 5192 9122 773↓ realistischer
SCAN_001.port_count.internal81 049999↑↑ massive Korrektur
SCAN_002.port_count.internal121 0501 000↑↑
SCAN_003.ip_count.internal34341FP-Bound, unverändert
SCAN_004.ip_count.internal2888zu niedrig — Floor fehlt

Bewertung: first_apply springt aggressiv auf das aktuelle Quantil. Bei reichlicher Datenlage sinnvoll (SCAN_001 von absurd-niedrig 8 → realistisch 1 049). Bei sparsamer Datenlage problematisch — SCAN_004 fiel von 28 auf 8, was praktisch jedes Subnet-Discovery alarmiert hätte. Daraus folgt RT2.

Test RT2 — Floor-Constraint (Commit c776cc0)

Sanity-Floor in tuner._postprocess(): getunte Schwelle wird nie unter schema.default × TUNER_FLOOR_FACTOR geklemmt (Default 0,3, ENV-tunbar).

Vor / nach Floor
Rule.Param.scopeVor FloorNach FloorEffekt
SCAN_004.ip_count.internal89klemmt sauber auf default × 0,3 ✓
Alle anderenunverändertunverändertbereits über Floor

max_change_per_cycle=0,20 greift parallel und limitiert auf old × 1,2 = 9,6 → int(9). Floor und max_change zusammen ergeben in diesem Cycle 9. Verifiziert.

Test RT3 — FP/TP-Konflikt-Pfad (Bug + Fix Commit afe8e49)

Synthetische Markierungen: 5× FP mit metric_value=11, 3× TP mit metric_value=9. → FP_lower = 12, TP_upper = 9 → Konflikt. Spec sagt: alten Wert behalten + Warning.

Bug-Beobachtung

value=50  value_internal=None  fp_seen=5  tp_seen=3  fp_max=12  tp_min=9

value_internal wurde auf None gesetzt → signature-engine fällt auf YAML-Default zurück (1 050 → 50, massiver Sprung).

Fix verifiziert

  1. TPs entfernt → kein Konflikt → Cycle setzt value_internal = 1 050.
  2. TPs wieder gesetzt → Konflikt → Cycle bewahrt value_internal.
2026-04-30T19:48:38 WARNING tuner – Rule SCAN_001/port_count: FP/TP-Konflikt
                              (FP_max+1=12.0 > TP_min=9.0) — alten Wert behalten
value=50  value_internal=1050  fp_seen=5  tp_seen=3  fp_max=12  tp_min=9

Bewertung: Konflikt-Pfad bewahrt jetzt sowohl value als auch value_internal aus dem existing_param-Eintrag. Skalar-Override-Form (Backwards-Compat) trägt kein value_internal — bleibt dort None (war schon vorher so).

Zusammenfassung

  1. Tap-Mirror funktioniert für normalen Subnet-Traffic. Burst-Lasten > 1 kpps verstopften vorher den Tap-Kafka-Buffer; mit Commit 2c957bb ist kafka_drop=0 beim 5 kpps × 2 s Worst-Case-Burst-Test.
  2. Heuristik (signature-engine) erkennt jeden Pentest sauber: SCAN/RECON/DOS für Scans + Floods, DNS_AMP/TUNNEL für DNS-Vektoren, plus korrekt-priorisierte Severity bis critical.
  3. ML-Engine war initial taub. Nach Commits ee26503/1496fc0 (18 Features, sauberer Bootstrap-Filter, contamination 0,005, Threshold 0,57) ist sie komplementär zur Heuristik aufgestellt: Single-Flow-Anomalien werden erkannt, Multi-Flow-Pattern bleiben Sache der Heuristik (architektonisches Limit).
  4. DNS-Detektion komplett funktional: AMP für Reflection-Source, TUNNEL für Volumen-Exfil, DGA nach Schwellen-Senkung 2,5 → 1,5 (Commit 48659ff) auch für reale chaotische Resolver-Pattern.
  5. Suppression-Lebenszyklus End-to-End verifiziert; Refresh-Intervall halbiert auf 30 s (Commit b58c208).
  6. rule-tuner mit zwei neuen Sicherungen: Sanity-Floor (Commit c776cc0) und Konflikt-Pfad-Fix (Commit afe8e49).
  7. Lehre: „ML soll nmap erkennen" ist die falsche Erwartung. Der Detektor dafür sitzt richtig in der signature-engine, ML ergänzt sie für nicht-benannte Verhaltens-Anomalien.