148 lines
2.9 KiB
Go
148 lines
2.9 KiB
Go
/*
|
|
©AngelaMos | 2026
|
|
shell.go
|
|
|
|
Interactive shell emulation for the SSH honeypot
|
|
|
|
Provides a realistic bash-like terminal using golang.org/x/term
|
|
with a root prompt, Ubuntu MOTD banner on connect, and full
|
|
read-eval-print loop. All I/O is captured by the asciicast recorder
|
|
and every command is published to the event bus for analysis.
|
|
*/
|
|
|
|
package sshd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/term"
|
|
|
|
"github.com/CarterPerez-dev/hive/internal/config"
|
|
"github.com/CarterPerez-dev/hive/internal/event"
|
|
"github.com/CarterPerez-dev/hive/internal/session"
|
|
"github.com/CarterPerez-dev/hive/pkg/types"
|
|
)
|
|
|
|
func RunShell(
|
|
ch ssh.Channel,
|
|
sessionID string,
|
|
sensorID string,
|
|
sourceIP string,
|
|
username string,
|
|
hostname string,
|
|
bus *event.Bus,
|
|
recorder *session.Recorder,
|
|
cols int,
|
|
rows int,
|
|
) {
|
|
now := time.Now().UTC()
|
|
banner := fmt.Sprintf(
|
|
config.SSHMOTDTemplate,
|
|
now.Format("Mon Jan 2 15:04:05 UTC 2006"),
|
|
now.Add(-24*time.Hour).Format(
|
|
"Mon Jan 2 15:04:05 2006",
|
|
),
|
|
)
|
|
|
|
writeAndRecord(ch, recorder, []byte(banner))
|
|
|
|
fs := NewFakeFS(hostname)
|
|
cmdCtx := &CommandContext{
|
|
FS: fs,
|
|
Hostname: hostname,
|
|
Username: username,
|
|
CWD: "/root",
|
|
}
|
|
|
|
prompt := fmt.Sprintf(
|
|
"%s@%s:%s# ", username, hostname, cmdCtx.CWD,
|
|
)
|
|
|
|
terminal := term.NewTerminal(ch, prompt)
|
|
_ = terminal.SetSize(cols, rows)
|
|
|
|
for {
|
|
line, err := terminal.ReadLine()
|
|
if err != nil {
|
|
if !errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
break
|
|
}
|
|
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
recorder.WriteInput([]byte(line + "\n"))
|
|
|
|
publishCommand(
|
|
bus, sessionID, sensorID,
|
|
sourceIP, line, hostname,
|
|
)
|
|
|
|
if line == "exit" || line == "logout" || line == "quit" {
|
|
writeAndRecord(
|
|
ch, recorder, []byte("logout\r\n"),
|
|
)
|
|
return
|
|
}
|
|
|
|
output := DispatchCommand(line, cmdCtx)
|
|
if output != "" {
|
|
crlf := strings.ReplaceAll(output, "\n", "\r\n")
|
|
writeAndRecord(ch, recorder, []byte(crlf))
|
|
}
|
|
|
|
prompt = fmt.Sprintf(
|
|
"%s@%s:%s# ",
|
|
username, hostname, cmdCtx.CWD,
|
|
)
|
|
terminal.SetPrompt(prompt)
|
|
}
|
|
}
|
|
|
|
func writeAndRecord(
|
|
w io.Writer,
|
|
recorder *session.Recorder,
|
|
data []byte,
|
|
) {
|
|
_, _ = w.Write(data)
|
|
recorder.WriteOutput(data)
|
|
}
|
|
|
|
func publishCommand(
|
|
bus *event.Bus,
|
|
sessionID string,
|
|
sensorID string,
|
|
sourceIP string,
|
|
command string,
|
|
hostname string,
|
|
) {
|
|
serviceData, _ := json.Marshal(map[string]string{
|
|
"command": command,
|
|
"hostname": hostname,
|
|
})
|
|
|
|
bus.Publish(config.TopicCommand, &types.Event{
|
|
ID: uuid.Must(uuid.NewV7()).String(),
|
|
SessionID: sessionID,
|
|
SensorID: sensorID,
|
|
Timestamp: time.Now().UTC(),
|
|
ServiceType: types.ServiceSSH,
|
|
EventType: types.EventCommand,
|
|
SourceIP: sourceIP,
|
|
Protocol: types.ProtocolTCP,
|
|
SchemaVersion: config.SchemaVersion,
|
|
ServiceData: serviceData,
|
|
})
|
|
}
|