Files
honeydany/app/services/ssh.go
T

133 lines
5.4 KiB
Go

package services
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"net"
"strconv"
"time"
"golang.org/x/crypto/ssh"
)
// NewSSHHandler returns a TCP handler that performs SSH handshake and logs auth attempts.
// getSigner is used to obtain a host key signer (so the app can reuse/generate one).
func NewSSHHandler(log LoggerFunc, getSigner func() (ssh.Signer, error)) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
sessionID := fmt.Sprintf("%x", time.Now().UnixNano())
start := time.Now()
signer, err := getSigner()
if err != nil {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{"error": "hostkey"}, RawPayload: err.Error()})
return
}
var authAttempts int
var lastUser, lastPass string
cfg := &ssh.ServerConfig{
NoClientAuth: false,
ServerVersion: "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1", // Updated version string
MaxAuthTries: 6, // Allow more attempts like real SSH (default is usually 6)
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
authAttempts++
lastUser = c.User()
lastPass = string(pass)
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
"event": "auth_attempt",
"attempt": strconv.Itoa(authAttempts),
"username": lastUser,
"password": lastPass,
"client": string(c.ClientVersion()),
"session_id": sessionID,
}})
// Small delay to simulate real SSH behavior
time.Sleep(100 * time.Millisecond)
return nil, fmt.Errorf("permission denied")
},
KeyboardInteractiveCallback: func(c ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
// Log keyboard interactive attempts
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
"event": "keyboard_interactive_attempt",
"username": c.User(),
"client": string(c.ClientVersion()),
"session_id": sessionID,
}})
return nil, fmt.Errorf("permission denied")
},
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
// Log public key attempts
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
"event": "pubkey_attempt",
"username": c.User(),
"key_type": pubKey.Type(),
"key_fingerprint": ssh.FingerprintSHA256(pubKey),
"client": string(c.ClientVersion()),
"session_id": sessionID,
}})
return nil, fmt.Errorf("permission denied")
},
}
cfg.AddHostKey(signer)
// Set stricter timeout
_ = conn.SetDeadline(time.Now().Add(90 * time.Second))
sc, chans, reqs, err := ssh.NewServerConn(conn, cfg)
if err != nil {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
"event": "session_end",
"session_id": sessionID,
"auth_attempts": strconv.Itoa(authAttempts),
"duration_sec": fmt.Sprintf("%.2f", time.Since(start).Seconds()),
"last_username": lastUser,
"last_password": lastPass,
"error": err.Error(),
}})
return
}
// Handle requests and channels with logging
go func() {
for req := range reqs {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
"event": "global_request",
"request_type": req.Type,
"want_reply": fmt.Sprintf("%v", req.WantReply),
"session_id": sessionID,
}})
if req.WantReply {
req.Reply(false, nil)
}
}
}()
for ch := range chans {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
"event": "channel_request",
"channel_type": ch.ChannelType(),
"session_id": sessionID,
}})
_ = ch.Reject(ssh.Prohibited, "not allowed")
}
_ = sc.Close()
}
}
// DefaultSigner provides a simple RSA signer if caller doesn't have one.
func DefaultSigner() (ssh.Signer, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return ssh.NewSignerFromKey(key)
}