
Broker de leurres expérimentaux
Un leurre réseau conteneurisé (honeypot) qui annonce SSH, RDP et SMB, observe chaque connexion entrante avec eBPF, et proxy inverse chaque session dans un conteneur leurre isolé.
La conception sépare deux préoccupations :
Cet outil est un outil défensif pour détecter et étudier les activités non autorisées sur les réseaux que vous possédez ou que vous êtes autorisé à surveiller. Déployez-le uniquement là où vous avez cette autorité.
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
Quatre conteneurs au total :
| Container | Rôle | Réseau |
|---|---|---|
broker | Porte d'entrée publique : observation eBPF et proxy inverse | edge + decoynet |
ssh-decoy | Module ssh OpenCanary (vrai handshake, capture des identifiants) | decoynet uniquement |
rdp-decoy | Module rdp OpenCanary (imite NLA, capture les noms d'utilisateur) | decoynet uniquement |
smb-decoy | SimpleSMBServer d'Impacket (vrai SMB2/3, capture l'authentification) | decoynet uniquement |
Les leurres vivent sur un réseau Docker internal (decoynet) sans route vers l'hôte ou le monde extérieur. Seul le broker peut les atteindre. Rien de ce qu'un attaquant fait à l'intérieur d'un leurre ne peut atteindre directement le réseau hôte.
Le broker publie les ports 22, 3389 et 445 vers l'hôte, donc les paquets entrants arrivent sur l'eth0 du broker. Deux choses se produisent alors pour chaque paquet :
broker/bpf/decoy.bpf.c) analyse les en-têtes Ethernet, IP et TCP, et pour chaque nouvelle tentative de connexion (SYN positionné, ACK effacé) écrit un conn_event dans un tampon circulaire : IP source et port, port de destination, drapeaux TCP, et si le port est un service annoncé. Le paquet est transmis inchangé (TC_ACT_OK).CONNECT vers le backend leurre pour ce service, puis relaie les octets dans les deux sens.La table eBPF advertised_ports est peuplée au démarrage depuis config.yaml, afin que le classifieur puisse marquer si un sondage a touché un port servi ou non sollicité. Cela rend visibles les scans de ports horizontaux même si seuls trois ports sont proxyés.
Si vous souhaitez annoncer « tout est ouvert » et diriger des ports de destination arbitraires vers le broker, étendez le classifieur pour réécrire le port de destination ou utilisez une redirection TPROXY / bpf_sk_assign. La version actuelle laisse le chemin du paquet intact et se limite à l'observation, ce qui est le défaut le plus sûr.
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 fourni, qui repose sur les balises !reset / !override).sudo mount -t bpf bpf /sys/fs/bpf.L'image broker détecte son architecture de construction et transmet la macro __TARGET_ARCH_* correspondante à clang, de sorte qu'elle se compile à la fois sur x86_64 et aarch64 (Apple Silicon, Graviton). Notez que gcc-multilib n'est délibérément pas installé : c'est un paquetage x86 uniquement sans candidat arm64, et l'inclure casse la construction sur arm64 avec le code de sortie apt 100. Seuls clang et libbpf-dev sont nécessaires pour compiler l'objet eBPF.
Docker Desktop sur macOS exécute les conteneurs dans une VM LinuxKit plutôt que sur votre noyau hôte, donc l'attachement eBPF TC/TCX ne fonctionnera généralement pas là-bas. Ce n'est pas fatal : eBPF est au mieux fourni par conception, donc le broker enregistre ebpf disabled: attach failed et le proxy inverse ainsi que les trois leurres fonctionnent et journalisent normalement. Vous pouvez développer et tester l'ensemble du chemin de proxy localement, puis obtenir une véritable observation eBPF lorsque vous déployez sur un hôte Linux.
docker-compose.override.yml est chargé automatiquement et rend cela agréable : il supprime les capacités eBPF (inutiles dans la VM) et remappe le port hôte 22 vers 2022, puisque le propre sshd du Mac occupe le 22.
docker compose up --build # local dev, override applied
docker compose -f docker-compose.yml up -d # real deployment, override bypassed
Exécutez d'abord la vérification préliminaire :
./scripts/setup.sh
# 1. Build all four images (compiles the eBPF object inside the broker image)
make build
# 2. Start the stack
make up
# 3. Watch what happens
make logs
Ensuite, sondez-le depuis une autre machine (ou localhost pour un test de fumée) :
ssh -p 22 user@DECOY_HOST # hits the SSH decoy
nc DECOY_HOST 3389 # hits the RDP decoy
nc DECOY_HOST 445 # hits the SMB decoy
nc DECOY_HOST 8080 # unadvertised: observed by eBPF, no proxy