update services to separate files

This commit is contained in:
2025-09-28 08:06:05 +01:00
parent 037f97b528
commit 662824d50b
26 changed files with 940 additions and 650 deletions
+15
View File
@@ -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
-619
View File
@@ -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
View File
@@ -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 {
+26
View File
@@ -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
}
+21
View File
@@ -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)}})
}
}
}
+42
View File
@@ -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"))
}
}
}
}
+41
View File
@@ -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})
}
}
+44
View File
@@ -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"))
}
}
}
}
+22
View File
@@ -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})
}
}
+22
View File
@@ -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"))
}
}
+37
View File
@@ -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)
}
}
+39
View File
@@ -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"))
}
}
}
}
+47
View File
@@ -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)
}
}
+22
View File
@@ -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})
}
}
+14
View File
@@ -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
}
+34
View File
@@ -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"))
}
}
}
+20
View File
@@ -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})
}
}
+51
View File
@@ -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"))
}
}
}
}
+21
View File
@@ -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)}})
}
}
}
+81
View File
@@ -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)
}
+34
View File
@@ -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
View File
@@ -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
View File
@@ -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"}}
+27
View File
@@ -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-----
+18
View File
@@ -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
View File
@@ -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-----