Skip to content
Open
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ COPY --from=build-env /opt/glutton/bin/server /opt/glutton/bin/server
COPY --from=build-env /opt/glutton/config /opt/glutton/config
COPY --from=build-env /opt/glutton/rules /opt/glutton/rules

CMD ["./bin/server", "-i", "eth0", "-l", "/var/log/glutton.log", "-d", "true"]
CMD ["./bin/server", "-i", "eth0", "-l", "/var/log/glutton.log", "--debug"]
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ run: build
sudo bin/server -i eth0

docker:
docker build -t glutton .
docker run --rm --cap-add=NET_ADMIN -it glutton
docker build --progress=plain -t glutton .
docker run --rm --cap-add=NET_ADMIN -it --name glutton glutton

test:
go test -v ./...
50 changes: 26 additions & 24 deletions glutton.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,32 +265,34 @@ func (g *Glutton) makeID() error {
if err := os.MkdirAll(viper.GetString("var-dir"), 0744); err != nil {
return fmt.Errorf("failed to create var-dir: %w", err)
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
g.id = uuid.New()
data, err := g.id.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal UUID: %w", err)
}
if err := os.WriteFile(filePath, data, 0744); err != nil {
return fmt.Errorf("failed to create new PID file: %w", err)
}
} else {
if err != nil {
return fmt.Errorf("failed to access PID file: %w", err)
}
f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open PID file: %w", err)
}
buff, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("failed to read PID file: %w", err)
}
g.id, err = uuid.FromBytes(buff)
if err != nil {
return fmt.Errorf("failed to create UUID from PID filed content: %w", err)
if _, err := os.Stat(filePath); err != nil {
if os.IsNotExist(err) {
g.id = uuid.New()
data, err := g.id.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal UUID: %w", err)
}
if err := os.WriteFile(filePath, data, 0744); err != nil {
return fmt.Errorf("failed to create new PID file: %w", err)
}
return nil
}
return fmt.Errorf("failed to access PID file: %w", err)
}

f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open PID file: %w", err)
}
buff, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("failed to read PID file: %w", err)
}
g.id, err = uuid.FromBytes(buff)
if err != nil {
return fmt.Errorf("failed to create UUID from PID filed content: %w", err)
}

return nil
}

Expand Down
10 changes: 9 additions & 1 deletion producer/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ func NewLogger(id string) *slog.Logger {
Compress: true, // disabled by default
},
}
handler := slog.NewJSONHandler(writer, &slog.HandlerOptions{})
handlerOptions := &slog.HandlerOptions{}
debug := viper.GetBool("debug")
switch debug {
case true:
handlerOptions.Level = slog.Leveler(slog.LevelDebug)
default:
handlerOptions.Level = slog.Leveler(slog.LevelInfo)
}
handler := slog.NewJSONHandler(writer, handlerOptions)
return slog.New(handler.WithAttrs([]slog.Attr{slog.String("sensorID", id)}))
}
15 changes: 9 additions & 6 deletions protocols/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/sha256"
"encoding/hex"
"os"
"path"
"path/filepath"
)

Expand All @@ -15,18 +16,20 @@ func FirstOrEmpty[T any](s []T) T {
return t
}

func StorePayload(data []byte) (string, error) {
sum := sha256.Sum256(data)
if err := os.MkdirAll("payloads", os.ModePerm); err != nil {
func StorePayload(data []byte, paths ...string) (string, error) {
paths = append([]string{"payloads"}, paths...)
path := path.Join(paths...)
if err := os.MkdirAll(path, os.ModePerm); err != nil {
return "", err
}
sum := sha256.Sum256(data)
sha256Hash := hex.EncodeToString(sum[:])
path := filepath.Join("payloads", sha256Hash)
if _, err := os.Stat(path); err == nil {
filePath := filepath.Join(path, sha256Hash)
if _, err := os.Stat(filePath); err == nil {
// file already exists
return "", nil
}
out, err := os.Create(path)
out, err := os.Create(filePath)
if err != nil {
return "", err
}
Expand Down
2 changes: 1 addition & 1 deletion protocols/tcp/bittorrent.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func HandleBittorrent(ctx context.Context, conn net.Conn, md connection.Metadata
}
}()

buffer := make([]byte, 1024)
buffer := make([]byte, maxBufferSize)
for {
if err := h.UpdateConnectionTimeout(ctx, conn); err != nil {
logger.Debug("Failed to set connection timeout", producer.ErrAttr(err), slog.String("handler", "bittorrent"))
Expand Down
122 changes: 110 additions & 12 deletions protocols/tcp/ftp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,47 @@ import (
"net"
"strconv"
"strings"
"time"

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

const (
// 1xx
StatusFileStatusOK = 150 // RFC 959, 4.2.1
// 2xx
StatusOK = 200 // RFC 959, 4.2.1
StatusServiceReady = 220 // RFC 959, 4.2.1
StatusClosingControlConn = 221 // RFC 959, 4.2.1
StatusClosingDataConn = 226 // RFC 959, 4.2.1
StatusUserLoggedIn = 230 // RFC 959, 4.2.1
// 3xx
StatusUserOK = 331 // RFC 959, 4.2.1
// 4xx
StatusCannotOpenDataConnection = 425 // RFC 959, 4.2.1
// 5xx
StatusActionNotTaken = 550 // RFC 959, 4.2.1
)

type parsedFTP struct {
Direction string `json:"direction,omitempty"`
Payload []byte `json:"payload,omitempty"`
Direction string `json:"direction,omitempty"`
Payload []byte `json:"payload,omitempty"`
PayloadHash string `json:"payload_hash,omitempty"`
}

type ftpServer struct {
events []parsedFTP
conn net.Conn
}

type ftpResponse struct {
code int
msg string
}

func (s *ftpServer) read(_ interfaces.Logger, _ interfaces.Honeypot) (string, error) {
msg, err := bufio.NewReader(s.conn).ReadString('\n')
if err != nil {
Expand All @@ -38,7 +62,8 @@ func (s *ftpServer) read(_ interfaces.Logger, _ interfaces.Honeypot) (string, er
return msg, nil
}

func (s *ftpServer) write(msg string) error {
func (s *ftpServer) write(resp ftpResponse) error {
msg := fmt.Sprintf("%d %s\r\n", resp.code, resp.msg)
_, err := s.conn.Write([]byte(msg))
if err != nil {
return err
Expand All @@ -51,6 +76,38 @@ func (s *ftpServer) write(msg string) error {

}

func (s *ftpServer) ftpSTOR(ip string, port int) (ftpResponse, string) {
dialer := &net.Dialer{Timeout: 2 * time.Second}
conn, err := dialer.Dial("tcp", fmt.Sprintf("%s:%d", ip, port))
if err != nil {
return ftpResponse{StatusCannotOpenDataConnection, "Failed to establish connection."}, ""
}
if err := conn.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil {
return ftpResponse{StatusActionNotTaken, "Failed to establish connection."}, ""
}
buffer := make([]byte, maxBufferSize)
n, err := conn.Read(buffer)
if err != nil || n == 0 {
return ftpResponse{StatusActionNotTaken, "Failed to read file."}, ""
}

fileHash, err := helpers.StorePayload(buffer, "ftp")
if err != nil {
return ftpResponse{StatusActionNotTaken, "Failed to store file."}, ""
}

s.events = append(s.events, parsedFTP{
Direction: "read",
PayloadHash: fileHash,
Payload: buffer,
})

if err := conn.Close(); err != nil {
return ftpResponse{StatusActionNotTaken, "Failed to close connection."}, ""
}
return ftpResponse{StatusClosingDataConn, "File received."}, fileHash
}

// HandleFTP takes a net.Conn and does basic FTP communication
func HandleFTP(ctx context.Context, conn net.Conn, md connection.Metadata, logger interfaces.Logger, h interfaces.Honeypot) error {
server := ftpServer{
Expand All @@ -65,21 +122,23 @@ func HandleFTP(ctx context.Context, conn net.Conn, md connection.Metadata, logge
}
}()

host, port, err := net.SplitHostPort(conn.RemoteAddr().String())
host, srcPort, err := net.SplitHostPort(conn.RemoteAddr().String())
if err != nil {
return err
}

if _, err := conn.Write([]byte("220 Welcome!\r\n")); err != nil {
if err := server.write(ftpResponse{StatusServiceReady, "Welcome"}); err != nil {
return err
}
var portParam string
loop:
for {
if err := h.UpdateConnectionTimeout(ctx, conn); err != nil {
logger.Debug("Failed to set connection timeout", slog.String("protocol", "ftp"), producer.ErrAttr(err))
return nil
}
msg, err := server.read(logger, h)
if err != nil || err != io.EOF {
if err != nil && err != io.EOF {
logger.Debug("Failed to read data", slog.String("protocol", "ftp"), producer.ErrAttr(err))
break
}
Expand All @@ -89,22 +148,61 @@ func HandleFTP(ctx context.Context, conn net.Conn, md connection.Metadata, logge
cmd := strings.ToUpper(msg[:4])

logger.Info(
"ftp payload received",
"ftp command received",
slog.String("dest_port", strconv.Itoa(int(md.TargetPort))),
slog.String("src_ip", host),
slog.String("src_port", port),
slog.String("src_port", srcPort),
slog.String("message", fmt.Sprintf("%q", msg)),
slog.String("handler", "ftp"),
)

var resp string
var (
resp ftpResponse
filehash string
)
switch cmd {
case "USER":
resp = "331 OK.\r\n"
resp = ftpResponse{StatusUserOK, "Ok."}
case "PASS":
resp = "230 OK.\r\n"
resp = ftpResponse{StatusUserLoggedIn, "Ok."}
case "STOR":
parts := strings.Split(portParam, ",")
ip := strings.Join(parts[:4], ".")

portByte1, err := strconv.Atoi(parts[4])
if err != nil {
return err
}
portByte2, err := strconv.Atoi(strings.TrimSpace(parts[5]))
if err != nil {
return err
}

port := portByte1<<8 + portByte2
if err := server.write(ftpResponse{StatusFileStatusOK, fmt.Sprintf("Connecting to port %d\r\n", port)}); err != nil {
return err
}
resp, filehash = server.ftpSTOR(ip, port)
if filehash != "" {
logger.Info(
"FTP payload receievd",
slog.String("dest_port", strconv.Itoa(int(md.TargetPort))),
slog.String("src_ip", host),
slog.String("src_port", srcPort),
slog.String("handler", "ftp"),
slog.String("payload_hash", filehash),
)
}
case "QUIT":
if err := server.write(ftpResponse{StatusClosingControlConn, "Goodbye."}); err != nil {
return err
}
break loop
case "PORT":
portParam = msg[5:]
resp = ftpResponse{StatusOK, "PORT command successful."}
default:
resp = "200 OK.\r\n"
resp = ftpResponse{StatusOK, "Ok."}
}
if err := server.write(resp); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion protocols/tcp/memcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

func HandleMemcache(ctx context.Context, conn net.Conn, md connection.Metadata, logger interfaces.Logger, h interfaces.Honeypot) error {
var dataMap = map[string]string{}
buffer := make([]byte, 1024)
buffer := make([]byte, maxBufferSize)
for {
if err := h.UpdateConnectionTimeout(ctx, conn); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion protocols/tcp/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func HandleMQTT(ctx context.Context, conn net.Conn, md connection.Metadata, logg
logger.Error(fmt.Sprintf("[mqtt ] error: %v", err))
}
}()
buffer := make([]byte, 1024)
buffer := make([]byte, maxBufferSize)
for {
if err := h.UpdateConnectionTimeout(ctx, conn); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion protocols/tcp/rdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func HandleRDP(ctx context.Context, conn net.Conn, md connection.Metadata, logge
}
}()

buffer := make([]byte, 1024)
buffer := make([]byte, maxBufferSize)
for {
if err := h.UpdateConnectionTimeout(ctx, conn); err != nil {
logger.Debug("Failed to set connection timeout", slog.String("protocol", "rdp"), producer.ErrAttr(err))
Expand Down
4 changes: 2 additions & 2 deletions protocols/tcp/smb.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func HandleSMB(ctx context.Context, conn net.Conn, md connection.Metadata, logge
}
}()

buffer := make([]byte, 4096)
buffer := make([]byte, maxBufferSize)
for {
if err := h.UpdateConnectionTimeout(ctx, conn); err != nil {
logger.Debug("Failed to set connection timeout", slog.String("protocol", "smb"), producer.ErrAttr(err))
Expand All @@ -64,7 +64,7 @@ func HandleSMB(ctx context.Context, conn net.Conn, md connection.Metadata, logge
logger.Debug("Failed to read data", slog.String("protocol", "smb"), producer.ErrAttr(err))
break
}
if n > 0 && n < 4096 {
if n > 0 && n < maxBufferSize {
logger.Debug("SMB Payload", slog.String("payload", hex.Dump(buffer[0:n])), slog.String("protocol", "smb"))
buffer, err := smb.ValidateData(buffer[0:n])
if err != 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 @@ -61,7 +61,7 @@ func HandleTCP(ctx context.Context, conn net.Conn, md connection.Metadata, logge

msgLength := 0
data := []byte{}
buffer := make([]byte, 1024)
buffer := make([]byte, maxBufferSize)

defer func() {
if msgLength > 0 {
Expand Down