2021-10-21 10:36:01 +00:00
|
|
|
package ssh
|
2021-04-10 05:58:01 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"io"
|
|
|
|
"net"
|
2021-04-10 08:53:02 +00:00
|
|
|
"os"
|
2021-04-10 05:58:01 +00:00
|
|
|
"time"
|
2021-09-17 07:27:00 +00:00
|
|
|
"unicode/utf8"
|
2021-04-10 05:58:01 +00:00
|
|
|
|
2021-04-10 08:53:02 +00:00
|
|
|
gossh "golang.org/x/crypto/ssh"
|
|
|
|
|
2022-01-13 08:10:36 +00:00
|
|
|
"git.t-juice.club/torjus/apiary/config"
|
2021-04-10 05:58:01 +00:00
|
|
|
|
2022-01-13 08:10:36 +00:00
|
|
|
"git.t-juice.club/torjus/apiary/honeypot/ssh/store"
|
|
|
|
"git.t-juice.club/torjus/apiary/models"
|
2021-10-21 10:36:01 +00:00
|
|
|
sshlib "github.com/gliderlabs/ssh"
|
2021-04-10 05:58:01 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
type HoneypotServer struct {
|
2021-04-11 00:24:39 +00:00
|
|
|
Logger *zap.SugaredLogger
|
2021-04-10 05:58:01 +00:00
|
|
|
|
2021-10-21 10:36:01 +00:00
|
|
|
sshServer *sshlib.Server
|
2021-04-10 05:58:01 +00:00
|
|
|
|
2021-04-11 00:24:39 +00:00
|
|
|
attemptStore store.LoginAttemptStore
|
|
|
|
attemptsCallbacks []func(l models.LoginAttempt)
|
|
|
|
|
|
|
|
throttleSpeed float64
|
2021-04-10 05:58:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewHoneypotServer(cfg config.HoneypotConfig, store store.LoginAttemptStore) (*HoneypotServer, error) {
|
|
|
|
var hs HoneypotServer
|
|
|
|
hs.attemptStore = store
|
|
|
|
hs.Logger = zap.NewNop().Sugar()
|
|
|
|
|
2021-10-21 10:36:01 +00:00
|
|
|
hs.sshServer = &sshlib.Server{
|
2021-04-10 05:58:01 +00:00
|
|
|
Addr: cfg.ListenAddr,
|
|
|
|
PasswordHandler: hs.passwordHandler,
|
|
|
|
ConnCallback: hs.connCallback,
|
|
|
|
Handler: handler,
|
2021-04-10 08:53:02 +00:00
|
|
|
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)
|
2021-04-10 05:58:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-10-21 10:36:01 +00:00
|
|
|
func (hs *HoneypotServer) passwordHandler(ctx sshlib.Context, password string) bool {
|
2021-04-10 05:58:01 +00:00
|
|
|
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,
|
|
|
|
}
|
2021-04-10 09:59:10 +00:00
|
|
|
country := hs.LookupCountry(la.RemoteIP)
|
2021-09-17 07:27:00 +00:00
|
|
|
if utf8.RuneCountInString(country) > 2 {
|
|
|
|
hs.Logger.Warnw("Too many characters in country", "country", country, "runecount", utf8.RuneCountInString(country))
|
|
|
|
country = "??"
|
|
|
|
}
|
|
|
|
|
2021-04-10 05:58:01 +00:00
|
|
|
la.Country = country
|
|
|
|
hs.Logger.Infow("Login attempt",
|
|
|
|
"remote_ip", la.RemoteIP.String(),
|
|
|
|
"username", la.Username,
|
2021-09-17 07:27:00 +00:00
|
|
|
"password", la.Password,
|
|
|
|
"country", la.Country)
|
2021-04-10 05:58:01 +00:00
|
|
|
|
|
|
|
if err := hs.attemptStore.AddAttempt(&la); err != nil {
|
2021-09-17 00:14:51 +00:00
|
|
|
hs.Logger.Warnw("Error adding attempt to store", "error", err)
|
2021-04-10 05:58:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, cFunc := range hs.attemptsCallbacks {
|
|
|
|
cFunc(la)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-10-21 10:36:01 +00:00
|
|
|
func (s *HoneypotServer) connCallback(ctx sshlib.Context, conn net.Conn) net.Conn {
|
2021-04-10 05:58:01 +00:00
|
|
|
throttledConn := newThrottledConn(conn)
|
2021-04-11 00:24:39 +00:00
|
|
|
throttledConn.SetSpeed(s.throttleSpeed)
|
2021-04-10 05:58:01 +00:00
|
|
|
ctx.SetValue("uuid", throttledConn.ID)
|
2021-04-11 00:24:39 +00:00
|
|
|
throttledConn.SetSpeed(s.throttleSpeed)
|
2021-04-10 05:58:01 +00:00
|
|
|
return throttledConn
|
|
|
|
}
|
|
|
|
|
2021-10-21 10:36:01 +00:00
|
|
|
func handler(session sshlib.Session) {
|
2021-04-10 05:58:01 +00:00
|
|
|
_, _ = 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)
|
|
|
|
}
|