From 1578685ed81a2f2bc39d9563fa1e1b36eedc3e2d Mon Sep 17 00:00:00 2001 From: Lukas Rist Date: Sat, 24 May 2025 16:35:23 +0200 Subject: [PATCH 1/2] embed default config --- .gitignore | 1 + app/server.go | 2 +- connection/connection.go | 18 +++++++++++++- connection/connection_test.go | 9 ++++--- glutton.go | 26 ++++++++----------- protocols/helpers/helpers.go | 6 ++--- protocols/tcp/tcp.go | 2 +- protocols/tcp/telnet.go | 47 +++++++++++++++-------------------- protocols/udp/udp.go | 2 +- 9 files changed, 59 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index 85381ee..16257c2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ _testmain.go # Artifacts glutton.log* payloads/ +samples/ # PoCs poc/ diff --git a/app/server.go b/app/server.go index b0ee91f..e059967 100644 --- a/app/server.go +++ b/app/server.go @@ -35,7 +35,7 @@ func main() { fmt.Printf("%s %s\n\n", VERSION, BUILDDATE) pflag.StringP("interface", "i", "eth0", "Bind to this interface") - pflag.IntP("ssh", "s", 0, "Override SSH port") + pflag.IntP("ssh", "s", 22, "Override SSH port") pflag.StringP("logpath", "l", "/dev/null", "Log file path") pflag.StringP("confpath", "c", "config/", "Configuration file path") pflag.BoolP("debug", "d", false, "Enable debug mode") diff --git a/connection/connection.go b/connection/connection.go index a643619..6e14760 100644 --- a/connection/connection.go +++ b/connection/connection.go @@ -1,6 +1,7 @@ package connection import ( + "context" "errors" "fmt" "net" @@ -55,10 +56,25 @@ type ConnTable struct { mtx sync.RWMutex } -func New() *ConnTable { +func New(ctx context.Context) *ConnTable { ct := &ConnTable{ table: make(map[CKey]Metadata, 1024), } + // every 5 minutes using a ticker, flush the table + + go func() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + ct.FlushOlderThan(5 * time.Minute) + } + } + }() + return ct } diff --git a/connection/connection_test.go b/connection/connection_test.go index 32327a8..4055858 100644 --- a/connection/connection_test.go +++ b/connection/connection_test.go @@ -1,6 +1,7 @@ package connection import ( + "context" "net" "testing" "time" @@ -32,12 +33,12 @@ func TestNewConnKeyFromNetConn(t *testing.T) { } func TestNewConnTable(t *testing.T) { - table := New() + table := New(context.Background()) require.NotNil(t, table) } func TestRegister(t *testing.T) { - table := New() + table := New(context.Background()) targetPort := 4321 m1, err := table.Register("127.0.0.1", "1234", uint16(targetPort), &rules.Rule{}) require.NoError(t, err) @@ -57,7 +58,7 @@ func TestRegisterConn(t *testing.T) { require.NoError(t, err) require.NotNil(t, conn) defer conn.Close() - table := New() + table := New(context.Background()) md, err := table.RegisterConn(conn, &rules.Rule{Target: "tcp"}) require.NoError(t, err) require.NotNil(t, md) @@ -67,7 +68,7 @@ func TestRegisterConn(t *testing.T) { } func TestFlushOlderThan(t *testing.T) { - table := New() + table := New(context.Background()) targetPort := 4321 md, err := table.Register("127.0.0.1", "1234", uint16(targetPort), &rules.Rule{}) require.NoError(t, err) diff --git a/glutton.go b/glutton.go index e56480f..c6ef637 100644 --- a/glutton.go +++ b/glutton.go @@ -43,25 +43,19 @@ type Glutton struct { //go:embed config/rules.yaml var defaultRules []byte +//go:embed config/config.yaml +var defaultConfig []byte + func (g *Glutton) initConfig() error { viper.SetConfigName("config") viper.AddConfigPath(viper.GetString("confpath")) if _, err := os.Stat(viper.GetString("confpath")); !os.IsNotExist(err) { - if err := viper.ReadInConfig(); err != nil { - return err - } + g.Logger.Info("Using configuration file", slog.String("path", viper.GetString("confpath")), slog.String("reporter", "glutton")) + return viper.ReadInConfig() } - // If no config is found, use the defaults - viper.SetDefault("ports.tcp", 5000) - viper.SetDefault("ports.udp", 5001) - viper.SetDefault("ports.ssh", 22) - viper.SetDefault("max_tcp_payload", 4096) - viper.SetDefault("conn_timeout", 45) - viper.SetDefault("rules_path", "rules/rules.yaml") - viper.SetDefault("interface", "eth0") // Default interface name - - g.Logger.Debug("configuration set successfully", slog.String("reporter", "glutton")) - return nil + + g.Logger.Info("No configuration file found, using default configuration", slog.String("reporter", "glutton")) + return viper.ReadConfig(bytes.NewBuffer(defaultConfig)) } // New creates a new Glutton instance @@ -69,17 +63,17 @@ func New(ctx context.Context) (*Glutton, error) { g := &Glutton{ tcpProtocolHandlers: make(map[string]protocols.TCPHandlerFunc), udpProtocolHandlers: make(map[string]protocols.UDPHandlerFunc), - connTable: connection.New(), } g.ctx, g.cancel = context.WithCancel(ctx) + g.connTable = connection.New(ctx) if err := g.makeID(); err != nil { return nil, err } g.Logger = producer.NewLogger(g.id.String()) // Loading the configuration - g.Logger.Info("Loading configurations from: config/config.yaml", slog.String("reporter", "glutton")) + g.Logger.Info("Loading configurations", slog.String("reporter", "glutton")) if err := g.initConfig(); err != nil { return nil, err } diff --git a/protocols/helpers/helpers.go b/protocols/helpers/helpers.go index 79e9939..8268af3 100644 --- a/protocols/helpers/helpers.go +++ b/protocols/helpers/helpers.go @@ -15,13 +15,13 @@ func FirstOrEmpty[T any](s []T) T { return t } -func StorePayload(data []byte) (string, error) { +func Store(data []byte, folder string) (string, error) { sum := sha256.Sum256(data) - if err := os.MkdirAll("payloads", os.ModePerm); err != nil { + if err := os.MkdirAll(folder, os.ModePerm); err != nil { return "", err } sha256Hash := hex.EncodeToString(sum[:]) - path := filepath.Join("payloads", sha256Hash) + path := filepath.Join(folder, sha256Hash) if _, err := os.Stat(path); err == nil { // file already exists return "", nil diff --git a/protocols/tcp/tcp.go b/protocols/tcp/tcp.go index 1e22d5a..e57b7ec 100644 --- a/protocols/tcp/tcp.go +++ b/protocols/tcp/tcp.go @@ -67,7 +67,7 @@ func HandleTCP(ctx context.Context, conn net.Conn, md connection.Metadata, logge defer func() { if msgLength > 0 { - payloadHash, err := helpers.StorePayload(data) + payloadHash, err := helpers.Store(data, "payloads") if err != nil { logger.Error("Failed to store payload", slog.String("handler", "tcp"), producer.ErrAttr(err)) } diff --git a/protocols/tcp/telnet.go b/protocols/tcp/telnet.go index 84e8ad8..d58ca4e 100644 --- a/protocols/tcp/telnet.go +++ b/protocols/tcp/telnet.go @@ -4,16 +4,12 @@ import ( "bufio" "context" "crypto/rand" - "crypto/sha256" - "encoding/hex" "errors" "io" "log/slog" "math/big" "net" "net/http" - "os" - "path/filepath" "regexp" "strings" "time" @@ -95,46 +91,38 @@ func (s *telnetServer) read(conn net.Conn) (string, error) { func (s *telnetServer) getSample(cmd string, logger interfaces.Logger) error { url := cmd[strings.Index(cmd, "http"):] url = strings.Split(url, " ")[0] + url = strings.TrimSpace(url) logger.Debug("Fetching sample", slog.String("url", url), slog.String("handler", "telnet")) resp, err := s.client.Get(url) if err != nil { return err } if resp.StatusCode != 200 { - return errors.New("getSample read http: error: Non 200 status code on getSample") + return errors.New("failed to fetch sample: " + resp.Status) } defer resp.Body.Close() if resp.ContentLength <= 0 { - return errors.New("getSample read http: error: Empty response body") + return errors.New("content length is 0") } - bodyBuffer, err := io.ReadAll(resp.Body) + + data, err := io.ReadAll(resp.Body) if err != nil { return err } - sum := sha256.Sum256(bodyBuffer) - // Ignoring errors for if the folder already exists - if err = os.MkdirAll("samples", os.ModePerm); err != nil { - return err - } - sha256Hash := hex.EncodeToString(sum[:]) - path := filepath.Join("samples", sha256Hash) - if _, err = os.Stat(path); err == nil { - logger.Debug("getSample already known", slog.String("sha", sha256Hash), slog.String("handler", "telnet")) - return nil - } - out, err := os.Create(path) - if err != nil { - return err + + if len(data) == 0 { + return errors.New("empty response body") } - defer out.Close() - _, err = out.Write(bodyBuffer) + + sha256Hash, err := helpers.Store(data, "samples") if err != nil { return err } + logger.Info( - "new sample fetched from telnet", + "New sample fetched", slog.String("handler", "telnet"), - slog.String("sha256", sha256Hash), + slog.String("sample_hash", sha256Hash), slog.String("source", url), ) return nil @@ -197,7 +185,12 @@ func HandleTelnet(ctx context.Context, conn net.Conn, md connection.Metadata, lo } for _, cmd := range strings.Split(msg, ";") { if strings.Contains(strings.Trim(cmd, " "), "wget http") { - go s.getSample(strings.Trim(cmd, " "), logger) + go func() { + err := s.getSample(strings.Trim(cmd, " "), logger) + if err != nil { + logger.Error("Failed to get sample", slog.String("handler", "telnet"), producer.ErrAttr(err)) + } + }() } if strings.TrimRight(cmd, "") == " rm /dev/.t" { continue @@ -226,7 +219,7 @@ func HandleTelnet(ctx context.Context, conn net.Conn, md connection.Metadata, lo } } else { // /bin/busybox YDKBI - re := regexp.MustCompile(`\/bin\/busybox (?P[A-Z]+)`) + re := regexp.MustCompile(`\/bin\/busybox (?P[A-Za-z]+)`) match := re.FindStringSubmatch(cmd) if len(match) > 1 { if err := s.write(conn, match[1]+": applet not found\r\n"); err != nil { diff --git a/protocols/udp/udp.go b/protocols/udp/udp.go index f57cb31..0805a19 100644 --- a/protocols/udp/udp.go +++ b/protocols/udp/udp.go @@ -14,7 +14,7 @@ import ( func HandleUDP(ctx context.Context, srcAddr, dstAddr *net.UDPAddr, data []byte, md connection.Metadata, log interfaces.Logger, h interfaces.Honeypot) error { log.Info(fmt.Sprintf("UDP payload:\n%s", hex.Dump(data[:len(data)%1024]))) - if _, err := helpers.StorePayload(data[:len(data)%1024]); err != nil { + if _, err := helpers.Store(data[:len(data)%1024], "payloads"); err != nil { log.Error("failed to store UDP payload", producer.ErrAttr(err)) } if err := h.ProduceUDP("udp", srcAddr, dstAddr, md, data[:len(data)%1024], nil); err != nil { From 9c9d8bdb39b6b6a1c7d6adabffe3529c5f6b2cb1 Mon Sep 17 00:00:00 2001 From: Lukas Rist Date: Sat, 24 May 2025 16:41:12 +0200 Subject: [PATCH 2/2] bedder udp payload truncating --- protocols/udp/udp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/udp/udp.go b/protocols/udp/udp.go index 0805a19..1002ab0 100644 --- a/protocols/udp/udp.go +++ b/protocols/udp/udp.go @@ -13,11 +13,11 @@ import ( ) func HandleUDP(ctx context.Context, srcAddr, dstAddr *net.UDPAddr, data []byte, md connection.Metadata, log interfaces.Logger, h interfaces.Honeypot) error { - log.Info(fmt.Sprintf("UDP payload:\n%s", hex.Dump(data[:len(data)%1024]))) - if _, err := helpers.Store(data[:len(data)%1024], "payloads"); err != nil { + log.Info(fmt.Sprintf("UDP payload:\n%s", hex.Dump(data[:min(len(data), 1024)]))) + if _, err := helpers.Store(data[:min(len(data), 1024)], "payloads"); err != nil { log.Error("failed to store UDP payload", producer.ErrAttr(err)) } - if err := h.ProduceUDP("udp", srcAddr, dstAddr, md, data[:len(data)%1024], nil); err != nil { + if err := h.ProduceUDP("udp", srcAddr, dstAddr, md, data[:min(len(data), 1024)], nil); err != nil { log.Error("failed to produce UDP payload", producer.ErrAttr(err)) } return nil