package app import ( "encoding/json" "fmt" "net" "os" "path/filepath" "sync" "time" ) // ThreatIntel tracks malicious IPs and their activities type ThreatIntel struct { mu sync.RWMutex maliciousIPs map[string]*IPThreatInfo configPath string autoSave bool } // IPThreatInfo contains information about a potentially malicious IP type IPThreatInfo struct { IP string `json:"ip"` FirstSeen time.Time `json:"first_seen"` LastSeen time.Time `json:"last_seen"` TotalConnections int `json:"total_connections"` Services map[string]int `json:"services"` // service -> connection count AuthAttempts int `json:"auth_attempts"` // total authentication attempts UniqueUsernames map[string]int `json:"unique_usernames"` // username -> attempt count UniquePasswords map[string]int `json:"unique_passwords"` // password -> attempt count Countries map[string]int `json:"countries"` // country -> count (if GeoIP available) ThreatScore int `json:"threat_score"` // calculated threat score IsBlacklisted bool `json:"is_blacklisted"` Notes []string `json:"notes"` RawPayloads []string `json:"raw_payloads,omitempty"` // sample payloads } // NewThreatIntel creates a new threat intelligence tracker func NewThreatIntel(configPath string, autoSave bool) *ThreatIntel { ti := &ThreatIntel{ maliciousIPs: make(map[string]*IPThreatInfo), configPath: configPath, autoSave: autoSave, } // Try to load existing data ti.Load() // Start auto-save routine if enabled if autoSave { go ti.autoSaveRoutine() } return ti } // RecordActivity records activity from an IP address func (ti *ThreatIntel) RecordActivity(record Record) { ti.mu.Lock() defer ti.mu.Unlock() ip := record.RemoteAddr if ip == "" { return } // Get or create IP info info, exists := ti.maliciousIPs[ip] if !exists { info = &IPThreatInfo{ IP: ip, FirstSeen: record.Timestamp, Services: make(map[string]int), UniqueUsernames: make(map[string]int), UniquePasswords: make(map[string]int), Countries: make(map[string]int), Notes: []string{}, RawPayloads: []string{}, } ti.maliciousIPs[ip] = info } // Update basic info info.LastSeen = record.Timestamp info.TotalConnections++ info.Services[record.Service]++ // Process authentication attempts if record.Details != nil { if username, ok := record.Details["username"]; ok && username != "" { info.UniqueUsernames[username]++ info.AuthAttempts++ } if password, ok := record.Details["password"]; ok && password != "" { info.UniquePasswords[password]++ } // Check for specific attack patterns ti.analyzeAttackPatterns(info, record) } // Store sample payloads (limit to 10) if record.RawPayload != "" && len(info.RawPayloads) < 10 { info.RawPayloads = append(info.RawPayloads, record.RawPayload) } // Recalculate threat score info.ThreatScore = ti.calculateThreatScore(info) // Auto-blacklist based on threat score if info.ThreatScore >= 100 && !info.IsBlacklisted { info.IsBlacklisted = true info.Notes = append(info.Notes, fmt.Sprintf("Auto-blacklisted at %s (threat score: %d)", time.Now().Format(time.RFC3339), info.ThreatScore)) } } // analyzeAttackPatterns looks for specific attack patterns and adds notes func (ti *ThreatIntel) analyzeAttackPatterns(info *IPThreatInfo, record Record) { // Check for brute force patterns if info.AuthAttempts > 10 { if len(info.Notes) == 0 || info.Notes[len(info.Notes)-1] != "Brute force attack detected" { info.Notes = append(info.Notes, "Brute force attack detected") } } // Check for credential stuffing (many unique usernames) if len(info.UniqueUsernames) > 20 { if len(info.Notes) == 0 || info.Notes[len(info.Notes)-1] != "Credential stuffing attack detected" { info.Notes = append(info.Notes, "Credential stuffing attack detected") } } // Check for service scanning (multiple services) if len(info.Services) > 5 { if len(info.Notes) == 0 || info.Notes[len(info.Notes)-1] != "Port/service scanning detected" { info.Notes = append(info.Notes, "Port/service scanning detected") } } // Check for common attack usernames commonAttackUsernames := []string{"admin", "root", "administrator", "user", "test", "guest", "oracle", "postgres", "mysql"} if username, ok := record.Details["username"]; ok { for _, attackUser := range commonAttackUsernames { if username == attackUser { info.Notes = append(info.Notes, fmt.Sprintf("Used common attack username: %s", username)) break } } } // Check for common attack passwords commonAttackPasswords := []string{"password", "123456", "admin", "root", "toor", "password123", "qwerty"} if password, ok := record.Details["password"]; ok { for _, attackPass := range commonAttackPasswords { if password == attackPass { info.Notes = append(info.Notes, fmt.Sprintf("Used common attack password: %s", password)) break } } } } // calculateThreatScore calculates a threat score based on various factors func (ti *ThreatIntel) calculateThreatScore(info *IPThreatInfo) int { score := 0 // Base score for any connection score += 1 // Score based on number of connections if info.TotalConnections > 100 { score += 50 } else if info.TotalConnections > 50 { score += 30 } else if info.TotalConnections > 10 { score += 15 } else if info.TotalConnections > 5 { score += 5 } // Score based on authentication attempts if info.AuthAttempts > 50 { score += 40 } else if info.AuthAttempts > 20 { score += 25 } else if info.AuthAttempts > 10 { score += 15 } else if info.AuthAttempts > 5 { score += 10 } // Score based on unique usernames (credential stuffing) if len(info.UniqueUsernames) > 50 { score += 30 } else if len(info.UniqueUsernames) > 20 { score += 20 } else if len(info.UniqueUsernames) > 10 { score += 10 } // Score based on service diversity (scanning) if len(info.Services) > 10 { score += 25 } else if len(info.Services) > 5 { score += 15 } else if len(info.Services) > 3 { score += 10 } // Score based on time span (persistent attacker) timeSpan := info.LastSeen.Sub(info.FirstSeen) if timeSpan > 24*time.Hour { score += 20 } else if timeSpan > 12*time.Hour { score += 15 } else if timeSpan > 6*time.Hour { score += 10 } else if timeSpan > 1*time.Hour { score += 5 } return score } // IsBlacklisted checks if an IP is blacklisted func (ti *ThreatIntel) IsBlacklisted(ip string) bool { ti.mu.RLock() defer ti.mu.RUnlock() if info, exists := ti.maliciousIPs[ip]; exists { return info.IsBlacklisted } return false } // GetThreatInfo returns threat information for an IP func (ti *ThreatIntel) GetThreatInfo(ip string) (*IPThreatInfo, bool) { ti.mu.RLock() defer ti.mu.RUnlock() info, exists := ti.maliciousIPs[ip] if !exists { return nil, false } // Return a copy to avoid race conditions infoCopy := *info infoCopy.Services = make(map[string]int) infoCopy.UniqueUsernames = make(map[string]int) infoCopy.UniquePasswords = make(map[string]int) infoCopy.Countries = make(map[string]int) for k, v := range info.Services { infoCopy.Services[k] = v } for k, v := range info.UniqueUsernames { infoCopy.UniqueUsernames[k] = v } for k, v := range info.UniquePasswords { infoCopy.UniquePasswords[k] = v } for k, v := range info.Countries { infoCopy.Countries[k] = v } infoCopy.Notes = make([]string, len(info.Notes)) copy(infoCopy.Notes, info.Notes) infoCopy.RawPayloads = make([]string, len(info.RawPayloads)) copy(infoCopy.RawPayloads, info.RawPayloads) return &infoCopy, true } // GetTopThreats returns the top N threats by score func (ti *ThreatIntel) GetTopThreats(n int) []*IPThreatInfo { ti.mu.RLock() defer ti.mu.RUnlock() var threats []*IPThreatInfo for _, info := range ti.maliciousIPs { threats = append(threats, info) } // Sort by threat score (simple bubble sort for small datasets) for i := 0; i < len(threats)-1; i++ { for j := 0; j < len(threats)-i-1; j++ { if threats[j].ThreatScore < threats[j+1].ThreatScore { threats[j], threats[j+1] = threats[j+1], threats[j] } } } if n > len(threats) { n = len(threats) } return threats[:n] } // GetBlacklistedIPs returns all blacklisted IPs func (ti *ThreatIntel) GetBlacklistedIPs() []string { ti.mu.RLock() defer ti.mu.RUnlock() var blacklisted []string for ip, info := range ti.maliciousIPs { if info.IsBlacklisted { blacklisted = append(blacklisted, ip) } } return blacklisted } // ManualBlacklist manually blacklists an IP func (ti *ThreatIntel) ManualBlacklist(ip string, reason string) { ti.mu.Lock() defer ti.mu.Unlock() info, exists := ti.maliciousIPs[ip] if !exists { info = &IPThreatInfo{ IP: ip, FirstSeen: time.Now(), Services: make(map[string]int), UniqueUsernames: make(map[string]int), UniquePasswords: make(map[string]int), Countries: make(map[string]int), Notes: []string{}, RawPayloads: []string{}, } ti.maliciousIPs[ip] = info } info.IsBlacklisted = true info.LastSeen = time.Now() note := fmt.Sprintf("Manually blacklisted at %s", time.Now().Format(time.RFC3339)) if reason != "" { note += fmt.Sprintf(" - Reason: %s", reason) } info.Notes = append(info.Notes, note) } // RemoveFromBlacklist removes an IP from the blacklist func (ti *ThreatIntel) RemoveFromBlacklist(ip string) { ti.mu.Lock() defer ti.mu.Unlock() if info, exists := ti.maliciousIPs[ip]; exists { info.IsBlacklisted = false info.Notes = append(info.Notes, fmt.Sprintf("Removed from blacklist at %s", time.Now().Format(time.RFC3339))) } } // Save saves threat intelligence data to file func (ti *ThreatIntel) Save() error { ti.mu.RLock() defer ti.mu.RUnlock() if ti.configPath == "" { return nil } // Create directory if it doesn't exist if err := os.MkdirAll(filepath.Dir(ti.configPath), 0755); err != nil { return fmt.Errorf("create threat intel dir: %w", err) } data, err := json.MarshalIndent(ti.maliciousIPs, "", " ") if err != nil { return fmt.Errorf("marshal threat intel data: %w", err) } return os.WriteFile(ti.configPath, data, 0644) } // Load loads threat intelligence data from file func (ti *ThreatIntel) Load() error { ti.mu.Lock() defer ti.mu.Unlock() if ti.configPath == "" { return nil } data, err := os.ReadFile(ti.configPath) if err != nil { if os.IsNotExist(err) { return nil // File doesn't exist yet, that's okay } return fmt.Errorf("read threat intel file: %w", err) } return json.Unmarshal(data, &ti.maliciousIPs) } // autoSaveRoutine periodically saves threat intelligence data func (ti *ThreatIntel) autoSaveRoutine() { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { if err := ti.Save(); err != nil { // Log error but don't stop the routine fmt.Printf("Error auto-saving threat intel: %v\n", err) } } } // GetStats returns overall statistics func (ti *ThreatIntel) GetStats() map[string]interface{} { ti.mu.RLock() defer ti.mu.RUnlock() totalIPs := len(ti.maliciousIPs) blacklistedIPs := 0 totalConnections := 0 totalAuthAttempts := 0 serviceStats := make(map[string]int) for _, info := range ti.maliciousIPs { if info.IsBlacklisted { blacklistedIPs++ } totalConnections += info.TotalConnections totalAuthAttempts += info.AuthAttempts for service, count := range info.Services { serviceStats[service] += count } } return map[string]interface{}{ "total_ips": totalIPs, "blacklisted_ips": blacklistedIPs, "total_connections": totalConnections, "total_auth_attempts": totalAuthAttempts, "service_stats": serviceStats, } } // ShouldBlock determines if a connection should be blocked based on IP reputation func (ti *ThreatIntel) ShouldBlock(ip string) bool { // Check if IP is blacklisted if ti.IsBlacklisted(ip) { return true } // Additional checks could be added here: // - Rate limiting // - Temporary blocks for high-frequency connections // - Integration with external threat feeds return false } // IsPrivateIP checks if an IP address is private/internal func IsPrivateIP(ip string) bool { parsedIP := net.ParseIP(ip) if parsedIP == nil { return false } // Check for private IP ranges privateRanges := []string{ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16", "::1/128", "fc00::/7", "fe80::/10", } for _, cidr := range privateRanges { _, network, err := net.ParseCIDR(cidr) if err != nil { continue } if network.Contains(parsedIP) { return true } } return false }