Add SMTP Honeypot
This commit is contained in:
parent
ed66cd5492
commit
4912eef00a
15
apiary.toml
15
apiary.toml
@ -63,7 +63,7 @@ RedirectHTTP = true
|
||||
[Ports]
|
||||
# Enable the port listener.
|
||||
# Default: false
|
||||
Enable = true
|
||||
Enable = false
|
||||
|
||||
# Which address to listen on.
|
||||
# Default: "" (listen to all addresses)
|
||||
@ -76,3 +76,16 @@ TCPPorts = ["25"]
|
||||
# Which UDP ports to listen to.
|
||||
# Default: []
|
||||
UDPPorts = ["25"]
|
||||
|
||||
[SMTP]
|
||||
# Enable the port listener.
|
||||
# Default: false
|
||||
Enable = true
|
||||
|
||||
# Which address and port to listen on.
|
||||
# Default: ":25"
|
||||
Addr = ":25"
|
||||
|
||||
# Enable collecting prometheus metrics
|
||||
# Default: false
|
||||
EnableMetrics = true
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.uio.no/torjus/apiary"
|
||||
"github.uio.no/torjus/apiary/config"
|
||||
"github.uio.no/torjus/apiary/honeypot/ports"
|
||||
"github.uio.no/torjus/apiary/honeypot/smtp"
|
||||
"github.uio.no/torjus/apiary/honeypot/ssh"
|
||||
"github.uio.no/torjus/apiary/honeypot/ssh/store"
|
||||
"github.uio.no/torjus/apiary/web"
|
||||
@ -144,6 +145,39 @@ func ActionServe(c *cli.Context) error {
|
||||
}()
|
||||
}
|
||||
|
||||
// Setup smtp honeypot if enabled
|
||||
if cfg.SMTP.Enable {
|
||||
honeypot, err := smtp.NewSMTPHoneypot()
|
||||
if err != nil {
|
||||
loggers.rootLogger.Warnw("Error seting up SMTP honeypot", "error", err)
|
||||
}
|
||||
honeypot.Addr = cfg.SMTP.Addr
|
||||
honeypot.Logger = loggers.smtpLogger
|
||||
if cfg.SMTP.EnableMetrics {
|
||||
honeypot.Store = smtp.NewMetricsStore(&smtp.DiscardStore{})
|
||||
} else {
|
||||
honeypot.Store = &smtp.DiscardStore{}
|
||||
}
|
||||
|
||||
// Start smtp honeypot
|
||||
go func() {
|
||||
loggers.rootLogger.Info("Starting SMTP server")
|
||||
if err := honeypot.ListenAndServe(); err != nil {
|
||||
loggers.rootLogger.Warnw("SMTP server returned error", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for smtp shutdown
|
||||
go func() {
|
||||
<-serversCtx.Done()
|
||||
loggers.rootLogger.Info("SMTP server shutdown started")
|
||||
if err := honeypot.Shutdown(); err != nil {
|
||||
loggers.rootLogger.Errorw("Error shutting down SMTP server", "error", err)
|
||||
}
|
||||
loggers.rootLogger.Info("SMTP server shutdown complete")
|
||||
}()
|
||||
}
|
||||
|
||||
// Handle interrupt
|
||||
go func() {
|
||||
<-interruptChan
|
||||
@ -250,6 +284,7 @@ type loggerCollection struct {
|
||||
webAccessLogger *zap.SugaredLogger
|
||||
webServerLogger *zap.SugaredLogger
|
||||
portsLogger *zap.SugaredLogger
|
||||
smtpLogger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func setupLoggers(cfg config.Config) *loggerCollection {
|
||||
@ -290,6 +325,7 @@ func setupLoggers(cfg config.Config) *loggerCollection {
|
||||
webAccessLogger: rootLogger.Named("ACC").Sugar(),
|
||||
webServerLogger: rootLogger.Named("WEB").Sugar(),
|
||||
portsLogger: rootLogger.Named("PRT").Sugar(),
|
||||
smtpLogger: rootLogger.Named("SMT").Sugar(),
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ type Config struct {
|
||||
Honeypot HoneypotConfig `toml:"Honeypot"`
|
||||
Frontend FrontendConfig `toml:"Frontend"`
|
||||
Ports PortsConfig `toml:"Ports"`
|
||||
SMTP SMTPConfig `toml:"SMTP"`
|
||||
}
|
||||
type StoreConfig struct {
|
||||
Type string `toml:"Type"`
|
||||
@ -54,6 +55,12 @@ type PortsConfig struct {
|
||||
UDPPorts []string `toml:"UDPPorts"`
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Enable bool `toml:"Enable"`
|
||||
Addr string `toml:"Addr"`
|
||||
EnableMetrics bool `toml:"EnableMetrics"`
|
||||
}
|
||||
|
||||
func FromReader(r io.Reader) (Config, error) {
|
||||
var c Config
|
||||
|
||||
@ -63,6 +70,7 @@ func FromReader(r io.Reader) (Config, error) {
|
||||
c.Frontend.ListenAddr = ":8080"
|
||||
c.Frontend.LogLevel = "INFO"
|
||||
c.Frontend.AccessLogEnable = true
|
||||
c.SMTP.Addr = ":25"
|
||||
|
||||
// Read from config
|
||||
decoder := toml.NewDecoder(r)
|
||||
|
2
go.mod
2
go.mod
@ -6,6 +6,8 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac // indirect
|
||||
github.com/emersion/go-smtp v0.15.0
|
||||
github.com/fujiwara/shapeio v1.0.0
|
||||
github.com/gliderlabs/ssh v0.3.3
|
||||
github.com/go-chi/chi/v5 v5.0.4
|
||||
|
5
go.sum
5
go.sum
@ -72,6 +72,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac h1:tn/OQ2PmwQ0XFVgAHfjlLyqMewry25Rz7jWnVoh4Ggs=
|
||||
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
|
||||
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
12
honeypot/smtp/mail.go
Normal file
12
honeypot/smtp/mail.go
Normal file
@ -0,0 +1,12 @@
|
||||
package smtp
|
||||
|
||||
import "time"
|
||||
|
||||
type MailData struct {
|
||||
From string
|
||||
Recipients []string
|
||||
Data string
|
||||
|
||||
RemoteAddr string
|
||||
Timestamp time.Time
|
||||
}
|
30
honeypot/smtp/metrics.go
Normal file
30
honeypot/smtp/metrics.go
Normal file
@ -0,0 +1,30 @@
|
||||
package smtp
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
type MetricsStore struct {
|
||||
backend SMTPStore
|
||||
mailTotal *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func NewMetricsStore(backend SMTPStore) *MetricsStore {
|
||||
store := &MetricsStore{backend: backend}
|
||||
|
||||
store.mailTotal = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "apiary_smtp_received_total",
|
||||
Help: "Total count of received emails.",
|
||||
ConstLabels: prometheus.Labels{"service": "honeypot_smtp"},
|
||||
},
|
||||
[]string{"from"},
|
||||
)
|
||||
prometheus.MustRegister(store.mailTotal)
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
func (s *MetricsStore) AddMailData(data *MailData) error {
|
||||
s.mailTotal.WithLabelValues(data.From).Inc()
|
||||
|
||||
return s.backend.AddMailData(data)
|
||||
}
|
112
honeypot/smtp/server.go
Normal file
112
honeypot/smtp/server.go
Normal file
@ -0,0 +1,112 @@
|
||||
package smtp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
smtplib "github.com/emersion/go-smtp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type backend struct {
|
||||
honeypot *SMTPHoneypot
|
||||
}
|
||||
|
||||
func (b *backend) Login(state *smtplib.ConnectionState, username, password string) (smtplib.Session, error) {
|
||||
sess := &session{
|
||||
honeypot: b.honeypot,
|
||||
loginUsername: username,
|
||||
loginPassword: password,
|
||||
remoteAddr: state.RemoteAddr.String(),
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
func (b *backend) AnonymousLogin(state *smtplib.ConnectionState) (smtplib.Session, error) {
|
||||
sess := &session{
|
||||
loginAnonymous: true,
|
||||
remoteAddr: state.RemoteAddr.String(),
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
type session struct {
|
||||
honeypot *SMTPHoneypot
|
||||
|
||||
loginUsername string
|
||||
loginPassword string
|
||||
loginAnonymous bool
|
||||
remoteAddr string
|
||||
|
||||
mail MailData
|
||||
}
|
||||
|
||||
func (sess *session) Reset() {
|
||||
}
|
||||
func (sess *session) Logout() error {
|
||||
return nil
|
||||
}
|
||||
func (sess *session) Mail(from string, opts smtplib.MailOptions) error {
|
||||
sess.mail.From = from
|
||||
return nil
|
||||
}
|
||||
func (sess *session) Rcpt(to string) error {
|
||||
sess.mail.Recipients = append(sess.mail.Recipients, to)
|
||||
return nil
|
||||
}
|
||||
func (sess *session) Data(r io.Reader) error {
|
||||
lr := io.LimitReader(r, 1024*1024)
|
||||
var buf bytes.Buffer
|
||||
n, err := io.Copy(&buf, lr)
|
||||
if err != nil {
|
||||
sess.honeypot.Logger.Debugw("Error reading email data from client.", "error", err, "remote_addr", sess.remoteAddr)
|
||||
}
|
||||
|
||||
if n == 1024*1024 {
|
||||
// There is probably more data, read the rest from the client
|
||||
// We don't really care what happens to this, or if it errors
|
||||
_, _ = io.Copy(io.Discard, r)
|
||||
}
|
||||
|
||||
sess.mail.Data = buf.String()
|
||||
|
||||
if err := sess.honeypot.Store.AddMailData(&sess.mail); err != nil {
|
||||
sess.honeypot.Logger.Warnw("Error storing maildata.", "error", err, "remote_addr", sess.remoteAddr)
|
||||
return smtplib.ErrDataTooLarge
|
||||
}
|
||||
sess.honeypot.Logger.Infow("Mail received", "from", sess.mail.From, "remote_addr", sess.remoteAddr, "size", len(sess.mail.Data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SMTPHoneypot struct {
|
||||
Addr string
|
||||
Logger *zap.SugaredLogger
|
||||
Store SMTPStore
|
||||
server *smtplib.Server
|
||||
}
|
||||
|
||||
func NewSMTPHoneypot() (*SMTPHoneypot, error) {
|
||||
honeypot := &SMTPHoneypot{
|
||||
Logger: zap.NewNop().Sugar(),
|
||||
}
|
||||
|
||||
return honeypot, nil
|
||||
}
|
||||
|
||||
func (sh *SMTPHoneypot) ListenAndServe() error {
|
||||
server := smtplib.NewServer(&backend{
|
||||
honeypot: sh,
|
||||
})
|
||||
server.Addr = sh.Addr
|
||||
server.Domain = "apiary.t-juice.club"
|
||||
server.AllowInsecureAuth = true
|
||||
server.ReadTimeout = 10 * time.Second
|
||||
server.WriteTimeout = 10 * time.Second
|
||||
sh.server = server
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (sh *SMTPHoneypot) Shutdown() error {
|
||||
return sh.server.Close()
|
||||
}
|
12
honeypot/smtp/store.go
Normal file
12
honeypot/smtp/store.go
Normal file
@ -0,0 +1,12 @@
|
||||
package smtp
|
||||
|
||||
type SMTPStore interface {
|
||||
AddMailData(mail *MailData) error
|
||||
}
|
||||
|
||||
type DiscardStore struct {
|
||||
}
|
||||
|
||||
func (s *DiscardStore) AddMailData(mail *MailData) error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user