Cybersecurity-Projects/PROJECTS/advanced/honeypot-network/internal/smbd/server.go

182 lines
3.7 KiB
Go

/*
©AngelaMos | 2026
server.go
SMB honeypot service handling NetBIOS negotiate requests
Listens for SMB connections, reads the initial negotiate request,
detects SMB1 versus SMB2, sends a valid negotiate response, and
closes the connection. This negotiate-only approach is sufficient
for logging scanning activity targeting port 445 without the
complexity of full SMB session setup.
*/
package smbd
import (
"context"
"encoding/json"
"fmt"
"net"
"time"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/CarterPerez-dev/hive/internal/config"
"github.com/CarterPerez-dev/hive/internal/event"
"github.com/CarterPerez-dev/hive/internal/ratelimit"
"github.com/CarterPerez-dev/hive/internal/session"
"github.com/CarterPerez-dev/hive/pkg/types"
)
type SMBService struct {
cfg *config.Config
bus *event.Bus
logger zerolog.Logger
tracker *session.Tracker
limiter *ratelimit.IPLimiter
}
func New(
cfg *config.Config,
bus *event.Bus,
logger *zerolog.Logger,
tracker *session.Tracker,
limiter *ratelimit.IPLimiter,
) *SMBService {
return &SMBService{
cfg: cfg,
bus: bus,
logger: logger.With().Str("service", "smb").Logger(),
tracker: tracker,
limiter: limiter,
}
}
func (s *SMBService) Name() string { return "smb" }
func (s *SMBService) Start(
ctx context.Context,
) error {
addr := s.cfg.Addr(s.cfg.SMB.Port)
listener, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf(
"smb listen %s: %w", addr, err,
)
}
s.logger.Info().
Str("addr", addr).
Msg("smb honeypot listening")
go func() {
<-ctx.Done()
_ = listener.Close()
}()
for ctx.Err() == nil {
conn, err := listener.Accept()
if err != nil {
s.logger.Debug().
Err(err).Msg("accept failed")
continue
}
go s.handleConnection(conn)
}
return nil
}
func (s *SMBService) handleConnection(
conn net.Conn,
) {
defer func() { _ = conn.Close() }()
srcIP, srcPort := types.RemoteAddr(conn)
if !s.limiter.Allow(srcIP) {
return
}
sess := s.tracker.Start(
s.cfg.Sensor.ID, types.ServiceSMB,
srcIP, srcPort, s.cfg.SMB.Port,
)
defer s.tracker.End(sess.ID)
s.publishConnect(sess, srcIP, srcPort)
_ = conn.SetReadDeadline(
time.Now().Add(10 * time.Second),
)
data, err := readNBFrame(conn)
if err != nil {
return
}
version := detectVersion(data)
if version == 0 {
return
}
dialects := extractDialects(data)
s.publishScan(sess, srcIP, version, dialects)
resp := buildNegotiateResponse(version)
_ = writeNBFrame(conn, resp)
}
func (s *SMBService) publishConnect(
sess *types.Session,
srcIP string,
srcPort int,
) {
s.bus.Publish(config.TopicConnect, &types.Event{
ID: uuid.Must(uuid.NewV7()).String(),
SessionID: sess.ID,
SensorID: s.cfg.Sensor.ID,
Timestamp: time.Now().UTC(),
ServiceType: types.ServiceSMB,
EventType: types.EventConnect,
SourceIP: srcIP,
SourcePort: srcPort,
DestPort: s.cfg.SMB.Port,
Protocol: types.ProtocolTCP,
SchemaVersion: config.SchemaVersion,
})
}
func (s *SMBService) publishScan(
sess *types.Session,
srcIP string,
version int,
dialects []string,
) {
serviceData, _ := json.Marshal(
map[string]interface{}{
"smb_version": version,
"dialects": dialects,
},
)
s.bus.Publish(config.TopicScan, &types.Event{
ID: uuid.Must(uuid.NewV7()).String(),
SessionID: sess.ID,
SensorID: s.cfg.Sensor.ID,
Timestamp: time.Now().UTC(),
ServiceType: types.ServiceSMB,
EventType: types.EventScan,
SourceIP: srcIP,
Protocol: types.ProtocolTCP,
SchemaVersion: config.SchemaVersion,
Tags: []string{
"smb-negotiate", "mitre:T1595",
},
ServiceData: serviceData,
})
}