update services to separate files
This commit is contained in:
@@ -20,6 +20,7 @@ type Config struct {
|
||||
|
||||
Services struct {
|
||||
HTTP bool `json:"http"`
|
||||
HTTPS bool `json:"https"`
|
||||
SSH bool `json:"ssh"`
|
||||
FTP bool `json:"ftp"`
|
||||
SMTP bool `json:"smtp"`
|
||||
@@ -42,6 +43,7 @@ type Config struct {
|
||||
|
||||
Ports struct {
|
||||
HTTP int `json:"http"`
|
||||
HTTPS int `json:"https"`
|
||||
SSH int `json:"ssh"`
|
||||
FTP int `json:"ftp"`
|
||||
SMTP int `json:"smtp"`
|
||||
@@ -60,6 +62,17 @@ type Config struct {
|
||||
SNMP int `json:"snmp"`
|
||||
LDAP int `json:"ldap"`
|
||||
} `json:"ports"`
|
||||
|
||||
// Certificates allows overriding default certificate/key locations.
|
||||
Certificates struct {
|
||||
// SSHHostKeyPath points to a PEM-encoded RSA private key to use as SSH host key.
|
||||
// If empty, a persistent key will be created in the same directory as LogPath.
|
||||
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"`
|
||||
} `json:"certificates"`
|
||||
}
|
||||
|
||||
// EnsureConfig writes a default config file if the given path doesn't exist
|
||||
@@ -100,6 +113,7 @@ func defaultConfig() Config {
|
||||
|
||||
// Enable common services by default
|
||||
c.Services.HTTP = true
|
||||
c.Services.HTTPS = false
|
||||
c.Services.SSH = true
|
||||
c.Services.FTP = true
|
||||
c.Services.SMTP = true
|
||||
@@ -121,6 +135,7 @@ func defaultConfig() Config {
|
||||
|
||||
// Standard ports
|
||||
c.Ports.HTTP = 8080
|
||||
c.Ports.HTTPS = 8443
|
||||
c.Ports.SSH = 2222
|
||||
c.Ports.FTP = 2121
|
||||
c.Ports.SMTP = 2525
|
||||
|
||||
@@ -1,619 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FTP Handler - implements basic FTP protocol with authentication logging
|
||||
func (a *App) ftpHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("ftp_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "ftp", "connection_start", nil)
|
||||
|
||||
// Send FTP welcome banner
|
||||
_, _ = conn.Write([]byte("220 Welcome to FTP Server\r\n"))
|
||||
|
||||
conn.SetDeadline(time.Now().Add(5 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
var username, password string
|
||||
authenticated := false
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, " ", 2)
|
||||
if len(parts) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := strings.ToUpper(parts[0])
|
||||
arg := ""
|
||||
if len(parts) > 1 {
|
||||
arg = parts[1]
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "USER":
|
||||
username = arg
|
||||
a.logServiceEvent(sessionID, remote, "ftp", "username_attempt", map[string]string{"username": username})
|
||||
_, _ = conn.Write([]byte("331 Password required for " + username + "\r\n"))
|
||||
case "PASS":
|
||||
password = arg
|
||||
a.logServiceEvent(sessionID, remote, "ftp", "password_attempt", map[string]string{
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
_, _ = conn.Write([]byte("530 Login incorrect\r\n"))
|
||||
case "QUIT":
|
||||
_, _ = conn.Write([]byte("221 Goodbye\r\n"))
|
||||
return
|
||||
default:
|
||||
if !authenticated {
|
||||
_, _ = conn.Write([]byte("530 Please login with USER and PASS\r\n"))
|
||||
} else {
|
||||
_, _ = conn.Write([]byte("502 Command not implemented\r\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SMTP Handler - implements basic SMTP protocol
|
||||
func (a *App) smtpHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("smtp_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "smtp", "connection_start", nil)
|
||||
|
||||
// Send SMTP welcome banner
|
||||
_, _ = conn.Write([]byte("220 mail.example.com ESMTP Postfix\r\n"))
|
||||
|
||||
conn.SetDeadline(time.Now().Add(5 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, " ", 2)
|
||||
if len(parts) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := strings.ToUpper(parts[0])
|
||||
arg := ""
|
||||
if len(parts) > 1 {
|
||||
arg = parts[1]
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "HELO", "EHLO":
|
||||
a.logServiceEvent(sessionID, remote, "smtp", "helo", map[string]string{"hostname": arg})
|
||||
if cmd == "EHLO" {
|
||||
_, _ = conn.Write([]byte("250-mail.example.com\r\n250-AUTH PLAIN LOGIN\r\n250 OK\r\n"))
|
||||
} else {
|
||||
_, _ = conn.Write([]byte("250 mail.example.com\r\n"))
|
||||
}
|
||||
case "AUTH":
|
||||
a.handleSMTPAuth(conn, sessionID, remote, arg)
|
||||
case "MAIL":
|
||||
_, _ = conn.Write([]byte("250 OK\r\n"))
|
||||
case "RCPT":
|
||||
_, _ = conn.Write([]byte("250 OK\r\n"))
|
||||
case "DATA":
|
||||
_, _ = conn.Write([]byte("354 End data with <CR><LF>.<CR><LF>\r\n"))
|
||||
case "QUIT":
|
||||
_, _ = conn.Write([]byte("221 Bye\r\n"))
|
||||
return
|
||||
default:
|
||||
_, _ = conn.Write([]byte("502 Command not implemented\r\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleSMTPAuth(conn net.Conn, sessionID, remote, authLine string) {
|
||||
parts := strings.Fields(authLine)
|
||||
if len(parts) < 1 {
|
||||
_, _ = conn.Write([]byte("501 Syntax error\r\n"))
|
||||
return
|
||||
}
|
||||
|
||||
method := strings.ToUpper(parts[0])
|
||||
switch method {
|
||||
case "PLAIN":
|
||||
if len(parts) > 1 {
|
||||
// Decode base64 auth
|
||||
decoded, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
if err == nil {
|
||||
authParts := strings.Split(string(decoded), "\x00")
|
||||
if len(authParts) >= 3 {
|
||||
username := authParts[1]
|
||||
password := authParts[2]
|
||||
a.logServiceEvent(sessionID, remote, "smtp", "auth_attempt", map[string]string{
|
||||
"method": "PLAIN",
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, _ = conn.Write([]byte("334 \r\n"))
|
||||
return
|
||||
}
|
||||
_, _ = conn.Write([]byte("535 Authentication failed\r\n"))
|
||||
case "LOGIN":
|
||||
_, _ = conn.Write([]byte("334 VXNlcm5hbWU6\r\n")) // "Username:" in base64
|
||||
default:
|
||||
_, _ = conn.Write([]byte("504 Authentication method not supported\r\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// Telnet Handler
|
||||
func (a *App) telnetHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("telnet_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "telnet", "connection_start", nil)
|
||||
|
||||
// Send telnet login prompt
|
||||
_, _ = conn.Write([]byte("\r\nUbuntu 20.04.3 LTS\r\n\r\nlogin: "))
|
||||
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
var username string
|
||||
expectingPassword := false
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
if !expectingPassword {
|
||||
username = line
|
||||
a.logServiceEvent(sessionID, remote, "telnet", "username_attempt", map[string]string{"username": username})
|
||||
_, _ = conn.Write([]byte("Password: "))
|
||||
expectingPassword = true
|
||||
} else {
|
||||
password := line
|
||||
a.logServiceEvent(sessionID, remote, "telnet", "password_attempt", map[string]string{
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
_, _ = conn.Write([]byte("\r\nLogin incorrect\r\nlogin: "))
|
||||
expectingPassword = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MySQL Handler
|
||||
func (a *App) mysqlHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("mysql_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "mysql", "connection_start", nil)
|
||||
|
||||
// Send MySQL handshake packet
|
||||
handshake := []byte{
|
||||
0x4a, 0x00, 0x00, 0x00, // packet length + sequence
|
||||
0x0a, // protocol version
|
||||
}
|
||||
handshake = append(handshake, []byte("5.7.34-0ubuntu0.18.04.1\x00")...) // server version
|
||||
handshake = append(handshake, []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // connection id
|
||||
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, // auth plugin data part 1
|
||||
0x00, // filler
|
||||
0xff, 0xf7, // capability flags lower 2 bytes
|
||||
0x08, // character set
|
||||
0x02, 0x00, // status flags
|
||||
0x0f, 0x80, // capability flags upper 2 bytes
|
||||
0x15, // auth plugin data length
|
||||
}...)
|
||||
|
||||
_, _ = conn.Write(handshake)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err == nil && n > 4 {
|
||||
// Parse login packet (simplified)
|
||||
payload := buf[4:n] // skip packet header
|
||||
if len(payload) > 32 {
|
||||
// Extract username (simplified parsing)
|
||||
usernameStart := 32
|
||||
usernameEnd := usernameStart
|
||||
for usernameEnd < len(payload) && payload[usernameEnd] != 0 {
|
||||
usernameEnd++
|
||||
}
|
||||
if usernameEnd < len(payload) {
|
||||
username := string(payload[usernameStart:usernameEnd])
|
||||
a.logServiceEvent(sessionID, remote, "mysql", "auth_attempt", map[string]string{
|
||||
"username": username,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send error packet
|
||||
errorPacket := []byte{
|
||||
0x24, 0x00, 0x00, 0x01, // packet header
|
||||
0xff, // error packet marker
|
||||
0x10, 0x04, // error code
|
||||
0x23, 0x48, 0x59, 0x30, 0x30, 0x30, // SQL state
|
||||
}
|
||||
errorPacket = append(errorPacket, []byte("Access denied for user")...)
|
||||
_, _ = conn.Write(errorPacket)
|
||||
}
|
||||
|
||||
// PostgreSQL Handler
|
||||
func (a *App) postgresqlHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("postgresql_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "postgresql", "connection_start", nil)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n >= 8 {
|
||||
// Parse startup message
|
||||
length := int(buf[0])<<24 | int(buf[1])<<16 | int(buf[2])<<8 | int(buf[3])
|
||||
if length > 8 && n >= length {
|
||||
params := string(buf[8:length])
|
||||
// Extract username from parameters
|
||||
if strings.Contains(params, "user") {
|
||||
re := regexp.MustCompile(`user\x00([^\x00]+)`)
|
||||
matches := re.FindStringSubmatch(params)
|
||||
if len(matches) > 1 {
|
||||
username := matches[1]
|
||||
a.logServiceEvent(sessionID, remote, "postgresql", "auth_attempt", map[string]string{
|
||||
"username": username,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send authentication request
|
||||
authRequest := []byte{
|
||||
0x52, // Authentication message type
|
||||
0x00, 0x00, 0x00, 0x08, // message length
|
||||
0x00, 0x00, 0x00, 0x03, // auth type (cleartext password)
|
||||
}
|
||||
_, _ = conn.Write(authRequest)
|
||||
|
||||
// Read password response
|
||||
n, err = conn.Read(buf)
|
||||
if err == nil && n > 5 && buf[0] == 0x70 { // password message
|
||||
length := int(buf[1])<<24 | int(buf[2])<<16 | int(buf[3])<<8 | int(buf[4])
|
||||
if length > 5 && n >= length {
|
||||
password := string(buf[5 : length-1]) // exclude null terminator
|
||||
a.logServiceEvent(sessionID, remote, "postgresql", "password_attempt", map[string]string{
|
||||
"password": password,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Send error response
|
||||
errorMsg := []byte{
|
||||
0x45, // Error message type
|
||||
0x00, 0x00, 0x00, 0x26, // message length
|
||||
}
|
||||
errorMsg = append(errorMsg, []byte("SFATAL\x00C28P01\x00Mpassword authentication failed\x00\x00")...)
|
||||
_, _ = conn.Write(errorMsg)
|
||||
}
|
||||
|
||||
// Redis Handler
|
||||
func (a *App) redisHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("redis_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "redis", "connection_start", nil)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse Redis protocol
|
||||
if strings.HasPrefix(line, "*") {
|
||||
// Multi-bulk request
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "$") {
|
||||
// Bulk string length
|
||||
continue
|
||||
} else {
|
||||
// Command
|
||||
cmd := strings.ToUpper(line)
|
||||
switch cmd {
|
||||
case "AUTH":
|
||||
// Next line should be password
|
||||
if scanner.Scan() {
|
||||
password := strings.TrimSpace(scanner.Text())
|
||||
a.logServiceEvent(sessionID, remote, "redis", "auth_attempt", map[string]string{
|
||||
"password": password,
|
||||
})
|
||||
}
|
||||
_, _ = conn.Write([]byte("-ERR invalid password\r\n"))
|
||||
case "PING":
|
||||
_, _ = conn.Write([]byte("+PONG\r\n"))
|
||||
case "INFO":
|
||||
_, _ = conn.Write([]byte("+redis_version:6.2.6\r\n"))
|
||||
case "QUIT":
|
||||
_, _ = conn.Write([]byte("+OK\r\n"))
|
||||
return
|
||||
default:
|
||||
_, _ = conn.Write([]byte("-ERR unknown command\r\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MongoDB Handler
|
||||
func (a *App) mongodbHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("mongodb_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "mongodb", "connection_start", nil)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n >= 16 {
|
||||
// Parse MongoDB wire protocol message
|
||||
// This is a simplified implementation
|
||||
a.logServiceEvent(sessionID, remote, "mongodb", "protocol_attempt", map[string]string{
|
||||
"bytes_received": strconv.Itoa(n),
|
||||
})
|
||||
}
|
||||
|
||||
// Send connection error
|
||||
_, _ = conn.Write([]byte("connection refused"))
|
||||
}
|
||||
|
||||
// POP3 Handler
|
||||
func (a *App) pop3Handler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("pop3_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "pop3", "connection_start", nil)
|
||||
|
||||
_, _ = 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)
|
||||
if len(parts) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := strings.ToUpper(parts[0])
|
||||
arg := ""
|
||||
if len(parts) > 1 {
|
||||
arg = parts[1]
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "USER":
|
||||
username = arg
|
||||
a.logServiceEvent(sessionID, remote, "pop3", "username_attempt", map[string]string{"username": username})
|
||||
_, _ = conn.Write([]byte("+OK\r\n"))
|
||||
case "PASS":
|
||||
password := arg
|
||||
a.logServiceEvent(sessionID, remote, "pop3", "password_attempt", map[string]string{
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
_, _ = 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IMAP Handler
|
||||
func (a *App) imapHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("imap_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "imap", "connection_start", nil)
|
||||
|
||||
_, _ = conn.Write([]byte("* OK IMAP4rev1 Service Ready\r\n"))
|
||||
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
tag := parts[0]
|
||||
cmd := strings.ToUpper(parts[1])
|
||||
|
||||
switch cmd {
|
||||
case "LOGIN":
|
||||
if len(parts) >= 4 {
|
||||
username := strings.Trim(parts[2], "\"")
|
||||
password := strings.Trim(parts[3], "\"")
|
||||
a.logServiceEvent(sessionID, remote, "imap", "login_attempt", map[string]string{
|
||||
"username": username,
|
||||
"password": password,
|
||||
})
|
||||
}
|
||||
_, _ = conn.Write([]byte(tag + " NO LOGIN failed\r\n"))
|
||||
case "CAPABILITY":
|
||||
_, _ = conn.Write([]byte("* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n"))
|
||||
_, _ = conn.Write([]byte(tag + " OK CAPABILITY completed\r\n"))
|
||||
case "LOGOUT":
|
||||
_, _ = conn.Write([]byte("* BYE IMAP4rev1 Server logging out\r\n"))
|
||||
_, _ = conn.Write([]byte(tag + " OK LOGOUT completed\r\n"))
|
||||
return
|
||||
default:
|
||||
_, _ = conn.Write([]byte(tag + " BAD Command not recognized\r\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RDP Handler (simplified)
|
||||
func (a *App) rdpHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("rdp_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "rdp", "connection_start", nil)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
a.logServiceEvent(sessionID, remote, "rdp", "protocol_attempt", map[string]string{
|
||||
"bytes_received": strconv.Itoa(n),
|
||||
})
|
||||
}
|
||||
|
||||
// Send RDP connection failure
|
||||
_, _ = conn.Write([]byte{0x03, 0x00, 0x00, 0x0b, 0x02, 0xf0, 0x80, 0x04, 0x01, 0x00, 0x01})
|
||||
}
|
||||
|
||||
// DNS Handler (UDP)
|
||||
func (a *App) dnsHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("dns_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "dns", "query_attempt", nil)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
buf := make([]byte, 512)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n > 12 {
|
||||
// Parse DNS query (simplified)
|
||||
a.logServiceEvent(sessionID, remote, "dns", "query_received", map[string]string{
|
||||
"query_size": strconv.Itoa(n),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SNMP Handler (UDP)
|
||||
func (a *App) snmpHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("snmp_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "snmp", "request_attempt", nil)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
a.logServiceEvent(sessionID, remote, "snmp", "request_received", map[string]string{
|
||||
"request_size": strconv.Itoa(n),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// LDAP Handler
|
||||
func (a *App) ldapHandler(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
sessionID := fmt.Sprintf("ldap_%x", time.Now().UnixNano())
|
||||
|
||||
a.logServiceEvent(sessionID, remote, "ldap", "connection_start", nil)
|
||||
|
||||
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
a.logServiceEvent(sessionID, remote, "ldap", "bind_attempt", map[string]string{
|
||||
"request_size": strconv.Itoa(n),
|
||||
})
|
||||
}
|
||||
|
||||
// Send LDAP bind failure
|
||||
_, _ = conn.Write([]byte{0x30, 0x0c, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0a, 0x01, 0x31, 0x04, 0x00, 0x04, 0x00})
|
||||
}
|
||||
|
||||
// Helper function to log service events
|
||||
func (a *App) logServiceEvent(sessionID, remote, service, eventType string, details map[string]string) {
|
||||
if details == nil {
|
||||
details = make(map[string]string)
|
||||
}
|
||||
details["session_id"] = sessionID
|
||||
details["event_type"] = eventType
|
||||
|
||||
a.logEvent(Record{
|
||||
Timestamp: time.Now().UTC(),
|
||||
RemoteAddr: remoteIP(remote),
|
||||
RemotePort: remotePort(remote),
|
||||
Service: service,
|
||||
Details: details,
|
||||
RawPayload: fmt.Sprintf("%s: %s", eventType, sessionID),
|
||||
})
|
||||
}
|
||||
+191
-22
@@ -4,18 +4,25 @@ import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"encoding/pem"
|
||||
"golang.org/x/crypto/ssh"
|
||||
svcs "honeydany/app/services"
|
||||
)
|
||||
|
||||
// App holds runtime pieces
|
||||
@@ -35,6 +42,52 @@ type App struct {
|
||||
conns map[net.Conn]struct{}
|
||||
}
|
||||
|
||||
// HTTPS honeypot using self-signed or configured certificate
|
||||
func (a *App) startHTTPS(port int) {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
var bodySnippet string
|
||||
if r.Body != nil {
|
||||
b, _ := io.ReadAll(io.LimitReader(r.Body, 1024*64))
|
||||
bodySnippet = string(b)
|
||||
}
|
||||
details := map[string]string{"method": r.Method, "url": r.URL.String(), "proto": r.Proto}
|
||||
if ua := r.Header.Get("User-Agent"); ua != "" { details["user_agent"] = ua }
|
||||
if sni := r.TLS; sni != nil && sni.ServerName != "" { details["sni_server_name"] = sni.ServerName }
|
||||
rec := Record{Timestamp: time.Now().UTC(), RemoteAddr: remoteIP(r.RemoteAddr), RemotePort: remotePort(r.RemoteAddr), Service: "https", Details: details, RawPayload: bodySnippet}
|
||||
a.logEvent(rec)
|
||||
|
||||
w.Header().Set("Server", "nginx/1.18.0 (Ubuntu)")
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(200)
|
||||
_, _ = w.Write([]byte("Welcome (TLS)\n"))
|
||||
})
|
||||
|
||||
// Load or create certificate
|
||||
cert, _, _, err := a.GetOrCreateTLSCertificate()
|
||||
if err != nil {
|
||||
log.Printf("HTTPS certificate error: %v", err)
|
||||
return
|
||||
}
|
||||
tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
|
||||
srv := &http.Server{Addr: addr, Handler: mux, TLSConfig: tlsCfg}
|
||||
a.addHTTPServer(srv)
|
||||
log.Printf("HTTPS listening on %s", addr)
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Printf("https listen failed on %s: %v", addr, err)
|
||||
return
|
||||
}
|
||||
// Wrap with TLS and serve
|
||||
tlsLn := tls.NewListener(ln, tlsCfg)
|
||||
if err := srv.Serve(tlsLn); err != nil && err != http.ErrServerClosed {
|
||||
log.Printf("https server error: %v", err)
|
||||
}
|
||||
log.Printf("https stopped")
|
||||
}
|
||||
|
||||
// addHTTPServer registers an HTTP server for graceful shutdown
|
||||
func (a *App) addHTTPServer(s *http.Server) {
|
||||
a.mu.Lock()
|
||||
@@ -158,123 +211,131 @@ func (a *App) Run(ctx context.Context) error {
|
||||
a.startHTTP(a.cfg.Ports.HTTP)
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.HTTPS {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startHTTPS(a.cfg.Ports.HTTPS)
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SSH {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("ssh", a.cfg.Ports.SSH, a.sshHandler)
|
||||
handler := svcs.NewSSHHandler(a.svcLogger(), a.getSSHSigner)
|
||||
a.startTCPService("ssh", a.cfg.Ports.SSH, handler)
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.FTP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("ftp", a.cfg.Ports.FTP, a.ftpHandler)
|
||||
a.startTCPService("ftp", a.cfg.Ports.FTP, svcs.NewFTPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SMTP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("smtp", a.cfg.Ports.SMTP, a.smtpHandler)
|
||||
a.startTCPService("smtp", a.cfg.Ports.SMTP, svcs.NewSMTPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.POP3 {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("pop3", a.cfg.Ports.POP3, a.pop3Handler)
|
||||
a.startTCPService("pop3", a.cfg.Ports.POP3, svcs.NewPOP3Handler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.IMAP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("imap", a.cfg.Ports.IMAP, a.imapHandler)
|
||||
a.startTCPService("imap", a.cfg.Ports.IMAP, svcs.NewIMAPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.Telnet {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("telnet", a.cfg.Ports.Telnet, a.telnetHandler)
|
||||
a.startTCPService("telnet", a.cfg.Ports.Telnet, svcs.NewTelnetHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.MySQL {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("mysql", a.cfg.Ports.MySQL, a.mysqlHandler)
|
||||
a.startTCPService("mysql", a.cfg.Ports.MySQL, svcs.NewMySQLHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.PostgreSQL {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("postgresql", a.cfg.Ports.PostgreSQL, a.postgresqlHandler)
|
||||
a.startTCPService("postgresql", a.cfg.Ports.PostgreSQL, svcs.NewPostgreSQLHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.Redis {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("redis", a.cfg.Ports.Redis, a.redisHandler)
|
||||
a.startTCPService("redis", a.cfg.Ports.Redis, svcs.NewRedisHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.MongoDB {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("mongodb", a.cfg.Ports.MongoDB, a.mongodbHandler)
|
||||
a.startTCPService("mongodb", a.cfg.Ports.MongoDB, svcs.NewMongoDBHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.RDP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("rdp", a.cfg.Ports.RDP, a.rdpHandler)
|
||||
a.startTCPService("rdp", a.cfg.Ports.RDP, svcs.NewRDPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SMB {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("smb", a.cfg.Ports.SMB, a.genericBannerHandler("SMB-Server-1.0"))
|
||||
a.startTCPService("smb", a.cfg.Ports.SMB, svcs.NewGenericBannerHandler("SMB-Server-1.0", a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.VNC {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("vnc", a.cfg.Ports.VNC, a.genericBannerHandler("RFB 003.008"))
|
||||
a.startTCPService("vnc", a.cfg.Ports.VNC, svcs.NewGenericBannerHandler("RFB 003.008", a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SIP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("sip", a.cfg.Ports.SIP, a.sipHandler)
|
||||
a.startTCPService("sip", a.cfg.Ports.SIP, svcs.NewSIPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.DNS {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("dns", a.cfg.Ports.DNS, a.dnsHandler)
|
||||
a.startTCPService("dns", a.cfg.Ports.DNS, svcs.NewDNSHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.SNMP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("snmp", a.cfg.Ports.SNMP, a.snmpHandler)
|
||||
a.startTCPService("snmp", a.cfg.Ports.SNMP, svcs.NewSNMPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
if a.cfg.Services.LDAP {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService("ldap", a.cfg.Ports.LDAP, a.ldapHandler)
|
||||
a.startTCPService("ldap", a.cfg.Ports.LDAP, svcs.NewLDAPHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
for _, p := range a.cfg.Services.Generic {
|
||||
@@ -282,7 +343,7 @@ func (a *App) Run(ctx context.Context) error {
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
a.startTCPService(fmt.Sprintf("generic-%d", port), port, a.genericEchoHandler)
|
||||
a.startTCPService(fmt.Sprintf("generic-%d", port), port, svcs.NewGenericEchoHandler(a.svcLogger()))
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -380,6 +441,20 @@ func (a *App) logEvent(r Record) {
|
||||
}
|
||||
}
|
||||
|
||||
// svcLogger adapts services.Record to the app Record and logs it
|
||||
func (a *App) svcLogger() func(svcs.Record) {
|
||||
return func(sr svcs.Record) {
|
||||
a.logEvent(Record{
|
||||
Timestamp: sr.Timestamp,
|
||||
RemoteAddr: sr.RemoteAddr,
|
||||
RemotePort: sr.RemotePort,
|
||||
Service: sr.Service,
|
||||
Details: sr.Details,
|
||||
RawPayload: sr.RawPayload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP honeypot
|
||||
func (a *App) startHTTP(port int) {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
@@ -549,11 +624,15 @@ func (a *App) getSSHSigner() (ssh.Signer, error) {
|
||||
if a.sshSigner != nil {
|
||||
return a.sshSigner, nil
|
||||
}
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Determine host key path: config override or alongside LogPath
|
||||
var hostKeyPath string
|
||||
if a.cfg.Certificates.SSHHostKeyPath != "" {
|
||||
hostKeyPath = a.cfg.Certificates.SSHHostKeyPath
|
||||
} else {
|
||||
baseDir := a.dataDir()
|
||||
hostKeyPath = filepath.Join(baseDir, "ssh_host_key.pem")
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
signer, err := a.loadOrCreateSSHHostKey(hostKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -561,6 +640,96 @@ func (a *App) getSSHSigner() (ssh.Signer, error) {
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// dataDir returns the directory where persistent data should live (same dir as LogPath)
|
||||
func (a *App) dataDir() string {
|
||||
if a.cfg.LogPath == "" {
|
||||
return "."
|
||||
}
|
||||
return filepath.Dir(a.cfg.LogPath)
|
||||
}
|
||||
|
||||
// loadOrCreateSSHHostKey loads an RSA private key from PEM or generates and saves one
|
||||
func (a *App) loadOrCreateSSHHostKey(path string) (ssh.Signer, error) {
|
||||
if b, err := os.ReadFile(path); err == nil {
|
||||
// Try parse PEM private key
|
||||
block, _ := pem.Decode(b)
|
||||
if block != nil && strings.Contains(block.Type, "PRIVATE KEY") {
|
||||
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
|
||||
return ssh.NewSignerFromKey(key)
|
||||
}
|
||||
if k2, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
|
||||
return ssh.NewSignerFromKey(k2)
|
||||
}
|
||||
}
|
||||
// If parse failed, fall through to generate new and overwrite
|
||||
}
|
||||
// Generate new RSA key and persist to PEM
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pemBlock := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||
if err := os.WriteFile(path, pem.EncodeToMemory(pemBlock), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ssh.NewSignerFromKey(key)
|
||||
}
|
||||
|
||||
// GetOrCreateTLSCertificate loads a TLS cert/key if provided or generates a self-signed pair
|
||||
// in the data directory (next to LogPath) and returns the tls.Certificate and the file paths used.
|
||||
func (a *App) GetOrCreateTLSCertificate() (tls.Certificate, string, string, error) {
|
||||
// If custom cert/key provided, try load them
|
||||
if a.cfg.Certificates.TLSCertPath != "" && a.cfg.Certificates.TLSKeyPath != "" {
|
||||
cert, err := tls.LoadX509KeyPair(a.cfg.Certificates.TLSCertPath, a.cfg.Certificates.TLSKeyPath)
|
||||
return cert, a.cfg.Certificates.TLSCertPath, a.cfg.Certificates.TLSKeyPath, err
|
||||
}
|
||||
// Else generate or reuse self-signed in data dir
|
||||
dir := a.dataDir()
|
||||
certPath := filepath.Join(dir, "tls_cert.pem")
|
||||
keyPath := filepath.Join(dir, "tls_key.pem")
|
||||
if _, err := os.Stat(certPath); err == nil {
|
||||
if _, err2 := os.Stat(keyPath); err2 == nil {
|
||||
cert, err3 := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
return cert, certPath, keyPath, err3
|
||||
}
|
||||
}
|
||||
// create self-signed cert
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
serial, _ := rand.Int(rand.Reader, big.NewInt(1<<62))
|
||||
tmpl := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{CommonName: "honeypot.local"},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().AddDate(5, 0, 0),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
certOut := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
|
||||
keyOut := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
|
||||
if err := os.WriteFile(certPath, certOut, 0600); err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
if err := os.WriteFile(keyPath, keyOut, 0600); err != nil {
|
||||
return tls.Certificate{}, "", "", err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(certOut, keyOut)
|
||||
return cert, certPath, keyPath, err
|
||||
}
|
||||
|
||||
// logSSHEvent is a helper to log SSH-specific events with consistent metadata
|
||||
func (a *App) logSSHEvent(sessionID, remote, eventType string, details map[string]string) {
|
||||
if details == nil {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoggerFunc is a callback for emitting service records back to the app layer.
|
||||
type LoggerFunc func(Record)
|
||||
|
||||
// Handler is a per-connection handler signature.
|
||||
type Handler func(net.Conn)
|
||||
|
||||
// Now returns UTC time for records.
|
||||
func Now() time.Time { return time.Now().UTC() }
|
||||
|
||||
// remote helpers (scoped to services package)
|
||||
func remoteIP(addr string) string {
|
||||
h, _, _ := net.SplitHostPort(addr)
|
||||
return h
|
||||
}
|
||||
|
||||
func remotePort(addr string) string {
|
||||
_, p, _ := net.SplitHostPort(addr)
|
||||
return p
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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)}})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewFTPHandler(log LoggerFunc) Handler {
|
||||
return func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
_, _ = conn.Write([]byte("220 Welcome to FTP Server\r\n"))
|
||||
conn.SetDeadline(time.Now().Add(5 * 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: "ftp", Details: map[string]string{"event":"username_attempt","username":username}})
|
||||
_, _ = conn.Write([]byte("331 Password required for "+username+"\r\n"))
|
||||
case "PASS":
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ftp", Details: map[string]string{"event":"password_attempt","username":username,"password":arg}})
|
||||
_, _ = conn.Write([]byte("530 Login incorrect\r\n"))
|
||||
case "QUIT":
|
||||
_, _ = conn.Write([]byte("221 Goodbye\r\n")); return
|
||||
default:
|
||||
_, _ = conn.Write([]byte("502 Command not implemented\r\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewGenericEchoHandler(log LoggerFunc) Handler {
|
||||
return func(c net.Conn) {
|
||||
defer c.Close()
|
||||
remote := c.RemoteAddr().String()
|
||||
c.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
_, _ = c.Write([]byte("220 Welcome to service\r\n"))
|
||||
r := bufio.NewReader(c)
|
||||
var b strings.Builder
|
||||
for i := 0; i < 10; i++ {
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil { break }
|
||||
b.WriteString(line)
|
||||
_, _ = c.Write([]byte("ACK\r\n"))
|
||||
}
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "generic", Details: map[string]string{"lines": strconv.Itoa(strings.Count(b.String(), "\n"))}, RawPayload: b.String()})
|
||||
}
|
||||
}
|
||||
|
||||
func NewGenericBannerHandler(banner string, log LoggerFunc) Handler {
|
||||
return func(c net.Conn) {
|
||||
defer c.Close()
|
||||
remote := c.RemoteAddr().String()
|
||||
_, _ = c.Write([]byte(fmt.Sprintf("%s\r\n", banner)))
|
||||
c.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
buf := make([]byte, 4096)
|
||||
n, _ := c.Read(buf)
|
||||
payload := strings.TrimSpace(string(buf[:n]))
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: banner, Details: map[string]string{"read_bytes": strconv.Itoa(n)}, RawPayload: payload})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewIMAPHandler(log LoggerFunc) Handler {
|
||||
return func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
_, _ = conn.Write([]byte("* OK IMAP4rev1 Service Ready\r\n"))
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" { continue }
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 { continue }
|
||||
tag := parts[0]
|
||||
cmd := strings.ToUpper(parts[1])
|
||||
switch cmd {
|
||||
case "LOGIN":
|
||||
if len(parts) >= 4 {
|
||||
user := strings.Trim(parts[2], "\"")
|
||||
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}})
|
||||
}
|
||||
_, _ = conn.Write([]byte(tag + " NO LOGIN failed\r\n"))
|
||||
case "CAPABILITY":
|
||||
_, _ = conn.Write([]byte("* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n"))
|
||||
_, _ = conn.Write([]byte(tag + " OK CAPABILITY completed\r\n"))
|
||||
case "LOGOUT":
|
||||
_, _ = conn.Write([]byte("* BYE IMAP4rev1 Server logging out\r\n"))
|
||||
_, _ = conn.Write([]byte(tag + " OK LOGOUT completed\r\n"))
|
||||
return
|
||||
default:
|
||||
_, _ = conn.Write([]byte(tag + " BAD Command not recognized\r\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"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"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewMySQLHandler(log LoggerFunc) Handler {
|
||||
return func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
// Simple handshake prefix
|
||||
handshake := []byte{0x4a,0x00,0x00,0x00,0x0a}
|
||||
handshake = append(handshake, []byte("5.7.34-0ubuntu0.18.04.1\x00")...)
|
||||
handshake = append(handshake, []byte{0x01,0x00,0x00,0x00, 0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0, 0x00, 0xff,0xf7, 0x08, 0x02,0x00, 0x0f,0x80, 0x15}...)
|
||||
_, _ = conn.Write(handshake)
|
||||
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err == nil && n > 4 {
|
||||
payload := buf[4:n]
|
||||
if len(payload) > 32 {
|
||||
us := 32
|
||||
ue := us
|
||||
for ue < len(payload) && payload[ue] != 0 { ue++ }
|
||||
if ue < len(payload) {
|
||||
username := string(payload[us:ue])
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "mysql", Details: map[string]string{"event":"auth_attempt","username":username}})
|
||||
}
|
||||
}
|
||||
}
|
||||
// error packet
|
||||
errorPacket := []byte{0x24,0x00,0x00,0x01, 0xff, 0x10,0x04, 0x23,0x48,0x59,0x30,0x30,0x30}
|
||||
errorPacket = append(errorPacket, []byte("Access denied for user")...)
|
||||
_, _ = conn.Write(errorPacket)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewPostgreSQLHandler(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 >= 8 {
|
||||
length := int(buf[0])<<24 | int(buf[1])<<16 | int(buf[2])<<8 | int(buf[3])
|
||||
if length > 8 && n >= length {
|
||||
params := string(buf[8:length])
|
||||
if strings.Contains(params, "user") {
|
||||
re := regexp.MustCompile(`user\x00([^\x00]+)`)
|
||||
m := re.FindStringSubmatch(params)
|
||||
if len(m) > 1 {
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "postgresql", Details: map[string]string{"event":"auth_attempt","username":m[1]}})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// request cleartext password
|
||||
authReq := []byte{0x52, 0x00,0x00,0x00,0x08, 0x00,0x00,0x00,0x03}
|
||||
_, _ = conn.Write(authReq)
|
||||
n, err = conn.Read(buf)
|
||||
if err == nil && n > 5 && buf[0] == 0x70 {
|
||||
length := int(buf[1])<<24 | int(buf[2])<<16 | int(buf[3])<<8 | int(buf[4])
|
||||
if length > 5 && n >= length {
|
||||
password := string(buf[5:length-1])
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "postgresql", Details: map[string]string{"event":"password_attempt","password":password}})
|
||||
}
|
||||
}
|
||||
// error
|
||||
errMsg := []byte{0x45, 0x00,0x00,0x00,0x26}
|
||||
errMsg = append(errMsg, []byte("SFATAL\x00C28P01\x00Mpassword authentication failed\x00\x00")...)
|
||||
_, _ = conn.Write(errMsg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"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})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package services
|
||||
|
||||
import "time"
|
||||
|
||||
// Record is the event shape emitted by service handlers.
|
||||
// The app layer will translate this into its own Record type.
|
||||
type Record struct {
|
||||
Timestamp time.Time
|
||||
RemoteAddr string
|
||||
RemotePort string
|
||||
Service string
|
||||
Details map[string]string
|
||||
RawPayload string
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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,20 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewSIPHandler(log LoggerFunc) Handler {
|
||||
return func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
conn.SetDeadline(time.Now().Add(8 * time.Second))
|
||||
r := bufio.NewReader(conn)
|
||||
line, _ := r.ReadString('\n')
|
||||
if strings.TrimSpace(line) == "" { return }
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "sip", Details: map[string]string{"event":"first_line","first_line":strings.TrimSpace(line)}, RawPayload: line})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewSMTPHandler(log LoggerFunc) Handler {
|
||||
return func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
_, _ = conn.Write([]byte("220 mail.example.com ESMTP Postfix\r\n"))
|
||||
conn.SetDeadline(time.Now().Add(5 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
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 "HELO":
|
||||
_, _ = conn.Write([]byte("250 mail.example.com\r\n"))
|
||||
case "EHLO":
|
||||
_, _ = conn.Write([]byte("250-mail.example.com\r\n250-AUTH PLAIN LOGIN\r\n250 OK\r\n"))
|
||||
case "AUTH":
|
||||
fields := strings.Fields(arg)
|
||||
if len(fields)>0 && strings.ToUpper(fields[0])=="PLAIN" && len(fields)>1 {
|
||||
if b, err := base64.StdEncoding.DecodeString(fields[1]); err==nil {
|
||||
parts := strings.Split(string(b), "\x00")
|
||||
if len(parts)>=3 {
|
||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smtp", Details: map[string]string{"event":"auth_attempt","method":"PLAIN","username":parts[1],"password":parts[2]}})
|
||||
}
|
||||
}
|
||||
}
|
||||
_, _ = conn.Write([]byte("535 Authentication failed\r\n"))
|
||||
case "MAIL","RCPT":
|
||||
_, _ = conn.Write([]byte("250 OK\r\n"))
|
||||
case "DATA":
|
||||
_, _ = conn.Write([]byte("354 End data with <CR><LF>.<CR><LF>\r\n"))
|
||||
case "QUIT":
|
||||
_, _ = conn.Write([]byte("221 Bye\r\n")); return
|
||||
default:
|
||||
_, _ = conn.Write([]byte("502 Command not implemented\r\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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)}})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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_7.9p1 Ubuntu-10",
|
||||
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()),
|
||||
}})
|
||||
return nil, fmt.Errorf("permission denied")
|
||||
},
|
||||
}
|
||||
cfg.AddHostKey(signer)
|
||||
|
||||
_ = conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||
|
||||
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
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
for ch := range chans {
|
||||
_ = 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)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewTelnetHandler(log LoggerFunc) Handler {
|
||||
return func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
remote := conn.RemoteAddr().String()
|
||||
_, _ = conn.Write([]byte("\r\nUbuntu 20.04.3 LTS\r\n\r\nlogin: "))
|
||||
conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||
scanner := bufio.NewScanner(conn)
|
||||
var username string
|
||||
expectingPassword := false
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
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}})
|
||||
_, _ = conn.Write([]byte("Password: "))
|
||||
expectingPassword = true
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
-4
@@ -8,17 +8,50 @@
|
||||
},
|
||||
"services": {
|
||||
"http": true,
|
||||
"https": true,
|
||||
"ssh": true,
|
||||
"smb": false,
|
||||
"sip": false,
|
||||
"vnc": false,
|
||||
"ftp": true,
|
||||
"smtp": true,
|
||||
"pop3": true,
|
||||
"imap": true,
|
||||
"telnet": true,
|
||||
"mysql": true,
|
||||
"postgresql": true,
|
||||
"redis": true,
|
||||
"mongodb": true,
|
||||
"rdp": true,
|
||||
"smb": true,
|
||||
"sip": true,
|
||||
"vnc": true,
|
||||
"dns": true,
|
||||
"snmp": true,
|
||||
"ldap": true,
|
||||
"generic": []
|
||||
},
|
||||
"ports": {
|
||||
"http": 8080,
|
||||
"https": 8443,
|
||||
"ssh": 2222,
|
||||
"ftp": 2121,
|
||||
"smtp": 2525,
|
||||
"pop3": 1110,
|
||||
"imap": 1143,
|
||||
"telnet": 2323,
|
||||
"mysql": 3306,
|
||||
"postgresql": 5432,
|
||||
"redis": 6379,
|
||||
"mongodb": 27017,
|
||||
"rdp": 3389,
|
||||
"smb": 4450,
|
||||
"sip": 5060,
|
||||
"vnc": 5900
|
||||
"vnc": 5900,
|
||||
"dns": 5353,
|
||||
"snmp": 1161,
|
||||
"ldap": 3890
|
||||
},
|
||||
"certificates": {
|
||||
"ssh_host_key_path": "",
|
||||
"tls_cert_path": "",
|
||||
"tls_key_path": ""
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -1,5 +1,7 @@
|
||||
{"timestamp":"2025-09-27T21:33:32.955904584Z","remote_addr":"127.0.0.1","remote_port":"44558","service":"ssh","details":{"auth_attempts":"0","duration_sec":"0.09","error":"read tcp 127.0.0.1:2222-\u003e127.0.0.1:44558: read: connection reset by peer","last_password":"","last_username":"","session_id":"18694132175c68ec"},"raw_payload":"session_end: map[auth_attempts:0 duration_sec:0.09 error:read tcp 127.0.0.1:2222-\u003e127.0.0.1:44558: read: connection reset by peer last_password: last_username: session_id:18694132175c68ec]"}
|
||||
{"timestamp":"2025-09-27T21:34:21.343267479Z","remote_addr":"127.0.0.1","remote_port":"59260","service":"ssh","details":{"attempt":"1","client":"SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14","password":"asxcbvc","session_id":"1869413c69661475","username":"bob"},"raw_payload":"auth_attempt: map[attempt:1 client:SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 password:asxcbvc session_id:1869413c69661475 username:bob]"}
|
||||
{"timestamp":"2025-09-27T21:34:24.503353375Z","remote_addr":"127.0.0.1","remote_port":"59260","service":"ssh","details":{"attempt":"2","client":"SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14","password":"ascojmnrhe[pom","session_id":"1869413c69661475","username":"bob"},"raw_payload":"auth_attempt: map[attempt:2 client:SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14 password:ascojmnrhe[pom session_id:1869413c69661475 username:bob]"}
|
||||
{"timestamp":"2025-09-27T21:34:30.534724871Z","remote_addr":"127.0.0.1","remote_port":"59260","service":"ssh","details":{"auth_attempts":"2","duration_sec":"13.35","error":"[ssh: no auth passed yet, permission denied, permission denied]","last_password":"ascojmnrhe[pom","last_username":"bob","session_id":"1869413c69661475"},"raw_payload":"session_end: map[auth_attempts:2 duration_sec:13.35 error:[ssh: no auth passed yet, permission denied, permission denied] last_password:ascojmnrhe[pom last_username:bob session_id:1869413c69661475]"}
|
||||
{"timestamp":"2025-09-28T06:17:13.052200075Z","remote_addr":"127.0.0.1","remote_port":"59496","service":"ssh","details":{"auth_attempts":"0","duration_sec":"0.19","error":"read tcp 127.0.0.1:2222-\u003e127.0.0.1:59496: read: connection reset by peer","last_password":"","last_username":"","session_id":"18695dc5a178d6df"},"raw_payload":"session_end: map[auth_attempts:0 duration_sec:0.19 error:read tcp 127.0.0.1:2222-\u003e127.0.0.1:59496: read: connection reset by peer last_password: last_username: session_id:18695dc5a178d6df]"}
|
||||
{"timestamp":"2025-09-28T06:47:07.900746418Z","remote_addr":"127.0.0.1","remote_port":"44382","service":"ssh","details":{"auth_attempts":"0","duration_sec":"0.06","error":"read tcp 127.0.0.1:2222-\u003e127.0.0.1:44382: read: connection reset by peer","event":"session_end","last_password":"","last_username":"","session_id":"18695f678e2752f9"}}
|
||||
{"timestamp":"2025-09-28T07:04:09.262727385Z","remote_addr":"127.0.0.1","remote_port":"38042","service":"ssh","details":{"auth_attempts":"0","duration_sec":"0.22","error":"read tcp 127.0.0.1:2222-\u003e127.0.0.1:38042: read: connection reset by peer","event":"session_end","last_password":"","last_username":"","session_id":"1869605552ec2e17"}}
|
||||
{"timestamp":"2025-09-28T07:04:22.008795861Z","remote_addr":"127.0.0.1","remote_port":"60472","service":"ssh","details":{"attempt":"1","client":"SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14","event":"auth_attempt","password":"ascasc","username":"root"}}
|
||||
{"timestamp":"2025-09-28T07:04:25.850146625Z","remote_addr":"127.0.0.1","remote_port":"60472","service":"ssh","details":{"auth_attempts":"1","duration_sec":"6.60","error":"read tcp 127.0.0.1:2222-\u003e127.0.0.1:60472: use of closed network connection","event":"session_end","last_password":"ascasc","last_username":"root","session_id":"18696057b35a4405"}}
|
||||
{"timestamp":"2025-09-28T07:04:34.552887861Z","remote_addr":"127.0.0.1","remote_port":"47432","service":"ssh","details":{"attempt":"1","client":"SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.14","event":"auth_attempt","password":"dhfgh567","username":"root"}}
|
||||
{"timestamp":"2025-09-28T07:05:10.090265232Z","remote_addr":"127.0.0.1","remote_port":"47432","service":"ssh","details":{"auth_attempts":"1","duration_sec":"37.47","error":"read tcp 127.0.0.1:2222-\u003e127.0.0.1:47432: use of closed network connection","event":"session_end","last_password":"dhfgh567","last_username":"root","session_id":"1869605ad02adcea"}}
|
||||
{"timestamp":"2025-09-28T07:05:35.135530797Z","remote_addr":"::1","remote_port":"34470","service":"http","details":{"method":"GET","proto":"HTTP/1.1","url":"/","user_agent":"curl/8.5.0"}}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAu9r5vDFVrDMeAbM4sL5Gs612YJ/GXy3f2x1KokqGLqcD5FHf
|
||||
fFoi/0CIbIFIKfx/Fw1M9mgBGz8GrDjk5VkALIlBuC0qYX5z1+akNUO8VYK9YgUr
|
||||
DinDOBhZP7V+Q4rEcjwT4XOtMmX0+aIpkPXpmyqsE8Ol5Lw6OWr8QWi67/xodO08
|
||||
vVN1pKzewdsidrqtcf2P+xJTC8LB0OVNRfL9+y7UxlCJvEnE7voEMmn3jFxBdUYu
|
||||
3nSwIhd/rolx2G45nyS1rP18z/dt7c2F28i6/ULNpBY8O40u+IfxpV7uSqKCsGdQ
|
||||
nuB6K6/t3OSmY//ELtSIRtF3h0DJLz9OVUmWLQIDAQABAoIBAAKdbarbeSxUFg3m
|
||||
j0UvA4xh+GBgusV2hNkLQ0iI8ZgA/Ue2hgxjCQuRkTuzp+TlSIIOsfOfeKwG7BJb
|
||||
UtWzIb1STVwsIo6A+4JkCeiTPNy9av7oldeRdnp+svCuJc74jNDyfbM+jM/t8/VQ
|
||||
hUl+PJ1nuUWZYcaggiMzeeI8UXq87dw59W7C18acsQdFqQ/lXxHMh51+KxjbjxkX
|
||||
gHqA4Mf+4I1A1A5DfpUt9Vv9EKwWU/snWYOCktCo8eYCWPYOPrm4GxGbaQcPTEKx
|
||||
V9Q5SQmzb6QvXmboafjiVZE1cxxz3Uf8RH4CJjn+MMoxMENZiO5bGgwSJ+BrqPi5
|
||||
G0CRKOcCgYEA+b/NpuA0XJDzJCE5Sfv80IStQLiVci5AgDHjxSQUt3c/Zb+e8YIZ
|
||||
cQ+VJnNdhS8YQWiz8ngvQgy8Dak1jVMRIN5Kwjw/5WQ8Cg5fuVGgK+1Lasxj0gER
|
||||
RI5EDGcHAGlairsM1rEKtQ32yt446cgDs3bNFTdnTY9MSpiwFmiB0JcCgYEAwI6a
|
||||
4yCgO8LqzCUfp67kgRIn3NME57+oRtUjC7FtmygZH4NGNBF4QVmTRB+uBWFNpY4z
|
||||
eyAbtsqD/l9+DkgxnaaZSBh6ILqCacC0GlRfbIDv8n/sbmwkdZxU4uyV+vjAKaNy
|
||||
UcExYzwRj4Rf/559Kd1bgXKqLLzcBIGNvo50o9sCgYEAuNQGqSoh9iNbnXBtCmDP
|
||||
b63Q3iX8i5zJJVZGn14das14gJ94THkgxPhoRCV6n5cD11xaV+Yz6yirf1yrgiRo
|
||||
d6+rGeYmz2gHutV6aBaNeBTMDISolwEtO1Qh7h/NIbPWSvc1ACnTp7xm2Snuaq0Y
|
||||
eBdCnSH8dHzJVCd9oYfEEo8CgYB86YWMpZOMcQuD1ulMC+Zr3G1DkRhzhh8RpB4R
|
||||
7c2eg0qY6L3X9SU/r24bGTn4f4CxTygSTWftEj7B+wx5E1gsXvC8ljRTmuoS1FGB
|
||||
aw5kAtilRVsI3tpf+UQP2U4J+ugdmswEQQFa0JLLuSHVXujvCYvc05eVYgaQXcKn
|
||||
xR3hVQKBgH75U8abmbbH7v6osTmWJBSqRbHtAjjcuJ70ApF11QY3sMdllmWl9zXS
|
||||
WYlUXLOFbbwlR8KfXPFaCIBas+N5l4eT6jYbSdcnWTdbqqc1EPf3oj92m1uxImxE
|
||||
Xwyn/O1MdhLudeP3qpRhR10hXKdsuq/+7k3nR2SZd8CwYCDczDu2
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC6TCCAdGgAwIBAgIIMQyP/0c+PXUwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UE
|
||||
AxMOaG9uZXlwb3QubG9jYWwwHhcNMjUwOTI4MDYwMzQzWhcNMzAwOTI4MDcwMzQz
|
||||
WjAZMRcwFQYDVQQDEw5ob25leXBvdC5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAMSsYJKMn5rdvzMZYRUsuAeC9t9bgID4Q2O+GbOdNtBCSsTq
|
||||
SGQTWyjzaudq3ODpZGJhft4ZzaqeRo9Ms9JOdmwAhG21Gfkv0Xk1J1od16/NBx89
|
||||
0F4Sba5pclamYxxhcx57sLYm4bUFGztbNYUbCMripBStKG9tRXExn6TMGi5kdanN
|
||||
kg+CPPrFs+a/qfhancLRPHEh1oCFirYZRTrt9n8N/EbppYcBKBuQ+STIT1XFYGrX
|
||||
ZN3RsrN7hEwR+qvWWYSf/ukpW8L7Od2fI0LoFHn9jhu7Z/qNutgLqs6f0+8BGlSC
|
||||
5vp4kKTAO2crg6lgAzc3EUh7pWyaZfpz5zpsVKMCAwEAAaM1MDMwDgYDVR0PAQH/
|
||||
BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZI
|
||||
hvcNAQELBQADggEBADNLWYnULwfAGqYcWF0h32APoOIzm+py/GTEcsW279oZ++09
|
||||
hvkf9xdhXKxs4NXCdUsMC2h//l5P/rlVM5OWTB2RFDDPMtAW/dpNK7YL0Q7HMUI8
|
||||
zrVQW4Gi+hLuQ4XohN/YijKbJQSvle5nU02AU6dqHNUFs5J4AhQIVlIHCFz/sDuh
|
||||
46WvY9dqv/3mvOuXDmXIvuv2vFD3KTGApHQ+SnRQGveczdleTQizEWzd/GmZsVOH
|
||||
Igk9ZJEiRhWLZCWa3taTcW/ZOn9QbGAHO8FP8O8p/j6hrqcReW4LgN0BKnVbdMbY
|
||||
gK+EpjI18uF98J8ZDwD4+CQsGt+x2Sm53Ot8z8g=
|
||||
-----END CERTIFICATE-----
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAxKxgkoyfmt2/MxlhFSy4B4L231uAgPhDY74Zs5020EJKxOpI
|
||||
ZBNbKPNq52rc4OlkYmF+3hnNqp5Gj0yz0k52bACEbbUZ+S/ReTUnWh3Xr80HHz3Q
|
||||
XhJtrmlyVqZjHGFzHnuwtibhtQUbO1s1hRsIyuKkFK0ob21FcTGfpMwaLmR1qc2S
|
||||
D4I8+sWz5r+p+FqdwtE8cSHWgIWKthlFOu32fw38RumlhwEoG5D5JMhPVcVgatdk
|
||||
3dGys3uETBH6q9ZZhJ/+6Slbwvs53Z8jQugUef2OG7tn+o262Auqzp/T7wEaVILm
|
||||
+niQpMA7ZyuDqWADNzcRSHulbJpl+nPnOmxUowIDAQABAoIBAERBsw6JgYcE+Kuq
|
||||
Xjg0GfZ5bGaeYh3gi7rdKhxdLr3elAZ9bPxWf2fZ+zsvqlLgjXdbcOVyPR++6Kwp
|
||||
KauOkajwEQXmOYpzHxca7HppKwcXeCZOlLdhW/GRJR6Phow+Ae8NbIn7OpBRol7a
|
||||
S9vTQxzuxZVrd0IcwWIEn+xY7ak1lnNTzWhjyIyN5HybGEU3lFjPMXRmj5X0P7GS
|
||||
8HcBsl4jFlpDwstucqDxmrm7Fr7EVo3tIPEcGdNPqKvnZ44Ravb5/jVW0C/KvjsM
|
||||
5lZmIGEKIQjyCpa4Dd2fkLvb4b9n9+hxdVRDNOYW7AnCBWZZwlJj8zwzufVXSTjx
|
||||
Av3uA7kCgYEA682lXPNR9Om3mxz76mj1+7JYkykDQ1sE6CwHxTAQjBNS6QUAEW+n
|
||||
meurM+3LmNldWixhLcJgH7c9tANyN1b6I1KWouK6gFdSuINvYIVDHvNtKtrne+vX
|
||||
LUuX1pfQvz5Oq9mqJXU/+WD5yqv+1rxsleNJF6TzXp40caUQpJGmSbcCgYEA1YS/
|
||||
BObe1uQ+OUPdsWXF4hGDEcHbEO3WBV+YZL/Hjclee/jafu3iA1aaA+QvQTJp4L0W
|
||||
Kik4NP/k2j/d3Tl0PFBY66kX3u/F1gzW1xB6Ql/aAdxoat9sNKLSrxbwPj3DCm11
|
||||
ypWAV1IMfc1UGcsMYjHbYr/lWv9Sa5df3LAIfHUCgYEAxJaoXLwHAfawOkOJyr5D
|
||||
BdqEefvhWpBRoPbEa6NMyFt77gVbLy41Pt/51ctUyFO/vmPtiObamNZ+PMv6tyRu
|
||||
WnCKYbZA4qrqriX1/zRa5zzvMKFcCDZxKLQzHJdpU2ew0xke/yendFNjLZMDXSeu
|
||||
J7Bbybidpa6j7nM9UtaTGjUCgYBJCm8Z6yxh0JRknI2zCMCntBvlMC6TXPjwv6Hv
|
||||
HRfTrgYPXLLJ5vCA+dgX6rArmmZTxftWEuGyZ7NO4bgw3F2h4E56105eKiHANoYr
|
||||
7ewU1ptKNa7WmHV4kBaIZM1sTU5yO72mvnu34054novdgvNKiAmnf0OjXGJCgfb+
|
||||
FI3inQKBgQDAQ/rTu7acMZAXGMlRHI/rJaXDnS705WFmXTYYgpY+62pi/aPZ1W8w
|
||||
9FtMuFoHtiwlV6jwKg+3GkZNnXKsuAc07dYCgpxMxd5uFcP5CHl/AwOZ8xe7kzwl
|
||||
sJuWf3wfRTHBwry+sgPF450iaHrYVzsyKyanafm5L1hNzAseEhAZyg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
Reference in New Issue
Block a user