
Broker de señuelo experimental
Un señuelo de red contenerizado (honeypot) que anuncia SSH, RDP y SMB, observa cada conexión entrante con eBPF y redirige mediante proxy inverso cada sesión a un contenedor señuelo aislado.
El diseño separa dos aspectos:
Esta es una herramienta defensiva para detectar y estudiar actividad no autorizada en redes que posees o estás autorizado a monitorear. Despliégalo solo donde tengas esa autoridad.
flowchart TB
A["Atacante / Escáner"]
subgraph host["Host Señuelo"]
direction TB
NIC["broker eth0<br/>publicados: 22, 3389, 445"]
subgraph brk["contenedor broker"]
direction TB
E["Clasificador eBPF TC<br/>registra cada SYN<br/>ve la IP fuente real"]
P["proxy inverso<br/>CONECTAR al backend"]
L["logs JSON estructurados"]
end
subgraph dec["decoynet (interna, sin ruta al host)"]
direction LR
S["ssh-decoy<br/>OpenCanary ssh<br/>puerto 2222"]
D["rdp-decoy<br/>OpenCanary rdp<br/>puerto 3389"]
M["smb-decoy<br/>Servidor SMB Impacket<br/>puerto 445"]
end
end
A --> NIC
NIC --> E
NIC --> P
E --> L
P --> S
P --> D
P --> M
Cuatro contenedores en total:
| Contenedor | Rol | Red |
|---|---|---|
broker | Puerta frontal pública: observación eBPF más proxy inverso | edge + decoynet |
ssh-decoy | Módulo ssh de OpenCanary (apretón de manos real, captura credenciales) | solo decoynet |
rdp-decoy | Módulo rdp de OpenCanary (imitación NLA, captura nombres de usuario) | solo decoynet |
smb-decoy | SimpleSMBServer de Impacket (SMB2/3 real, captura autenticación) | solo decoynet |
Los señuelos viven en una red Docker internal (decoynet) sin ruta al host ni al mundo exterior. Solo el broker puede alcanzarlos. Nada de lo que un atacante haga dentro de un señuelo puede alcanzar la red del host directamente.
El broker publica los puertos 22, 3389 y 445 al host, por lo que los paquetes entrantes llegan a la eth0 del broker. Dos cosas suceden entonces con cada paquete:
broker/bpf/decoy.bpf.c) analiza las cabeceras Ethernet, IP y TCP, y por cada nuevo intento de conexión (SYN establecido, ACK limpio) escribe un conn_event en un buffer anular: IP fuente y puerto, puerto destino, flags TCP, y si el puerto es un servicio anunciado. El paquete se pasa sin cambios (TC_ACT_OK).CONNECT al backend señuelo para ese servicio, luego retransmite bytes en ambos sentidos.El mapa eBPF advertised_ports se rellena al inicio desde config.yaml, por lo que el clasificador puede etiquetar si un sondeo alcanzó un puerto servido o uno no solicitado. Esto hace visibles los escaneos de puertos horizontales aunque solo se estén proxyando tres puertos.
Si deseas anunciar "todo está abierto" y canalizar puertos destino arbitrarios hacia el broker, extiende el clasificador para reescribir el puerto destino o usa una redirección TPROXY / bpf_sk_assign. La versión actual mantiene la ruta del paquete intacta y se limita a la observación, que es el valor predeterminado más seguro.
cyber-decoy/
├── README.md
├── docker-compose.yml # Pila de 4 contenedores
├── docker-compose.override.yml # Desarrollo local macOS: sin capacidades eBPF, reasignación del puerto 22
├── Makefile # Helpers build / up / down / bpf
├── LICENSE
├── scripts/
│ └── setup.sh # Comprobaciones previas del host
├── broker/
│ ├── Dockerfile # Compila objeto eBPF + binario Go
│ ├── config.yaml # Servicios anunciados (configurables)
│ ├── go.mod
│ ├── main.go # Punto de entrada
│ ├── bpf/
│ │ └── decoy.bpf.c # Clasificador eBPF TC
│ └── internal/
│ ├── config/config.go # Cargador de configuración
│ ├── proxy/proxy.go # Proxy inverso TCP
│ └── bpf/loader.go # Carga + adjunta eBPF, transmite eventos
└── decoys/ # Los tres ejecutan OpenCanary
├── ssh/
│ ├── Dockerfile
│ └── opencanary.conf # Módulo ssh, puerto 2222
├── rdp/
│ ├── Dockerfile
│ └── opencanary.conf # Módulo rdp, puerto 3389
└── smb/
├── Dockerfile # Proceso Python único, no root
├── smb_decoy.py # SimpleSMBServer de Impacket + registro JSON
└── requirements.txt # impacket (fijado)
docker-compose.override.yml incluido, que depende de las etiquetas !reset / !override).sudo mount -t bpf bpf /sys/fs/bpf.La imagen del broker detecta su arquitectura de compilación y pasa la macro __TARGET_ARCH_* correspondiente a clang, por lo que se compila tanto en x86_64 como en aarch64 (Apple Silicon, Graviton). Ten en cuenta que gcc-multilib deliberadamente no está instalado: es un paquete solo x86 sin candidato arm64, e incluirlo rompe la compilación en arm64 con el código de salida 100 de apt. Solo se necesitan clang y libbpf-dev para compilar el objeto eBPF.
Docker Desktop en macOS ejecuta contenedores dentro de una máquina virtual LinuxKit en lugar de en tu kernel host, por lo que el adjunto TC/TCX eBPF generalmente no funcionará allí. Esto no es fatal: eBPF es de mejor esfuerzo por diseño, por lo que el broker registra ebpf disabled: attach failed y el proxy inverso más los tres señuelos se ejecutan y registran normalmente. Puedes desarrollar y probar toda la ruta del proxy localmente, luego obtener observación eBPF real cuando despliegues en un host Linux.
docker-compose.override.yml se carga automáticamente y hace esto agradable: elimina las capacidades eBPF (inútiles en la VM) y reasigna el puerto host 22 a 2022, ya que el propio sshd del Mac posee el 22.
docker compose up --build # desarrollo local, override aplicado
docker compose -f docker-compose.yml up -d # despliegue real, override omitido
Ejecuta la comprobación previa primero:
./scripts/setup.sh
# 1. Construir las cuatro imágenes (compila el objeto eBPF dentro de la imagen del broker)
make build
# 2. Iniciar la pila
make up
# 3. Observar lo que sucede
make logs
Luego pruébalo desde otra máquina (o localhost para una prueba de humo):
ssh -p 22 user@DECOY_HOST # golpea el señuelo SSH
nc DECOY_HOST 3389 # golpea el señuelo RDP
nc DECOY_HOST 445 # golpea el señuelo SMB
nc DECOY_HOST 8080 # no anunciado: observado por eBPF, sin proxy
El broker emite JSON para eventos de sonda eBPF y sesiones proxyadas; cada señuelo emite eventos JSON de OpenCanary. Para ver las credenciales llegar: