package ssh import ( "context" "io" "net" "os" "time" "unicode/utf8" gossh "golang.org/x/crypto/ssh" "git.t-juice.club/torjus/apiary/config" "git.t-juice.club/torjus/apiary/honeypot/ssh/store" "git.t-juice.club/torjus/apiary/models" sshlib "github.com/gliderlabs/ssh" "github.com/google/uuid" "go.uber.org/zap" ) type HoneypotServer struct { Logger *zap.SugaredLogger sshServer *sshlib.Server attemptStore store.LoginAttemptStore attemptsCallbacks []func(l models.LoginAttempt) throttleSpeed float64 } func NewHoneypotServer(cfg config.HoneypotConfig, store store.LoginAttemptStore) (*HoneypotServer, error) { var hs HoneypotServer hs.attemptStore = store hs.Logger = zap.NewNop().Sugar() hs.sshServer = &sshlib.Server{ Addr: cfg.ListenAddr, PasswordHandler: hs.passwordHandler, ConnCallback: hs.connCallback, Handler: handler, Version: "OpenSSH_7.4p1 Debian-10+deb9u6", } if cfg.HostKeyPath != "" { f, err := os.Open(cfg.HostKeyPath) if err != nil { return nil, err } pemBytes, err := io.ReadAll(f) if err != nil { return nil, err } signer, err := gossh.ParsePrivateKey(pemBytes) if err != nil { return nil, err } hs.sshServer.AddHostKey(signer) } hs.throttleSpeed = 1024 * 1024 return &hs, nil } func (hs *HoneypotServer) ListenAndServe() error { return hs.sshServer.ListenAndServe() } func (hs *HoneypotServer) Shutdown(ctx context.Context) error { return hs.sshServer.Shutdown(ctx) } func (hs *HoneypotServer) AddLoginCallback(c func(l models.LoginAttempt)) { hs.attemptsCallbacks = append(hs.attemptsCallbacks, c) } func (hs *HoneypotServer) passwordHandler(ctx sshlib.Context, password string) bool { sessUUID, ok := ctx.Value("uuid").(uuid.UUID) if !ok { hs.Logger.Warn("Unable to get session UUID") return false } la := models.LoginAttempt{ Date: time.Now(), RemoteIP: ipFromAddr(ctx.RemoteAddr().String()), Username: ctx.User(), Password: password, SSHClientVersion: ctx.ClientVersion(), ConnectionUUID: sessUUID, } country := hs.LookupCountry(la.RemoteIP) if utf8.RuneCountInString(country) > 2 { hs.Logger.Warnw("Too many characters in country", "country", country, "runecount", utf8.RuneCountInString(country)) country = "??" } la.Country = country hs.Logger.Infow("Login attempt", "remote_ip", la.RemoteIP.String(), "username", la.Username, "password", la.Password, "country", la.Country) if err := hs.attemptStore.AddAttempt(&la); err != nil { hs.Logger.Warnw("Error adding attempt to store", "error", err) } for _, cFunc := range hs.attemptsCallbacks { cFunc(la) } return false } func (s *HoneypotServer) connCallback(ctx sshlib.Context, conn net.Conn) net.Conn { s.Logger.Debugw("Connection received.", "remote_addr", conn.RemoteAddr()) throttledConn := newThrottledConn(conn) throttledConn.SetSpeed(s.throttleSpeed) ctx.SetValue("uuid", throttledConn.ID) return throttledConn } func handler(session sshlib.Session) { _, _ = io.WriteString(session, "[root@hostname ~]#") _ = session.Exit(1) } func ipFromAddr(addr string) net.IP { host, _, err := net.SplitHostPort(addr) if err != nil { return nil } return net.ParseIP(host) }