Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: FTP Honeypot Implementation #8

Merged
merged 10 commits into from
Jul 27, 2024
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ A HTTP tarpit written in Go designed to maximize bot misery through very slowly

## Features
- **Realistic output**: Go pot will respond to requests with an infinite stream of realistic looking, parseable structured data full of fake secrets. `xml`, `json`, `yaml`, `hcl`, `toml`, `csv`, `ini`, and `sql` are all supported.
- **Multiple protocols**: Both `http` and `ftp` are supported out of the box. Each with a tailored implementation. *More protocols are planned.*
- **Intelligent stalling**: Go pot will attempt to work out how long a bot is willing to wait for a response and stall for exactly that long. This is done gradually making requests slower and slower until a timeout is reached. (Or the bot hangs forever!)
- **Small Profile**: Go pot can run on extremely low resource machines and is designed to be as lightweight as possible.
- **Clustering Support**: Go pot can be run in a clustered mode where multiple instances can share information about how long bots are willing to wait for a response. Also in cluster mode nodes can be configured to restart / reallocate IP addresses to avoid being blacklisted by connecting clients.
- **Customizable**: Go pot can be customized to respond with different different response times support for more protocols is planned.
- **Customizable**: Go pot can be customized to respond with different different response times.

## Installation
Go pot is distributed as a standalone go binary or docker image. You can download the latest release from the [releases page](https://github.com/ryanolee/go-pot/releases). Docker images are available on the [ghcr.io registry](https://github.com/ryanolee/go-pot/pkgs/container/go-pot).
Expand Down Expand Up @@ -50,7 +51,7 @@ Go pot was originally inspired by the [Reverse slow loris](https://github.com/ni
The go pot logo created by `@_iroshi` and is licensed under the [CC0](https://creativecommons.org/publicdomain/zero/1.0/) license.

## What the future holds 🔮
- **More protocols**: Support for more protocols is planned. Including `ssh`, `sql`, `ftp`, `smtp` and more. Anything that can be stalled will be stalled and must be stalled!
- **More protocols**: Support for more protocols is planned. Including `ssh`, `sql`, `smtp` and more. Anything that can be stalled will be stalled and must be stalled!
- **Tests**: There are *no* unit tests. The was originally built as a proof of concept for a talk and has been refactored several times since. It is still in need of firmer testing.

(Originally the subject of a talk for [Birmingham go](https://www.meetup.com/golang-birmingham/))
37 changes: 37 additions & 0 deletions cmd/ftp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cmd

import (
"fmt"
"os"

"github.com/ryanolee/ryan-pot/config"
"github.com/ryanolee/ryan-pot/di"
"github.com/spf13/cobra"
)

var ftpCommand = &cobra.Command{
Use: "ftp",
Short: "Starts the FTP server",
Run: func(cmd *cobra.Command, args []string) {
conf, err := config.NewConfig(cmd, config.GetFtpFlags())

if err != nil {
fmt.Println(err)
os.Exit(1)
}

// Make sure only the FTP server is enabled
conf.FtpServer.Enabled = true
conf.Server.Disable = true

di := di.CreateContainer(conf)
di.Run()

},
}

func init() {
config.BindConfigFlags(ftpCommand, config.GetFtpFlags())
config.BindConfigFileFlags(ftpCommand)
rootCmd.AddCommand(ftpCommand)
}
36 changes: 36 additions & 0 deletions cmd/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cmd

import (
"fmt"
"os"

"github.com/ryanolee/ryan-pot/config"
"github.com/ryanolee/ryan-pot/di"
"github.com/spf13/cobra"
)

var httpCommand = &cobra.Command{
Use: "http",
Short: "Starts the HTTP server",
Run: func(cmd *cobra.Command, args []string) {
conf, err := config.NewConfig(cmd, config.GetHttpFlags())

if err != nil {
fmt.Println(err)
os.Exit(1)
}

// Make sure only the HTTP server is enabled
conf.FtpServer.Enabled = false
conf.Server.Disable = false

di := di.CreateContainer(conf)
di.Run()
},
}

func init() {
config.BindConfigFlags(httpCommand, config.GetHttpFlags())
config.BindConfigFileFlags(httpCommand)
rootCmd.AddCommand(httpCommand)
}
13 changes: 4 additions & 9 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,22 @@ import (

var startCmd = &cobra.Command{
Use: "start",
Short: "Resync secrets from gitleaks",
Short: "Allows for all pots to be started from a single command",
Run: func(cmd *cobra.Command, args []string) {
_, err := cmd.Flags().GetInt("port")
conf, err := config.NewConfig(cmd, config.GetStartFlags())
if err != nil {
fmt.Println(err)
os.Exit(1)
}

conf, err := config.NewConfig(cmd)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

di := di.CreateContainer(conf)
di.Run()

},
}

func init() {
config.BindConfigFlags(startCmd)
config.BindConfigFlags(startCmd, config.GetStartFlags())
config.BindConfigFileFlags(startCmd)
rootCmd.AddCommand(startCmd)
}
64 changes: 58 additions & 6 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"strings"

validate "github.com/go-playground/validator/v10"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/structs"
"github.com/knadh/koanf/v2"
Expand All @@ -15,16 +14,20 @@ type (
// This struct covers the entire application configuration
Config struct {
Server serverConfig `koanf:"server"`
FtpServer ftpServerConfig `koanf:"ftp_server"`
Logging loggingConfig `koanf:"logging"`
Cluster clusterConfig `koanf:"cluster"`
TimeoutWatcher timeoutWatcherConfig `koanf:"timeout_watcher"`
Recast recastConfig `koanf:"recast"`
Telemetry telemetryConfig `koanf:"telemetry"`
Staller httpStallerConfig `koanf:"staller"`
Staller stallerConfig `koanf:"staller"`
}

// Server specific configuration
serverConfig struct {
// If the http server should be disabled
Disable bool `koanf:"disable"`

// Server port to listen on
Port int `koanf:"port" validate:"required,min=1,max=65535"`

Expand All @@ -42,6 +45,51 @@ type (
TrustedProxies []string `koanf:"trusted_proxies" validate:"omitempty,dive,ipv4|ipv6|cidr|cidrv6"`
}

// Config relating to FTP Server File Transfer
ftpTransferConfig struct {
// The size of each chunk that makes up each file to be transferred (in bytes)
ChunkSize int `koanf:"chunk_size" validate:"omitempty,min=1"`

// How often a chunk of data (In milliseconds)
ChunkSendRate int `koanf:"chunk_rate" validate:"omitempty,min=1"`

// The size of each file to be advertised to connected clients (in bytes)
FileSize int `koanf:"file_size" validate:"omitempty,min=1"`
}

ftpThrottleConfig struct {
// The maximum number of pending operations to throttle
MaxPendingOperations int `koanf:"max_pending_operations" validate:"required,min=1"`

// The time to wait before releasing a pending operation
WaitTime int `koanf:"wait_time" validate:"required,min=1"`
}

// Settings relating to the FTP server
ftpServerConfig struct {
// If the FTP server should be enabled
Enabled bool `koanf:"enabled"`

// The port to listen on N.b this is the control port
// port 20 is used for data transfer by default in active mode.
Port int `koanf:"port" validate:"required,min=1,max=65535"`

// Host to listen on
Host string `koanf:"host" validate:"required"`

// Lower bound of ports exposed for passive mode default 50000-50100
PassivePortRange string `koanf:"passive_port_range" validate:"omitempty,port_range"`

// The common name for the self signed certificate
CertCommonName string `koanf:"cert_common_name" validate:"omitempty"`

// Commands relating to throttling ongoing connections to the ftp server
Throttle ftpThrottleConfig `koanf:"throttle"`

//
Transfer ftpTransferConfig `koanf:"transfer"`
}

// Cluster specific configuration
clusterConfig struct {
// If cluster mode is enabled (Nodes will become aware of each other)
Expand Down Expand Up @@ -204,16 +252,20 @@ type (
TimeWastedRatio float64 `koanf:"time_wasted_ratio" validate:"omitempty,min=0,max=1"`
}

httpStallerConfig struct {
stallerConfig struct {
// The maximum number of connections that can be made to the pot at any given time
MaximumConnections int `koanf:"maximum_connections" validate:"required,min=1"`

// The maximum number of stallers allowed per group (Normally representing a single connected client)
// Any connections that exceed this limit will be rejected
GroupLimit int `koanf:"group_limit" validate:"required,min=1"`

// The transfer rate for the staller (bytes per second)
BytesPerSecond int `koanf:"bytes_per_second" validate:"omitempty,min=1"`
}
)

func NewConfig(cmd *cobra.Command) (*Config, error) {
func NewConfig(cmd *cobra.Command, flagsUsed flagMap) (*Config, error) {

k := koanf.New(".")

Expand All @@ -227,7 +279,7 @@ func NewConfig(cmd *cobra.Command) (*Config, error) {
}

// Override the default configuration with values given by the flags
k = writeFlagValues(k, cmd)
k = writeFlagValues(k, cmd, flagsUsed)

// Write environment variables to the configuration
err := k.Load(env.ProviderWithValue("GOPOT__", ".", func(s string, v string) (string, interface{}) {
Expand All @@ -253,7 +305,7 @@ func NewConfig(cmd *cobra.Command) (*Config, error) {
return nil, err
}

validator := validate.New()
validator := newConfigValidator()
if err := validator.Struct(cfg); err != nil {
return nil, err
}
Expand Down
20 changes: 19 additions & 1 deletion config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@ import "go.uber.org/zap/zapcore"
// Default configuration values for the application
var defaultConfig = Config{
Server: serverConfig{
Disable: false,
Port: 8080,
Host: "127.0.0.1",
Network: "tcp4",
ProxyHeader: "X-Forwarded-For",
TrustedProxies: []string{},
},
FtpServer: ftpServerConfig{
Enabled: false,
Port: 2121,
Host: "0.0.0.0",
PassivePortRange: "50000-50100",
CertCommonName: "unknown",
Throttle: ftpThrottleConfig{
WaitTime: 1000,
MaxPendingOperations: 10,
},
Transfer: ftpTransferConfig{
ChunkSize: 1,
ChunkSendRate: 1000,
FileSize: 1024 * 1024 * 20, // 20Mb
},
},
Logging: loggingConfig{
Level: zapcore.InfoLevel.String(),
},
Expand Down Expand Up @@ -76,8 +93,9 @@ var defaultConfig = Config{
MaximumRecastIntervalMin: 120,
TimeWastedRatio: 0.05,
},
Staller: httpStallerConfig{
Staller: stallerConfig{
MaximumConnections: 200,
GroupLimit: 1,
BytesPerSecond: 8,
},
}
Loading