
Experimenteller Köder-Broker
Ein containerisierter Netzwerk-Köder (Honeypot), der SSH, RDP und SMB annonciert, jede eingehende Verbindung mit eBPF beobachtet und jede Sitzung per Reverse-Proxy in einen isolierten Köder-Container leitet.
Das Design trennt zwei Aufgaben:
Dies ist ein defensives Werkzeug zur Erkennung und Untersuchung unbefugter Aktivitäten in Netzwerken, die Ihnen gehören oder die Sie überwachen dürfen. Setzen Sie es nur dort ein, wo Sie diese Befugnis haben.
flowchart TB
A["Attacker / Scanner"]
subgraph host["Decoy Host"]
direction TB
NIC["broker eth0<br/>published: 22, 3389, 445"]
subgraph brk["broker container"]
direction TB
E["eBPF TC classifier<br/>logs every SYN<br/>sees true source IP"]
P["reverse proxy<br/>CONNECT to backend"]
L["structured JSON logs"]
end
subgraph dec["decoynet (internal, no host route)"]
direction LR
S["ssh-decoy<br/>OpenCanary ssh<br/>port 2222"]
D["rdp-decoy<br/>OpenCanary rdp<br/>port 3389"]
M["smb-decoy<br/>Impacket SMB server<br/>port 445"]
end
end
A --> NIC
NIC --> E
NIC --> P
E --> L
P --> S
P --> D
P --> M
Insgesamt vier Container:
| Container | Rolle | Netzwerk |
|---|---|---|
broker | Öffentliche Eingangstür: eBPF-Beobachtung plus Reverse-Proxy | edge + decoynet |
ssh-decoy | OpenCanary ssh-Modul (echter Handshake, erfasst Anmeldedaten) | nur decoynet |
rdp-decoy | OpenCanary rdp-Modul (NLA-Imitation, erfasst Benutzernamen) | nur decoynet |
smb-decoy | Impacket SimpleSMBServer (echtes SMB2/3, erfasst Authentifizierung) | nur decoynet |
Die Köder leben in einem internal Docker-Netzwerk (decoynet) ohne Route zum Host oder zur Außenwelt. Nur der Broker kann sie erreichen. Nichts, was ein Angreifer in einem Köder tut, kann direkt das Host-Netzwerk erreichen.
Der Broker veröffentlicht die Ports 22, 3389 und 445 an den Host, sodass eingehende Pakete auf der eth0 des Brokers eintreffen. Zwei Dinge passieren dann mit jedem Paket:
broker/bpf/decoy.bpf.c) parst die Ethernet-, IP- und TCP-Header und schreibt für jeden neuen Verbindungsversuch (SYN gesetzt, ACK nicht gesetzt) ein conn_event in einen Ringpuffer: Quell-IP und Port, Ziel-Port, TCP-Flags und ob der Port ein annoncierter Dienst ist. Das Paket wird unverändert durchgelassen (TC_ACT_OK).CONNECT zum hinterlegten Köder-Backend für diesen Dienst aus, leitet dann Bytes in beide Richtungen.Die eBPF-Map advertised_ports wird beim Start aus config.yaml befüllt, sodass der Klassifikator markieren kann, ob ein Test einen bedienten Port oder einen unaufgeforderten Port getroffen hat. Dadurch sind horizontale Portscans sichtbar, obwohl nur drei Ports proxied werden.
Wenn Sie „alles ist offen“ annoncieren und beliebige Zielports in den Broker leiten möchten, erweitern Sie den Klassifikator, um den Zielport umzuschreiben, oder verwenden Sie eine TPROXY/bpf_sk_assign-Umleitung. Die aktuelle Version lässt den Paketpfad unberührt und beschränkt sich auf Beobachtung, was die sicherere Voreinstellung ist.
cyber-decoy/
├── README.md
├── docker-compose.yml # 4-container stack
├── docker-compose.override.yml # local macOS dev: no eBPF caps, port 22 remap
├── Makefile # build / up / down / bpf helpers
├── LICENSE
├── scripts/
│ └── setup.sh # host preflight checks
├── broker/
│ ├── Dockerfile # compiles eBPF object + Go binary
│ ├── config.yaml # advertised services (configurable)
│ ├── go.mod
│ ├── main.go # entrypoint
│ ├── bpf/
│ │ └── decoy.bpf.c # eBPF TC classifier
│ └── internal/
│ ├── config/config.go # config loader
│ ├── proxy/proxy.go # TCP reverse proxy
│ └── bpf/loader.go # loads + attaches eBPF, streams events
└── decoys/ # all three run OpenCanary
├── ssh/
│ ├── Dockerfile
│ └── opencanary.conf # ssh module, port 2222
├── rdp/
│ ├── Dockerfile
│ └── opencanary.conf # rdp module, port 3389
└── smb/
├── Dockerfile # single Python process, non-root
├── smb_decoy.py # Impacket SimpleSMBServer + JSON logging
└── requirements.txt # impacket (pinned)
docker-compose.override.yml verwenden, die auf die !reset/!override-Tags angewiesen ist).sudo mount -t bpf bpf /sys/fs/bpf.Das Broker-Image erkennt seine Build-Architektur und übergibt das passende Makro __TARGET_ARCH_* an clang, sodass es sowohl auf x86_64 als auch auf aarch64 (Apple Silicon, Graviton) baut. Beachten Sie, dass gcc-multilib bewusst nicht installiert ist: Es ist ein reines x86-Paket ohne arm64-Kandidaten, und seine Einbindung bricht den Build auf arm64 mit apt-Exitcode 100 ab. Es werden nur clang und libbpf-dev benötigt, um das eBPF-Objekt zu kompilieren.
Docker Desktop unter macOS führt Container in einer LinuxKit-VM aus, nicht auf Ihrem Host-Kernel, daher funktioniert TC/TCX-eBPF-Attach dort im Allgemeinen nicht. Das ist nicht fatal: eBPF ist absichtlich als „Best Effort“ konzipiert, sodass der Broker ebpf disabled: attach failed protokolliert und der Reverse-Proxy sowie alle drei Köder normal laufen und protokollieren. Sie können den gesamten Proxy-Pfad lokal entwickeln und testen und dann echte eBPF-Beobachtung erhalten, wenn Sie auf einem Linux-Host bereitstellen.
docker-compose.override.yml wird automatisch geladen und macht dies angenehm: Es entfernt die eBPF-Fähigkeiten (in der VM nutzlos) und kartiert Host-Port 22 auf 2022 um, da der Mac seinen eigenen sshd auf Port 22 hat.
docker compose up --build # lokale Entwicklung, Override angewandt
docker compose -f docker-compose.yml up -d # echte Bereitstellung, Override umgangen
Führen Sie zuerst den Vorab-Check durch:
./scripts/setup.sh
# 1. Baue alle vier Images (kompiliert das eBPF-Objekt im Broker-Image)
make build
# 2. Starte den Stack
make up
# 3. Beobachte, was passiert
make logs
Testen Sie dann von einem anderen Rechner (oder localhost für einen Rauchtest):
ssh -p 22 user@DECOY_HOST # trifft den SSH-Köder
nc DECOY_HOST 3389 # trifft den RDP-Köder
nc DECOY_HOST 445 # trifft den SMB-Köder
nc DECOY_HOST 8080 # nicht annonciert: von eBPF beobachtet, kein Proxy