Created
September 19, 2025 21:42
-
-
Save Splinters-io/268e76eb7cca5fcead0dd9c7d09e638c to your computer and use it in GitHub Desktop.
MannaWeb.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "bytes" | |
| "context" | |
| "crypto/tls" | |
| "encoding/hex" | |
| "encoding/json" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net" | |
| "net/http" | |
| "os" | |
| "os/signal" | |
| "regexp" | |
| "strings" | |
| "sync" | |
| "syscall" | |
| "time" | |
| "unicode/utf8" | |
| ) | |
| type ConnectionTracker struct { | |
| mu sync.RWMutex | |
| connections map[string]*ConnectionInfo | |
| } | |
| type ConnectionInfo struct { | |
| StartTime time.Time | |
| TLSVersion string | |
| CipherSuite string | |
| ClientCert string | |
| RequestCount int | |
| } | |
| var ( | |
| connTracker = &ConnectionTracker{connections: make(map[string]*ConnectionInfo)} | |
| suspiciousPatterns = []*regexp.Regexp{ | |
| regexp.MustCompile(`(?i)\.\.[\\/]`), | |
| regexp.MustCompile(`(?i)(union|select|drop|insert|delete)\s+`), | |
| regexp.MustCompile(`(?i)<script[^>]*>`), | |
| regexp.MustCompile(`(?i)eval\s*\(`), | |
| regexp.MustCompile(`(?i)base64_decode`), | |
| } | |
| maxBodySize = int64(1024 * 1024) // 1MB | |
| slackWebhook string | |
| logFile *os.File | |
| ) | |
| type SlackPayload struct { | |
| Text string `json:"text"` | |
| } | |
| func sendSlackAlert(message string) { | |
| if slackWebhook == "" { | |
| return | |
| } | |
| payload := SlackPayload{Text: message} | |
| jsonData, _ := json.Marshal(payload) | |
| go func() { | |
| resp, err := http.Post(slackWebhook, "application/json", bytes.NewBuffer(jsonData)) | |
| if err != nil { | |
| log.Printf("SLACK_ERROR: %v", err) | |
| return | |
| } | |
| resp.Body.Close() | |
| }() | |
| } | |
| func logToFile(message string) { | |
| if logFile != nil { | |
| logFile.WriteString(fmt.Sprintf("%s %s\n", time.Now().Format(time.RFC3339), message)) | |
| logFile.Sync() | |
| } | |
| } | |
| func (ct *ConnectionTracker) addConnection(remoteAddr string, tlsState *tls.ConnectionState) { | |
| ct.mu.Lock() | |
| defer ct.mu.Unlock() | |
| info := &ConnectionInfo{ | |
| StartTime: time.Now(), | |
| } | |
| if tlsState != nil { | |
| info.TLSVersion = getTLSVersionString(tlsState.Version) | |
| info.CipherSuite = tls.CipherSuiteName(tlsState.CipherSuite) | |
| if len(tlsState.PeerCertificates) > 0 { | |
| info.ClientCert = fmt.Sprintf("CN=%s", tlsState.PeerCertificates[0].Subject.CommonName) | |
| } | |
| } | |
| ct.connections[remoteAddr] = info | |
| log.Printf("TLS_CONNECT Remote=%s TLS=%s Cipher=%s ClientCert=%s", | |
| remoteAddr, info.TLSVersion, info.CipherSuite, info.ClientCert) | |
| } | |
| func (ct *ConnectionTracker) incrementRequest(remoteAddr string) { | |
| ct.mu.Lock() | |
| defer ct.mu.Unlock() | |
| if info, exists := ct.connections[remoteAddr]; exists { | |
| info.RequestCount++ | |
| } | |
| } | |
| func getTLSVersionString(version uint16) string { | |
| switch version { | |
| case tls.VersionTLS10: | |
| return "TLS1.0" | |
| case tls.VersionTLS11: | |
| return "TLS1.1" | |
| case tls.VersionTLS12: | |
| return "TLS1.2" | |
| case tls.VersionTLS13: | |
| return "TLS1.3" | |
| default: | |
| return fmt.Sprintf("Unknown(%d)", version) | |
| } | |
| } | |
| func getRealClientIP(r *http.Request) string { | |
| // Check X-Forwarded-For | |
| if xff := r.Header.Get("X-Forwarded-For"); xff != "" { | |
| ips := strings.Split(xff, ",") | |
| return strings.TrimSpace(ips[0]) | |
| } | |
| // Check X-Real-IP | |
| if xri := r.Header.Get("X-Real-IP"); xri != "" { | |
| return xri | |
| } | |
| // Fallback to RemoteAddr | |
| host, _, _ := net.SplitHostPort(r.RemoteAddr) | |
| return host | |
| } | |
| func isBinaryContent(data []byte) bool { | |
| if len(data) == 0 { | |
| return false | |
| } | |
| // Check for NULL bytes or non-UTF8 content | |
| for i, b := range data { | |
| if b == 0 { | |
| return true | |
| } | |
| if i > 512 { // Check first 512 bytes | |
| break | |
| } | |
| } | |
| return !utf8.Valid(data) | |
| } | |
| func checkSuspiciousPatterns(data string) []string { | |
| var matches []string | |
| for _, pattern := range suspiciousPatterns { | |
| if pattern.MatchString(data) { | |
| matches = append(matches, pattern.String()) | |
| } | |
| } | |
| return matches | |
| } | |
| func logRequest(r *http.Request) { | |
| start := time.Now() | |
| realIP := getRealClientIP(r) | |
| // Track connection | |
| connTracker.incrementRequest(r.RemoteAddr) | |
| // Read body with size limit | |
| limitedBody := http.MaxBytesReader(nil, r.Body, maxBodySize) | |
| body, err := io.ReadAll(limitedBody) | |
| r.Body.Close() | |
| if err != nil { | |
| log.Printf("ERROR_BODY_READ Remote=%s Error=%v", r.RemoteAddr, err) | |
| body = []byte("[ERROR: Body too large or read failed]") | |
| } | |
| // Basic request info | |
| requestInfo := fmt.Sprintf("REQUEST: %s %s from %s (Real IP: %s) UA: %s", | |
| r.Method, r.URL.String(), r.RemoteAddr, realIP, r.UserAgent()) | |
| log.Printf("=== REQUEST START ===") | |
| log.Printf("METHOD=%s URL=%s PROTO=%s", r.Method, r.URL.String(), r.Proto) | |
| log.Printf("HOST=%s REMOTE=%s REAL_IP=%s", r.Host, r.RemoteAddr, realIP) | |
| log.Printf("USER_AGENT=%s", r.UserAgent()) | |
| log.Printf("REFERER=%s", r.Referer()) | |
| log.Printf("CONTENT_LENGTH=%d CONTENT_TYPE=%s", r.ContentLength, r.Header.Get("Content-Type")) | |
| // Log to file | |
| logToFile(requestInfo) | |
| // Log TLS info if available | |
| if r.TLS != nil { | |
| log.Printf("TLS_VERSION=%s CIPHER=%s", getTLSVersionString(r.TLS.Version), tls.CipherSuiteName(r.TLS.CipherSuite)) | |
| if len(r.TLS.PeerCertificates) > 0 { | |
| log.Printf("CLIENT_CERT=%s", r.TLS.PeerCertificates[0].Subject.CommonName) | |
| } | |
| } | |
| // Log all headers | |
| log.Printf("--- HEADERS ---") | |
| for name, values := range r.Header { | |
| for _, value := range values { | |
| log.Printf("HEADER: %s=%s", name, value) | |
| } | |
| } | |
| // Log body | |
| var bodyAlert string | |
| if len(body) > 0 { | |
| if isBinaryContent(body) { | |
| log.Printf("BODY_BINARY: %d bytes, hex=%s", len(body), hex.EncodeToString(body[:min(32, len(body))])) | |
| bodyAlert = fmt.Sprintf(" with %d bytes binary data", len(body)) | |
| } else { | |
| bodyStr := string(body) | |
| log.Printf("BODY_TEXT: %s", bodyStr) | |
| bodyAlert = fmt.Sprintf(" with body: %.100s", bodyStr) | |
| // Check for suspicious patterns | |
| suspicious := checkSuspiciousPatterns(bodyStr) | |
| if len(suspicious) > 0 { | |
| alertMsg := fmt.Sprintf("🚨 SECURITY ALERT: %s%s - Suspicious body patterns: %v", requestInfo, bodyAlert, suspicious) | |
| log.Printf("SECURITY_ALERT: Suspicious patterns detected: %v", suspicious) | |
| sendSlackAlert(alertMsg) | |
| logToFile(alertMsg) | |
| } | |
| } | |
| } | |
| // Check URL for suspicious patterns | |
| urlSuspicious := checkSuspiciousPatterns(r.URL.String()) | |
| if len(urlSuspicious) > 0 { | |
| alertMsg := fmt.Sprintf("🚨 SECURITY ALERT: %s%s - Suspicious URL patterns: %v", requestInfo, bodyAlert, urlSuspicious) | |
| log.Printf("SECURITY_ALERT: Suspicious URL patterns: %v", urlSuspicious) | |
| sendSlackAlert(alertMsg) | |
| logToFile(alertMsg) | |
| } | |
| // Send general alert for interesting requests | |
| if r.Method != "GET" || len(body) > 0 || r.URL.Path != "/" { | |
| alertMsg := fmt.Sprintf("📡 Traffic Alert: %s%s", requestInfo, bodyAlert) | |
| sendSlackAlert(alertMsg) | |
| } | |
| log.Printf("=== REQUEST END === (Duration: %v)", time.Since(start)) | |
| } | |
| func min(a, b int) int { | |
| if a < b { | |
| return a | |
| } | |
| return b | |
| } | |
| var serveFile string | |
| func handler(w http.ResponseWriter, r *http.Request) { | |
| logRequest(r) | |
| if serveFile != "" { | |
| http.ServeFile(w, r, serveFile) | |
| } else { | |
| w.WriteHeader(200) | |
| w.Write([]byte("OK")) | |
| } | |
| } | |
| func healthHandler(w http.ResponseWriter, r *http.Request) { | |
| w.WriteHeader(200) | |
| w.Write([]byte("HEALTHY")) | |
| } | |
| type ConnStateLogger struct { | |
| listener net.Listener | |
| } | |
| func (c *ConnStateLogger) Accept() (net.Conn, error) { | |
| conn, err := c.listener.Accept() | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &LoggingConn{Conn: conn}, nil | |
| } | |
| func (c *ConnStateLogger) Close() error { | |
| return c.listener.Close() | |
| } | |
| func (c *ConnStateLogger) Addr() net.Addr { | |
| return c.listener.Addr() | |
| } | |
| type LoggingConn struct { | |
| net.Conn | |
| logged bool | |
| } | |
| func (lc *LoggingConn) Read(b []byte) (int, error) { | |
| if !lc.logged { | |
| log.Printf("CONN_ESTABLISHED Remote=%s Local=%s", lc.RemoteAddr(), lc.LocalAddr()) | |
| lc.logged = true | |
| } | |
| return lc.Conn.Read(b) | |
| } | |
| func (lc *LoggingConn) Close() error { | |
| log.Printf("CONN_CLOSED Remote=%s", lc.RemoteAddr()) | |
| return lc.Conn.Close() | |
| } | |
| func main() { | |
| port := flag.String("port", "443", "Port") | |
| cert := flag.String("cert", "/etc/letsencrypt/live/YOURDOMAIN/fullchain.pem", "Cert file") | |
| key := flag.String("key", "/etc/letsencrypt/live/YOURDOMAIN/privkey.pem", "Key file") | |
| maxBody := flag.Int64("max-body", 1024*1024, "Max request body size in bytes") | |
| file := flag.String("file", "", "File to serve for all requests (optional)") | |
| webhook := flag.String("slack", "", "Slack webhook URL for alerts") | |
| logPath := flag.String("log", "", "Log file path (optional)") | |
| flag.Parse() | |
| maxBodySize = *maxBody | |
| serveFile = *file | |
| slackWebhook = *webhook | |
| // Setup log file | |
| if *logPath != "" { | |
| var err error | |
| logFile, err = os.OpenFile(*logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) | |
| if err != nil { | |
| log.Fatal("Failed to open log file:", err) | |
| } | |
| defer logFile.Close() | |
| } | |
| if _, err := os.Stat(*cert); os.IsNotExist(err) { | |
| log.Fatal("Cert file not found:", *cert) | |
| } | |
| if _, err := os.Stat(*key); os.IsNotExist(err) { | |
| log.Fatal("Key file not found:", *key) | |
| } | |
| // Setup routes | |
| http.HandleFunc("/", handler) | |
| http.HandleFunc("/health", healthHandler) | |
| // TLS config with connection state callback | |
| tlsConfig := &tls.Config{ | |
| MinVersion: tls.VersionTLS12, | |
| GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { | |
| // Log legacy protocol attempts | |
| if len(chi.SupportedVersions) > 0 { | |
| for _, version := range chi.SupportedVersions { | |
| if version < tls.VersionTLS12 { | |
| alertMsg := fmt.Sprintf("🔒 Legacy TLS attempt from %s - versions: %v", "unknown", chi.SupportedVersions) | |
| log.Printf("TLS_LEGACY_ATTEMPT: %s", alertMsg) | |
| sendSlackAlert(alertMsg) | |
| logToFile(alertMsg) | |
| break | |
| } | |
| } | |
| } | |
| cert, err := tls.LoadX509KeyPair(*cert, *key) | |
| if err != nil { | |
| log.Printf("TLS_HANDSHAKE_ERROR Error=%v", err) | |
| return nil, err | |
| } | |
| return &cert, nil | |
| }, | |
| } | |
| server := &http.Server{ | |
| Addr: ":" + *port, | |
| TLSConfig: tlsConfig, | |
| ReadTimeout: 30 * time.Second, | |
| WriteTimeout: 30 * time.Second, | |
| IdleTimeout: 120 * time.Second, | |
| ConnState: func(conn net.Conn, state http.ConnState) { | |
| switch state { | |
| case http.StateNew: | |
| log.Printf("HTTP_CONN_NEW Remote=%s", conn.RemoteAddr()) | |
| case http.StateActive: | |
| if tlsConn, ok := conn.(*tls.Conn); ok { | |
| state := tlsConn.ConnectionState() | |
| connTracker.addConnection(conn.RemoteAddr().String(), &state) | |
| } | |
| case http.StateClosed: | |
| log.Printf("HTTP_CONN_CLOSED Remote=%s", conn.RemoteAddr()) | |
| } | |
| }, | |
| } | |
| // Graceful shutdown | |
| c := make(chan os.Signal, 1) | |
| signal.Notify(c, os.Interrupt, syscall.SIGTERM) | |
| go func() { | |
| <-c | |
| log.Println("SHUTDOWN_INITIATED") | |
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | |
| defer cancel() | |
| server.Shutdown(ctx) | |
| log.Println("SHUTDOWN_COMPLETE") | |
| }() | |
| log.Printf("STARTUP port=%s cert=%s key=%s max_body=%d", *port, *cert, *key, *maxBody) | |
| log.Printf("SERVER_LISTENING port=%s", *port) | |
| if err := server.ListenAndServeTLS(*cert, *key); err != nil && err != http.ErrServerClosed { | |
| log.Fatal("SERVER_ERROR:", err) | |
| } | |
| log.Println("SERVER_STOPPED") | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment