add more functionality to the services

This commit is contained in:
2025-09-28 08:56:11 +01:00
parent 251bf071f0
commit 843080e455
13 changed files with 992 additions and 737 deletions
+2 -22
View File
@@ -24,20 +24,15 @@ type Config struct {
SSH bool `json:"ssh"`
FTP bool `json:"ftp"`
SMTP bool `json:"smtp"`
POP3 bool `json:"pop3"`
IMAP bool `json:"imap"`
Telnet bool `json:"telnet"`
MySQL bool `json:"mysql"`
PostgreSQL bool `json:"postgresql"`
Redis bool `json:"redis"`
MongoDB bool `json:"mongodb"`
RDP bool `json:"rdp"`
SMB bool `json:"smb"`
SIP bool `json:"sip"`
VNC bool `json:"vnc"`
DNS bool `json:"dns"`
SNMP bool `json:"snmp"`
LDAP bool `json:"ldap"`
Generic []int `json:"generic"`
} `json:"services"`
@@ -47,20 +42,15 @@ type Config struct {
SSH int `json:"ssh"`
FTP int `json:"ftp"`
SMTP int `json:"smtp"`
POP3 int `json:"pop3"`
IMAP int `json:"imap"`
Telnet int `json:"telnet"`
MySQL int `json:"mysql"`
PostgreSQL int `json:"postgresql"`
Redis int `json:"redis"`
MongoDB int `json:"mongodb"`
RDP int `json:"rdp"`
SMB int `json:"smb"`
SIP int `json:"sip"`
VNC int `json:"vnc"`
DNS int `json:"dns"`
SNMP int `json:"snmp"`
LDAP int `json:"ldap"`
} `json:"ports"`
// Certificates allows overriding default certificate/key locations.
@@ -70,8 +60,8 @@ type Config struct {
SSHHostKeyPath string `json:"ssh_host_key_path"`
// TLSCertPath and TLSKeyPath are used by TLS-capable services if provided.
// If empty, a self-signed certificate will be generated and stored next to LogPath.
TLSCertPath string `json:"tls_cert_path"`
TLSKeyPath string `json:"tls_key_path"`
TLSCertPath string `json:"tls_cert_path"`
TLSKeyPath string `json:"tls_key_path"`
} `json:"certificates"`
}
@@ -120,17 +110,12 @@ func defaultConfig() Config {
c.Services.Telnet = true
c.Services.MySQL = false
c.Services.PostgreSQL = false
c.Services.Redis = false
c.Services.MongoDB = false
c.Services.POP3 = false
c.Services.IMAP = false
c.Services.RDP = false
c.Services.SMB = false
c.Services.SIP = false
c.Services.VNC = false
c.Services.DNS = false
c.Services.SNMP = false
c.Services.LDAP = false
c.Services.Generic = []int{}
// Standard ports
@@ -139,20 +124,15 @@ func defaultConfig() Config {
c.Ports.SSH = 2222
c.Ports.FTP = 2121
c.Ports.SMTP = 2525
c.Ports.POP3 = 1110
c.Ports.IMAP = 1143
c.Ports.Telnet = 2323
c.Ports.MySQL = 3306
c.Ports.PostgreSQL = 5432
c.Ports.Redis = 6379
c.Ports.MongoDB = 27017
c.Ports.RDP = 3389
c.Ports.SMB = 4450
c.Ports.SIP = 5060
c.Ports.VNC = 5900
c.Ports.DNS = 5353
c.Ports.SNMP = 1161
c.Ports.LDAP = 3890
return c
}
+513 -543
View File
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +0,0 @@
package services
import (
"net"
"strconv"
"time"
)
func NewDNSHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(10 * time.Second))
buf := make([]byte, 512)
n, err := conn.Read(buf)
if err != nil { return }
if n > 12 {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "dns", Details: map[string]string{"event":"query_received","query_size":strconv.Itoa(n)}})
}
}
}
+2 -3
View File
@@ -14,7 +14,6 @@ func NewIMAPHandler(log LoggerFunc) Handler {
_, _ = conn.Write([]byte("* OK IMAP4rev1 Service Ready\r\n"))
conn.SetDeadline(time.Now().Add(2 * time.Minute))
scanner := bufio.NewScanner(conn)
authed := false
selected := false
mailbox := "INBOX"
for scanner.Scan() {
@@ -31,7 +30,6 @@ func NewIMAPHandler(log LoggerFunc) Handler {
pass := strings.Trim(parts[3], "\"")
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "imap", Details: map[string]string{"event":"login_attempt","username":user,"password":pass}})
}
authed = true // pretend success to keep interaction
_, _ = conn.Write([]byte(tag + " OK LOGIN completed\r\n"))
case "CAPABILITY":
_, _ = conn.Write([]byte("* CAPABILITY IMAP4rev1 AUTH=PLAIN IDLE\r\n"))
@@ -44,7 +42,8 @@ func NewIMAPHandler(log LoggerFunc) Handler {
// fake mailbox with 2 messages
_, _ = conn.Write([]byte("* 2 EXISTS\r\n"))
_, _ = conn.Write([]byte("* OK [UIDVALIDITY 1] UIDs valid\r\n"))
_, _ = conn.Write([]byte(tag + " OK [READ-WRITE] SELECT completed\r\n"))
_, _ = conn.Write([]byte(tag + " OK [READ-WRITE] SELECT completed (" + mailbox + ")\r\n"))
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "imap", Details: map[string]string{"event":"select","mailbox":mailbox}})
case "FETCH":
if !selected { _, _ = conn.Write([]byte(tag + " BAD No mailbox selected\r\n")); continue }
// minimal fake fetch
-22
View File
@@ -1,22 +0,0 @@
package services
import (
"net"
"strconv"
"time"
)
func NewLDAPHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(30 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil { return }
if n > 0 {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ldap", Details: map[string]string{"event":"bind_attempt","request_size":strconv.Itoa(n)}})
}
_, _ = conn.Write([]byte{0x30,0x0c,0x02,0x01,0x01,0x61,0x07,0x0a,0x01,0x31,0x04,0x00,0x04,0x00})
}
}
+195 -15
View File
@@ -1,22 +1,202 @@
package services
import (
"net"
"strconv"
"time"
"encoding/binary"
"net"
"strconv"
"strings"
"time"
)
func NewMongoDBHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(30 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil { return }
if n >= 16 {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "mongodb", Details: map[string]string{"event":"protocol_attempt","bytes_received":strconv.Itoa(n)}})
}
_, _ = conn.Write([]byte("connection refused"))
}
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(30 * time.Second))
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil { return }
details := map[string]string{"event":"protocol_attempt","bytes_received":strconv.Itoa(n)}
if n >= 16 {
msgLen := int32(binary.LittleEndian.Uint32(buf[0:4]))
// reqID := int32(binary.LittleEndian.Uint32(buf[4:8]))
// respTo := int32(binary.LittleEndian.Uint32(buf[8:12]))
opCode := int32(binary.LittleEndian.Uint32(buf[12:16]))
details["opcode"] = strconv.Itoa(int(opCode))
details["msg_len"] = strconv.Itoa(int(msgLen))
if opCode == 2004 { // OP_QUERY
// flags (4) + cstring ns starting at offset 20
// skip flags
i := 20
// extract cstring namespace
end := i
for end < n && buf[end] != 0 { end++ }
ns := string(buf[i:end])
details["namespace"] = ns
} else if opCode == 2013 { // OP_MSG (modern commands like hello)
// Structure: flags (4) + sections...
payload := buf[16:n]
if len(payload) >= 5 {
// first section
kind := payload[4]
if kind == 0 && len(payload) > 5 { // body BSON document
doc := payload[5:]
out := map[string]string{}
// Flatten with prefix, cap strings to 64, cap total fields to 32
parseBSONFlat(doc, 64, "", out, 32)
// Extract common command indicators
for _, k := range []string{"hello","isMaster","ismaster","saslStart","saslContinue","client","mechanism"} {
if v, ok := out[k]; ok {
details["op_msg_"+k] = v
}
}
if _, ok := out["hello"]; ok { details["op_msg_hint"] = "hello" }
if _, ok := out["isMaster"]; ok { details["op_msg_hint"] = "isMaster" }
if _, ok := out["ismaster"]; ok { details["op_msg_hint"] = "ismaster" }
if _, ok := out["saslStart"]; ok { details["op_msg_hint"] = "saslStart" }
}
}
}
}
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "mongodb", Details: details})
// Send a minimal isMaster/hello-like JSON to keep client talking
reply := `{"ok":1,"ismaster":true,"maxWireVersion":13,"minWireVersion":0,"helloOk":true}`
_, _ = conn.Write([]byte(reply))
}
}
// parseBSONFlat flattens a BSON document or array into out with key prefixing.
// - maxStr: maximum string length to record
// - prefix: current key prefix (e.g., "client.")
// - out: destination map
// - maxFields: stop after this many fields to avoid floods
func parseBSONFlat(b []byte, maxStr int, prefix string, out map[string]string, maxFields int) {
if len(out) >= maxFields || len(b) < 5 { return }
// First 4 bytes are int32 length; be lenient but keep bounds
i := 4
for i < len(b) && len(out) < maxFields {
t := b[i]
if t == 0x00 { // EOO
break
}
i++
// cstring key
ks := i
for i < len(b) && b[i] != 0 { i++ }
if i >= len(b) { return }
key := string(b[ks:i])
if prefix != "" { key = prefix + key }
i++ // skip NUL
switch t {
case 0x01: // double
if i+8 > len(b) { return }
out[key] = "<double>"
i += 8
case 0x02: // string
if i+4 > len(b) { return }
sl := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
i += 4
if sl <= 0 || i+sl > len(b) { return }
val := string(b[i : i+sl-1]) // exclude trailing NUL
if maxStr > 0 && len(val) > maxStr { val = val[:maxStr] }
out[key] = val
i += sl
case 0x03: // embedded document
if i+4 > len(b) { return }
// read declared length to bound sub-doc
subLen := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
end := i + subLen
if subLen <= 4 || end > len(b) { return }
parseBSONFlat(b[i:end], maxStr, key+".", out, maxFields)
i = end
case 0x04: // array
if i+4 > len(b) { return }
subLen := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
end := i + subLen
if subLen <= 4 || end > len(b) { return }
parseBSONFlat(b[i:end], maxStr, key+".", out, maxFields)
i = end
case 0x08: // bool
if i >= len(b) { return }
if b[i] == 0x01 { out[key] = "true" } else { out[key] = "false" }
i++
case 0x10: // int32
if i+4 > len(b) { return }
v := int32(binary.LittleEndian.Uint32(b[i : i+4]))
out[key] = strconv.FormatInt(int64(v), 10)
i += 4
case 0x12: // int64
if i+8 > len(b) { return }
out[key] = "<int64>"
i += 8
default:
// skip unknown types conservatively: stop parsing to avoid desync
return
}
}
}
// containsAny reports whether s contains any of the substrings in list.
func containsAny(s string, list []string) bool {
ls := strings.ToLower(s)
for _, t := range list {
if t == "" { continue }
if strings.Contains(ls, strings.ToLower(t)) {
return true
}
}
return false
}
// parseBSONStrings extracts top-level string/boolean/int32/double markers from a BSON document buffer.
// It is intentionally minimal and bounded for honeypot logging.
func parseBSONStrings(b []byte, maxStr int) map[string]string {
out := map[string]string{}
if len(b) < 5 {
return out
}
i := 4 // skip doc length
for i < len(b) {
t := b[i]
if t == 0x00 { // terminator
break
}
i++
// read cstring key
ks := i
for i < len(b) && b[i] != 0 { i++ }
if i >= len(b) {
break
}
key := string(b[ks:i])
i++ // skip NUL
switch t {
case 0x02: // string
if i+4 > len(b) { return out }
sl := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
i += 4
if sl <= 0 || i+sl > len(b) { return out }
val := string(b[i : i+sl-1]) // exclude trailing NUL
if maxStr > 0 && len(val) > maxStr { val = val[:maxStr] }
out[key] = val
i += sl
case 0x08: // boolean
if i >= len(b) { return out }
if b[i] == 0x01 { out[key] = "true" } else { out[key] = "false" }
i++
case 0x10: // int32
if i+4 > len(b) { return out }
v := int32(binary.LittleEndian.Uint32(b[i : i+4]))
out[key] = strconv.FormatInt(int64(v), 10)
i += 4
case 0x01: // double
if i+8 > len(b) { return out }
out[key] = "<double>"
i += 8
default:
// Stop on types we don't parse to avoid desync
return out
}
}
return out
}
-39
View File
@@ -1,39 +0,0 @@
package services
import (
"bufio"
"net"
"strings"
"time"
)
func NewPOP3Handler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
_, _ = conn.Write([]byte("+OK POP3 server ready\r\n"))
conn.SetDeadline(time.Now().Add(2 * time.Minute))
scanner := bufio.NewScanner(conn)
var username string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" { continue }
parts := strings.SplitN(line, " ", 2)
cmd := strings.ToUpper(parts[0])
arg := ""; if len(parts)>1 { arg = parts[1] }
switch cmd {
case "USER":
username = arg
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "pop3", Details: map[string]string{"event":"username_attempt","username":username}})
_, _ = conn.Write([]byte("+OK\r\n"))
case "PASS":
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "pop3", Details: map[string]string{"event":"password_attempt","username":username,"password":arg}})
_, _ = conn.Write([]byte("-ERR Authentication failed\r\n"))
case "QUIT":
_, _ = conn.Write([]byte("+OK Bye\r\n")); return
default:
_, _ = conn.Write([]byte("-ERR Unknown command\r\n"))
}
}
}
}
+28 -15
View File
@@ -1,22 +1,35 @@
package services
import (
"net"
"strconv"
"time"
"encoding/binary"
"net"
"strconv"
"time"
)
func NewRDPHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(30 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil { return }
if n > 0 {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "rdp", Details: map[string]string{"event":"protocol_attempt","bytes_received":strconv.Itoa(n)}})
}
_, _ = conn.Write([]byte{0x03,0x00,0x00,0x0b,0x02,0xf0,0x80,0x04,0x01,0x00,0x01})
}
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(30 * time.Second))
buf := make([]byte, 2048)
n, err := conn.Read(buf)
if err != nil { return }
det := map[string]string{"event":"protocol_attempt","bytes_received":strconv.Itoa(n)}
if n >= 4 {
// TPKT Header: 0x03 0x00 length(2)
if buf[0] == 0x03 && buf[1] == 0x00 {
tpktLen := int(binary.BigEndian.Uint16(buf[2:4]))
det["tpkt_len"] = strconv.Itoa(tpktLen)
if n >= 7 {
// Basic X.224 header follows; first byte of X.224 should be length
det["x224_len"] = strconv.Itoa(int(buf[4]))
det["x224_type"] = strconv.Itoa(int(buf[5])) // likely 0xE0 for CR TPDU
}
}
}
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "rdp", Details: det})
// Send short failure/abort PDU to conclude early but cleanly
_, _ = conn.Write([]byte{0x03,0x00,0x00,0x0b,0x02,0xf0,0x80,0x04,0x01,0x00,0x01})
}
}
-34
View File
@@ -1,34 +0,0 @@
package services
import (
"bufio"
"net"
"strings"
"time"
)
func NewRedisHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(2 * time.Minute))
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" { continue }
if strings.ToUpper(line) == "PING" {
_, _ = conn.Write([]byte("+PONG\r\n"))
continue
}
if strings.ToUpper(line) == "AUTH" {
if scanner.Scan() {
password := strings.TrimSpace(scanner.Text())
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "redis", Details: map[string]string{"event":"auth_attempt","password":password}})
}
_, _ = conn.Write([]byte("-ERR invalid password\r\n"))
continue
}
_, _ = conn.Write([]byte("-ERR unknown command\r\n"))
}
}
}
+122
View File
@@ -0,0 +1,122 @@
package services
import (
"bytes"
"encoding/binary"
"net"
"strconv"
"strings"
"time"
)
// NewSMBHandler logs incoming SMB negotiate/session setup attempts and returns a minimal response.
// It is NOT a full SMB implementation; it's enough to keep scanners interacting and capture tokens.
func NewSMBHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(15 * time.Second))
buf := make([]byte, 2048)
n, _ := conn.Read(buf)
if n <= 0 {
return
}
first := buf[:n]
det := map[string]string{"event":"smb_probe","bytes_received":strconv.Itoa(n)}
// rough fingerprint if NTLMSSP is present
if bytes.Contains(first, []byte("NTLMSSP")) {
det["ntlmssp"] = "present"
if u, d, w, lmLen, ntLen := parseNTLMSSP(first); u != "" || d != "" || w != "" {
if u != "" { det["user"] = u }
if d != "" { det["domain"] = d }
if w != "" { det["workstation"] = w }
if lmLen > 0 { det["lm_resp_len"] = strconv.Itoa(lmLen) }
if ntLen > 0 { det["nt_resp_len"] = strconv.Itoa(ntLen) }
}
}
// Try to guess dialect by sniffing strings
fstr := strings.ToUpper(string(first))
if strings.Contains(fstr, "SMB 2.1") || strings.Contains(fstr, "SMB2") {
det["dialect"] = "smb2"
} else if strings.Contains(fstr, "SMB 3") {
det["dialect"] = "smb3"
} else {
det["dialect"] = "unknown"
}
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smb", Details: det})
// Send a minimal SMB2 NEGOTIATE failure-like response (not a valid implementation, just a banner)
resp := []byte{
0x00, 0x00, 0x00, 0x3F, // NetBIOS length header (len following)
0xFE, 0x53, 0x4D, 0x42, // SMB2 magic
0x40, 0x00, 0x00, 0x00, // header fields
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x24, 0x00, // StructureSize
0x00, 0x00, // SecurityMode
0x01, 0x00, // DialectRevision (SMB 2.1)
0x00, 0x00, // NegotiateContextCount
0x00, 0x00, 0x00, 0x00, // ServerGuid part (fake)
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // Capabilities & MaxTrans/Read/WriteSize (fake zeros)
}
_, _ = conn.Write(resp)
}
}
// parseNTLMSSP tries to extract fields from an NTLMSSP message embedded in SMB payloads.
// It supports Type 1, 2, and 3 and focuses on Type 3 credential info.
func parseNTLMSSP(p []byte) (username, domain, workstation string, lmLen, ntLen int) {
sig := []byte("NTLMSSP\x00")
i := bytes.Index(p, sig)
if i < 0 || i+12 >= len(p) { return "","","",0,0 }
// Message type at offset i+8 (LE uint32)
if i+12 > len(p) { return }
mtype := binary.LittleEndian.Uint32(p[i+8 : i+12])
switch mtype {
case 1: // Negotiate - no creds
return "","","",0,0
case 2: // Challenge - no creds to extract
return "","","",0,0
case 3: // Authenticate - contains domain, user, workstation
// Offsets are relative to start of NTLMSSP message
base := i
// Helper to read fields: len(2), maxLen(2), offset(4)
readField := func(off int) (l, o int) {
if base+off+8 > len(p) { return 0, 0 }
l = int(binary.LittleEndian.Uint16(p[base+off : base+off+2]))
o = int(binary.LittleEndian.Uint32(p[base+off+4 : base+off+8]))
return
}
// Per spec Type 3 layout:
// 0x14 LMResp, 0x1C NTResp, 0x24 DomainName, 0x2C UserName, 0x34 Workstation, 0x3C EncryptedRandomSessionKey
lmLen, _ = readField(0x14)
ntLen, _ = readField(0x1C)
domLen, domOff := readField(0x24)
usrLen, usrOff := readField(0x2C)
wsLen, wsOff := readField(0x34)
// Extract UTF-16LE strings safely
username = readUTF16LE(p, usrOff, usrLen)
domain = readUTF16LE(p, domOff, domLen)
workstation = readUTF16LE(p, wsOff, wsLen)
return
default:
return "","","",0,0
}
}
func readUTF16LE(p []byte, off, length int) string {
if off <= 0 || length <= 1 || off+length > len(p) { return "" }
// best-effort: convert UTF-16LE to UTF-8 by dropping high bytes if ASCII
// for simplicity; full decoding not necessary for logging
b := make([]byte, 0, length/2)
for j := 0; j+1 < length && off+j+1 < len(p); j += 2 {
b = append(b, p[off+j])
}
s := strings.TrimSpace(string(b))
return s
}
-21
View File
@@ -1,21 +0,0 @@
package services
import (
"net"
"strconv"
"time"
)
func NewSNMPHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(10 * time.Second))
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil { return }
if n > 0 {
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "snmp", Details: map[string]string{"event":"request_received","request_size":strconv.Itoa(n)}})
}
}
}
+12 -2
View File
@@ -16,8 +16,17 @@ func NewTelnetHandler(log LoggerFunc) Handler {
scanner := bufio.NewScanner(conn)
var username string
expectingPassword := false
inShell := false
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if inShell {
// fake shell: log the command and respond with minimal output
cmd := line
if cmd == "exit" || cmd == "logout" { _, _ = conn.Write([]byte("logout\r\n")); return }
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "telnet", Details: map[string]string{"event":"shell_cmd","cmd":cmd}})
_, _ = conn.Write([]byte("sh: " + cmd + ": command not found\r\n$ "))
continue
}
if !expectingPassword {
username = line
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "telnet", Details: map[string]string{"event":"username_attempt","username":username}})
@@ -26,8 +35,9 @@ func NewTelnetHandler(log LoggerFunc) Handler {
} else {
password := line
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "telnet", Details: map[string]string{"event":"password_attempt","username":username,"password":password}})
_, _ = conn.Write([]byte("\r\nLogin incorrect\r\nlogin: "))
expectingPassword = false
// drop into a fake shell prompt to encourage interaction
_, _ = conn.Write([]byte("\r\nWelcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-42-generic x86_64)\r\n$ "))
inShell = true
}
}
}
+118
View File
@@ -0,0 +1,118 @@
package services
import (
"bufio"
"encoding/binary"
"net"
"strings"
"time"
)
// NewVNCHandler implements a minimal RFB handshake and then fails auth to keep interaction realistic.
func NewVNCHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(20 * time.Second))
// Send server protocol version banner
serverVer := "RFB 003.008\n"
_, _ = conn.Write([]byte(serverVer))
r := bufio.NewReader(conn)
clientVer, _ := r.ReadString('\n')
clientVer = strings.TrimSpace(clientVer)
if clientVer == "" {
return
}
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "vnc", Details: map[string]string{"event":"version","client":clientVer}})
// Offer two security types: None (1) and VNC Authentication (2)
_, _ = conn.Write([]byte{2, 1, 2})
// Read client's selected security type (1 byte)
b, _ := r.ReadByte()
// If client chose None (1), send SecurityResult OK and minimal ServerInit
if b == 1 {
// OK result
_, _ = conn.Write([]byte{0, 0, 0, 0})
// ClientInit (read but ignore)
_, _ = r.ReadByte()
// Send ServerInit: framebuffer width/height, pixel format, name length + name
// Width=800, Height=600
srv := make([]byte, 0, 24)
srv = append(srv, 0x03, 0x20) // width 800
srv = append(srv, 0x02, 0x58) // height 600
// Pixel format (16 bytes): 32bpp, 24 depth, big endian=0, trueColor=1, max red/green/blue, shifts
srv = append(srv, 32, 24, 0, 1, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 16, 8, 0)
// pad 3 bytes
srv = append(srv, 0, 0, 0)
// Name length and name
name := []byte("Ubuntu VNC")
nameLen := []byte{0, 0, byte(len(name) >> 8), byte(len(name) & 0xff)}
// Actually RFB uses 32-bit name length big-endian
nameLen = []byte{0, 0, 0, byte(len(name))}
srv = append(srv, nameLen...)
srv = append(srv, name...)
_, _ = conn.Write(srv)
// Enter a simple loop to keep client engaged: respond to FramebufferUpdateRequest (type=3)
for {
// Read message type
mt, err := r.ReadByte()
if err != nil { return }
switch mt {
case 0: // SetPixelFormat: skip 3 pad + 16 bytes
skip := make([]byte, 19)
if _, err := r.Read(skip); err != nil { return }
case 2: // SetEncodings: 1 pad + int16 num + list
pad, _ := r.ReadByte(); _ = pad
Hdr := make([]byte, 2)
if _, err := r.Read(Hdr); err != nil { return }
nEnc := int(binary.BigEndian.Uint16(Hdr))
encs := make([]byte, nEnc*4)
if _, err := r.Read(encs); err != nil { return }
case 3: // FramebufferUpdateRequest
req := make([]byte, 9)
if _, err := r.Read(req); err != nil { return }
// incremental := req[0]
// x,y,w,h (big-endian)
// Respond with one small RAW rectangle 64x32 at 0,0
reply := make([]byte, 0, 4+12+64*32*4)
// message-type=0 (FramebufferUpdate) + pad
reply = append(reply, 0, 0)
// number-of-rectangles = 1
reply = append(reply, 0, 1)
// x=0,y=0,w=64,h=32
rb := make([]byte, 12)
// x,y
binary.BigEndian.PutUint16(rb[0:2], 0)
binary.BigEndian.PutUint16(rb[2:4], 0)
// w,h
binary.BigEndian.PutUint16(rb[4:6], 64)
binary.BigEndian.PutUint16(rb[6:8], 32)
// encoding RAW = 0
binary.BigEndian.PutUint32(rb[8:12], 0)
reply = append(reply, rb...)
// pixel data (32bpp). Simple grey fill (e.g., 0x202020ff little-endian if needed)
px := make([]byte, 64*32*4)
for i := 0; i < len(px); i += 4 {
// BGRA for many viewers when little endian flag=0; we keep it neutral
px[i+0] = 0x20
px[i+1] = 0x20
px[i+2] = 0x20
px[i+3] = 0xFF
}
reply = append(reply, px...)
if _, err := conn.Write(reply); err != nil { return }
default:
// Unknown or unhandled; try to keep reading, but bail on error
}
}
return
}
// Otherwise (e.g., VNC Auth), send failure
_, _ = conn.Write([]byte{0, 0, 0, 1})
reason := []byte("Authentication failed")
msg := append([]byte{0, 0, 0, byte(len(reason))}, reason...)
_, _ = conn.Write(msg)
}
}