Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ producers:

conn_timeout: 45
max_tcp_payload: 4096

capture_traffic:
enabled: false
3 changes: 3 additions & 0 deletions config/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ rules:
- match: tcp dst port 27017
type: conn_handler
target: mongodb
- match: tcp dst port 9889
type: passthrough
Comment thread
namay26 marked this conversation as resolved.
Outdated
target: 127.0.0.1:9889 # Can use hostip:port for the required destination.
- match: tcp
type: conn_handler
target: tcp
Expand Down
23 changes: 17 additions & 6 deletions glutton.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,23 @@ func (g *Glutton) tcpListen() {
g.Logger.Error("Failed to set connection timeout", producer.ErrAttr(err))
}

if hfunc, ok := g.tcpProtocolHandlers[rule.Target]; ok {
go func() {
if err := hfunc(g.ctx, conn, md); err != nil {
g.Logger.Error("Failed to handle TCP connection", producer.ErrAttr(err), slog.String("handler", rule.Target))
}
}()
// If hostip:port is used as target, pass on to the passthrough handler.
if host, port, err := net.SplitHostPort(rule.Target); err == nil && host != "" && port != "" {
if hfunc, ok := g.tcpProtocolHandlers["passthrough"]; ok {
Comment thread
namay26 marked this conversation as resolved.
Outdated
go func() {
if err := hfunc(g.ctx, conn, md); err != nil {
g.Logger.Error("Failed to handle TCP passthrough", producer.ErrAttr(err), slog.String("handler", "Passthrough"))
}
}()
}
Comment thread
glaslos marked this conversation as resolved.
Outdated
} else {
if hfunc, ok := g.tcpProtocolHandlers[rule.Target]; ok {
go func() {
if err := hfunc(g.ctx, conn, md); err != nil {
g.Logger.Error("Failed to handle TCP connection", producer.ErrAttr(err), slog.String("handler", rule.Target))
}
}()
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions protocols/protocols.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/mushorg/glutton/protocols/interfaces"
"github.com/mushorg/glutton/protocols/tcp"
"github.com/mushorg/glutton/protocols/udp"
"github.com/spf13/viper"
Comment thread
namay26 marked this conversation as resolved.
Outdated
)

type TCPHandlerFunc func(ctx context.Context, conn net.Conn, md connection.Metadata) error
Expand Down Expand Up @@ -72,6 +73,14 @@ func MapTCPProtocolHandlers(log interfaces.Logger, h interfaces.Honeypot) map[st
protocolHandlers["mongodb"] = func(ctx context.Context, conn net.Conn, md connection.Metadata) error {
return tcp.HandleMongoDB(ctx, conn, md, log, h)
}
var capture bool
if viper.GetBool("capture_traffic.enabled") {
log.Info("Capturing traffic enabled.")
capture = true
}
Comment thread
namay26 marked this conversation as resolved.
Outdated
protocolHandlers["passthrough"] = func(ctx context.Context, conn net.Conn, md connection.Metadata) error {
return tcp.HandlePassThrough(ctx, conn, md, log, h, capture)
}
protocolHandlers["tcp"] = func(ctx context.Context, conn net.Conn, md connection.Metadata) error {
snip, bufConn, err := Peek(conn, 4)
if err != nil {
Expand Down
141 changes: 141 additions & 0 deletions protocols/tcp/passthrough.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package tcp

import (
"context"
"crypto/sha256"
"fmt"
"io"
"log/slog"
"net"

"github.com/mushorg/glutton/connection"
"github.com/mushorg/glutton/producer"
"github.com/mushorg/glutton/protocols/interfaces"
)

type parsedPassThrough struct {
Direction string `json:"direction,omitempty"`
Payload []byte `json:"payload,omitempty"`
PayloadHash string `json:"payload_hash,omitempty"` // Used for easier identification, can remove
}

type passThroughServer struct {
events []parsedPassThrough
conn net.Conn
target string
source string
}

func (srv *passThroughServer) recordEvent(dir string, buf []byte, capture bool) {
if !capture {
return
}
hash := sha256.Sum256(buf)

payload := append([]byte(nil), buf...) // defensive copy

srv.events = append(srv.events, parsedPassThrough{
Direction: dir,
Payload: payload,
PayloadHash: fmt.Sprintf("%x", hash[:]),
})
}

Comment thread
glaslos marked this conversation as resolved.
// Dial to the source ip, acting as a proxy between the client and real source by piping the data back and forth w/o interfering w it.
func HandlePassThrough(ctx context.Context, conn net.Conn, md connection.Metadata, logger interfaces.Logger, h interfaces.Honeypot, capture bool) error {
var err error

srcAddr := conn.RemoteAddr().String()
destAddr := md.Rule.Target
Comment thread
namay26 marked this conversation as resolved.

server := &passThroughServer{
events: []parsedPassThrough{},
conn: conn,
target: destAddr,
source: srcAddr,
}

defer func() {
var events []parsedPassThrough
if capture {
events = server.events
}
if err := h.ProduceTCP("passthrough", conn, md, nil, events); err != nil {
logger.Error("failed to produce passthrough message", producer.ErrAttr(err))
}
if err := conn.Close(); err != nil {
logger.Error("failed to close incoming connection", slog.String("handler", "passthrough"), producer.ErrAttr(err))
}
}()

if destAddr == "" {
logger.Error("no target defined", slog.String("handler", "passthrough"))
return nil
}

targetConn, err := net.Dial("tcp", destAddr)
Comment thread
namay26 marked this conversation as resolved.
Outdated
if err != nil {
logger.Error("failed to connect to the target", slog.String("handler", "passthrough"), slog.String("target", string(destAddr)), producer.ErrAttr(err))
Comment thread
namay26 marked this conversation as resolved.
Outdated
return nil
}
defer targetConn.Close()

logger.Info("starting passthrough", slog.String("source", srcAddr), slog.String("target", string(destAddr)), slog.String("handler", "passthrough"))

errChan := make(chan error, 2)

// Source to target
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if err != nil {
errChan <- err
return
}
if n > 0 {
logger.Info("source to target", slog.String("payload", string(buf[:n])))
server.recordEvent("source->target", buf[:n], capture)
if _, err := targetConn.Write(buf[:n]); err != nil {
errChan <- err
return
}
}
}
}()

go func() {
buf := make([]byte, 4096)
for {
n, err := targetConn.Read(buf)
if err != nil {
errChan <- err
return
}
if n > 0 {
logger.Info("target to source", slog.String("payload", string(buf[:n])))
server.recordEvent("target->source", buf[:n], capture)
if _, err := conn.Write(buf[:n]); err != nil {
errChan <- err
return
}
}

}
}()

// When either of the error is returned or no more data is left to be sent, the go routines exit.
select {
case err := <-errChan:
if err != nil && err != io.EOF {
logger.Error("transfer error", producer.ErrAttr(err))
return err
}
case <-ctx.Done():
logger.Info("context cancelled")
return ctx.Err()
}

logger.Info("Passthrough completed successfully")
return nil
}
9 changes: 6 additions & 3 deletions rules/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type RuleType int
const (
UserConnHandler RuleType = iota
Drop
Passthrough
)

type Config struct {
Expand All @@ -32,7 +33,7 @@ type Rule struct {
Name string `yaml:"name,omitempty"`

isInit bool
ruleType RuleType
RuleType RuleType
index int
matcher *pcap.BPF
}
Expand All @@ -59,9 +60,11 @@ func (rule *Rule) init(idx int) error {

switch rule.Type {
case "conn_handler":
rule.ruleType = UserConnHandler
rule.RuleType = UserConnHandler
case "passthrough":
rule.RuleType = Passthrough
case "drop":
rule.ruleType = Drop
rule.RuleType = Drop
default:
return fmt.Errorf("unknown rule type: %s", rule.Type)
}
Expand Down