Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ _testmain.go
# Artifacts
glutton.log*
payloads/
samples/

# PoCs
poc/
Expand Down
2 changes: 1 addition & 1 deletion app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
18 changes: 17 additions & 1 deletion connection/connection.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package connection

import (
"context"
"errors"
"fmt"
"net"
Expand Down Expand Up @@ -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
}

Expand Down
9 changes: 5 additions & 4 deletions connection/connection_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package connection

import (
"context"
"net"
"testing"
"time"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
26 changes: 10 additions & 16 deletions glutton.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,43 +43,37 @@ 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
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
}
Expand Down
6 changes: 3 additions & 3 deletions protocols/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion protocols/tcp/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
47 changes: 20 additions & 27 deletions protocols/tcp/telnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<applet>[A-Z]+)`)
re := regexp.MustCompile(`\/bin\/busybox (?P<applet>[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 {
Expand Down
6 changes: 3 additions & 3 deletions protocols/udp/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.StorePayload(data[:len(data)%1024]); 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
Expand Down