440 lines
11 KiB
Go
440 lines
11 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ThreatManager integrates threat analysis with the main application
|
|
type ThreatManager struct {
|
|
analyzer *ThreatAnalyzer
|
|
api *ThreatAPI
|
|
authManager *AuthManager
|
|
securityManager *SecurityManager
|
|
userAPI *UserAPI
|
|
blocklistExporter *BlocklistExporter
|
|
webTemplateAPI *WebTemplateAPI
|
|
webServicesAPI *WebServicesAPI
|
|
}
|
|
|
|
// NewThreatManager creates a new threat manager instance
|
|
func NewThreatManager(dbPath string) (*ThreatManager, error) {
|
|
analyzer, err := NewThreatAnalyzer(dbPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Initialize authentication manager with the same database
|
|
authManager, err := NewAuthManager(analyzer.db)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Initialize security manager
|
|
securityManager := NewSecurityManager(authManager)
|
|
|
|
// Initialize APIs
|
|
api := NewThreatAPI(analyzer)
|
|
userAPI := NewUserAPI(authManager, securityManager)
|
|
|
|
// Initialize blocklist exporter
|
|
blocklistExporter := NewBlocklistExporter(analyzer)
|
|
|
|
// Initialize web template API
|
|
templateManager := NewWebTemplateManager("webtemplates")
|
|
webTemplateAPI := NewWebTemplateAPI(templateManager)
|
|
|
|
// Initialize web services API
|
|
configManager := NewConfigManager("config.json")
|
|
webServicesAPI := NewWebServicesAPI(configManager)
|
|
|
|
tm := &ThreatManager{
|
|
analyzer: analyzer,
|
|
api: api,
|
|
authManager: authManager,
|
|
securityManager: securityManager,
|
|
userAPI: userAPI,
|
|
blocklistExporter: blocklistExporter,
|
|
webTemplateAPI: webTemplateAPI,
|
|
webServicesAPI: webServicesAPI,
|
|
}
|
|
|
|
return tm, nil
|
|
}
|
|
|
|
// ProcessHoneypotRecord processes a honeypot log record for threat analysis
|
|
func (tm *ThreatManager) ProcessHoneypotRecord(timestamp time.Time, remoteAddr, remotePort, service string, details map[string]interface{}, rawPayload string) {
|
|
// Convert to LogRecord format
|
|
record := LogRecord{
|
|
IP: remoteAddr,
|
|
Service: service,
|
|
Timestamp: timestamp,
|
|
Details: details,
|
|
}
|
|
|
|
// Add additional analysis data
|
|
if record.Details == nil {
|
|
record.Details = make(map[string]interface{})
|
|
}
|
|
|
|
record.Details["remote_port"] = remotePort
|
|
record.Details["raw_payload_length"] = len(rawPayload)
|
|
|
|
// Analyze payload for suspicious patterns
|
|
if suspiciousPatterns := analyzeSuspiciousPatterns(rawPayload, service); len(suspiciousPatterns) > 0 {
|
|
record.Details["suspicious_patterns"] = suspiciousPatterns
|
|
}
|
|
|
|
// Process the record
|
|
if err := tm.analyzer.ProcessLogRecord(record); err != nil {
|
|
log.Printf("Failed to process log record for threat analysis: %v", err)
|
|
}
|
|
}
|
|
|
|
// analyzeSuspiciousPatterns detects suspicious patterns in payloads
|
|
func analyzeSuspiciousPatterns(payload, service string) []string {
|
|
var patterns []string
|
|
|
|
// Common attack patterns
|
|
suspiciousStrings := []string{
|
|
"admin", "root", "administrator", "test", "guest", "user",
|
|
"password", "123456", "qwerty", "letmein", "welcome",
|
|
"../", "../../", "/etc/passwd", "/etc/shadow",
|
|
"<script>", "javascript:", "eval(", "exec(",
|
|
"union select", "drop table", "insert into",
|
|
"wget", "curl", "nc -", "netcat", "/bin/sh", "/bin/bash",
|
|
"cmd.exe", "powershell", "certutil",
|
|
}
|
|
|
|
payloadLower := strings.ToLower(payload)
|
|
|
|
for _, pattern := range suspiciousStrings {
|
|
if strings.Contains(payloadLower, pattern) {
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
|
|
// Service-specific patterns
|
|
switch service {
|
|
case "ssh":
|
|
sshPatterns := []string{
|
|
"SSH-2.0-libssh", "SSH-2.0-PuTTY", "SSH-2.0-OpenSSH",
|
|
}
|
|
for _, pattern := range sshPatterns {
|
|
if strings.Contains(payload, pattern) {
|
|
patterns = append(patterns, "ssh_client:"+pattern)
|
|
}
|
|
}
|
|
|
|
case "http", "https":
|
|
httpPatterns := []string{
|
|
"User-Agent:", "Mozilla/", "curl/", "wget/", "python-requests/",
|
|
"sqlmap", "nikto", "nmap", "masscan", "zmap",
|
|
}
|
|
for _, pattern := range httpPatterns {
|
|
if strings.Contains(payloadLower, pattern) {
|
|
patterns = append(patterns, "http_pattern:"+pattern)
|
|
}
|
|
}
|
|
|
|
case "ftp":
|
|
ftpPatterns := []string{
|
|
"USER anonymous", "USER ftp", "PASS anonymous",
|
|
}
|
|
for _, pattern := range ftpPatterns {
|
|
if strings.Contains(payloadLower, pattern) {
|
|
patterns = append(patterns, "ftp_pattern:"+pattern)
|
|
}
|
|
}
|
|
}
|
|
|
|
return patterns
|
|
}
|
|
|
|
// GetAnalyzer returns the threat analyzer instance
|
|
func (tm *ThreatManager) GetAnalyzer() *ThreatAnalyzer {
|
|
return tm.analyzer
|
|
}
|
|
|
|
// GetAPI returns the threat API instance
|
|
func (tm *ThreatManager) GetAPI() *ThreatAPI {
|
|
return tm.api
|
|
}
|
|
|
|
// GetSecurityManager returns the security manager instance
|
|
func (tm *ThreatManager) GetSecurityManager() *SecurityManager {
|
|
return tm.securityManager
|
|
}
|
|
// GetUserAPI returns the user API instance
|
|
func (tm *ThreatManager) GetUserAPI() *UserAPI {
|
|
return tm.userAPI
|
|
}
|
|
|
|
// GetBlocklistExporter returns the blocklist exporter
|
|
func (tm *ThreatManager) GetBlocklistExporter() *BlocklistExporter {
|
|
return tm.blocklistExporter
|
|
}
|
|
|
|
// GetWebTemplateAPI returns the web template API
|
|
func (tm *ThreatManager) GetWebTemplateAPI() *WebTemplateAPI {
|
|
return tm.webTemplateAPI
|
|
}
|
|
|
|
// GetWebServicesAPI returns the web services API
|
|
func (tm *ThreatManager) GetWebServicesAPI() *WebServicesAPI {
|
|
return tm.webServicesAPI
|
|
}
|
|
|
|
// GetBlockedIPs returns currently blocked IPs for firewall integration
|
|
func (tm *ThreatManager) GetBlockedIPs() ([]string, error) {
|
|
return tm.analyzer.GetBlockedIPs()
|
|
}
|
|
// IsIPBlocked checks if an IP is currently blocked
|
|
func (tm *ThreatManager) IsIPBlocked(ip string) (bool, error) {
|
|
report, err := tm.analyzer.AnalyzeIP(ip)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return report.IsBlocked, nil
|
|
}
|
|
|
|
// RunPeriodicTasks starts background tasks for threat analysis
|
|
func (tm *ThreatManager) RunPeriodicTasks(ctx context.Context) {
|
|
// Update threat scores every 5 minutes
|
|
go func() {
|
|
ticker := time.NewTicker(5 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
if err := tm.updateThreatScores(); err != nil {
|
|
// Only log if not a context cancellation or database closed error
|
|
if ctx.Err() == nil && !strings.Contains(err.Error(), "database is closed") {
|
|
log.Printf("Failed to update threat scores: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Run rule evaluation every minute
|
|
go func() {
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
if err := tm.evaluateAllRules(); err != nil {
|
|
// Only log if not a context cancellation or database closed error
|
|
if ctx.Err() == nil && !strings.Contains(err.Error(), "database is closed") {
|
|
log.Printf("Failed to evaluate rules: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// updateThreatScores recalculates threat scores for all IPs
|
|
func (tm *ThreatManager) updateThreatScores() error {
|
|
// Get all IPs from the last 24 hours
|
|
query := `SELECT DISTINCT ip FROM ip_analysis WHERE last_seen >= ?`
|
|
yesterday := time.Now().Add(-24 * time.Hour)
|
|
|
|
rows, err := tm.analyzer.db.Query(query, yesterday)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var ips []string
|
|
for rows.Next() {
|
|
var ip string
|
|
if err := rows.Scan(&ip); err != nil {
|
|
continue
|
|
}
|
|
ips = append(ips, ip)
|
|
}
|
|
|
|
// Calculate threat score for each IP
|
|
for _, ip := range ips {
|
|
score := tm.calculateThreatScore(ip)
|
|
|
|
// Update the threat score in database
|
|
updateQuery := `UPDATE ip_analysis SET threat_score = ?, updated_at = CURRENT_TIMESTAMP WHERE ip = ?`
|
|
_, err := tm.analyzer.db.Exec(updateQuery, score, ip)
|
|
if err != nil {
|
|
log.Printf("Failed to update threat score for IP %s: %v", ip, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// calculateThreatScore calculates a threat score (0-100) for an IP
|
|
func (tm *ThreatManager) calculateThreatScore(ip string) int {
|
|
score := 0
|
|
|
|
// Get IP statistics
|
|
var connections, authAttempts int
|
|
var servicesJSON string
|
|
query := `SELECT total_connections, total_auth_attempts, services FROM ip_analysis WHERE ip = ?`
|
|
err := tm.analyzer.db.QueryRow(query, ip).Scan(&connections, &authAttempts, &servicesJSON)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
// Base score from connections (max 20 points)
|
|
if connections > 100 {
|
|
score += 20
|
|
} else if connections > 50 {
|
|
score += 15
|
|
} else if connections > 20 {
|
|
score += 10
|
|
} else if connections > 10 {
|
|
score += 5
|
|
}
|
|
|
|
// Score from auth attempts (max 25 points)
|
|
if authAttempts > 50 {
|
|
score += 25
|
|
} else if authAttempts > 20 {
|
|
score += 20
|
|
} else if authAttempts > 10 {
|
|
score += 15
|
|
} else if authAttempts > 5 {
|
|
score += 10
|
|
}
|
|
|
|
// Score from service diversity (max 15 points)
|
|
var services []string
|
|
if servicesJSON != "" {
|
|
json.Unmarshal([]byte(servicesJSON), &services)
|
|
}
|
|
serviceCount := len(services)
|
|
if serviceCount > 5 {
|
|
score += 15
|
|
} else if serviceCount > 3 {
|
|
score += 10
|
|
} else if serviceCount > 1 {
|
|
score += 5
|
|
}
|
|
|
|
// Score from threat events (max 40 points)
|
|
var eventCount int
|
|
eventQuery := `SELECT COUNT(*) FROM threat_events WHERE ip = ? AND last_seen >= ?`
|
|
yesterday := time.Now().Add(-24 * time.Hour)
|
|
err = tm.analyzer.db.QueryRow(eventQuery, ip, yesterday).Scan(&eventCount)
|
|
if err == nil {
|
|
if eventCount > 10 {
|
|
score += 40
|
|
} else if eventCount > 5 {
|
|
score += 30
|
|
} else if eventCount > 2 {
|
|
score += 20
|
|
} else if eventCount > 0 {
|
|
score += 10
|
|
}
|
|
}
|
|
|
|
// Cap at 100
|
|
if score > 100 {
|
|
score = 100
|
|
}
|
|
|
|
return score
|
|
}
|
|
|
|
// evaluateAllRules runs all active rules against recent activity
|
|
func (tm *ThreatManager) evaluateAllRules() error {
|
|
// Get recent log entries (last 5 minutes)
|
|
// This would need to be integrated with the actual logging system
|
|
// For now, we'll just update existing threat events
|
|
|
|
rules, err := tm.analyzer.GetRules()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, rule := range rules {
|
|
if !rule.Enabled {
|
|
continue
|
|
}
|
|
|
|
// Evaluate rule against recent activity
|
|
if err := tm.evaluateRuleForRecentActivity(rule); err != nil {
|
|
log.Printf("Failed to evaluate rule %s: %v", rule.Name, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// evaluateRuleForRecentActivity evaluates a rule against recent activity
|
|
func (tm *ThreatManager) evaluateRuleForRecentActivity(rule ThreatRule) error {
|
|
timeWindow := time.Now().Add(-time.Duration(rule.TimeWindow) * time.Minute)
|
|
|
|
// Get IPs with recent activity
|
|
query := `SELECT DISTINCT ip FROM ip_analysis WHERE last_seen >= ?`
|
|
rows, err := tm.analyzer.db.Query(query, timeWindow)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var ips []string
|
|
for rows.Next() {
|
|
var ip string
|
|
if err := rows.Scan(&ip); err != nil {
|
|
continue
|
|
}
|
|
ips = append(ips, ip)
|
|
}
|
|
|
|
// Evaluate rule for each IP
|
|
for _, ip := range ips {
|
|
// Create a mock log record for evaluation
|
|
record := LogRecord{
|
|
IP: ip,
|
|
Service: rule.Service,
|
|
Timestamp: time.Now(),
|
|
Details: make(map[string]interface{}),
|
|
}
|
|
|
|
triggered, err := tm.analyzer.evaluateRule(rule, record)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if triggered {
|
|
// Check if we already have a recent event for this IP/rule combination
|
|
var existingCount int
|
|
checkQuery := `SELECT COUNT(*) FROM threat_events
|
|
WHERE ip = ? AND rule_id = ? AND last_seen >= ?`
|
|
err := tm.analyzer.db.QueryRow(checkQuery, ip, rule.ID, timeWindow).Scan(&existingCount)
|
|
|
|
if err == nil && existingCount == 0 {
|
|
// Create new threat event
|
|
if err := tm.analyzer.createThreatEvent(rule, record); err != nil {
|
|
log.Printf("Failed to create threat event: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes the threat manager and its resources
|
|
func (tm *ThreatManager) Close() error {
|
|
return tm.analyzer.Close()
|
|
}
|