add more functionality to the services
This commit is contained in:
+2
-22
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)}})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user