mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-09 03:22:37 +02:00
Release prep: 54 engines, self-hosted signatures, i18n, dashboard updates
This commit is contained in:
parent
694e32be26
commit
41cbfd6e0a
178 changed files with 36008 additions and 399 deletions
215
internal/domain/soc/threat_intel.go
Normal file
215
internal/domain/soc/threat_intel.go
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
package soc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ThreatIntelEngine implements §6 — IOC (Indicator of Compromise) matching.
|
||||
// Maintains feed subscriptions and in-memory IOC database for real-time matching.
|
||||
type ThreatIntelEngine struct {
|
||||
mu sync.RWMutex
|
||||
iocs map[string]*IOC // key = value (IP, domain, hash)
|
||||
feeds []Feed
|
||||
hits []IOCHit
|
||||
max int
|
||||
}
|
||||
|
||||
// IOCType categorizes the indicator.
|
||||
type IOCType string
|
||||
|
||||
const (
|
||||
IOCIP IOCType = "ip"
|
||||
IOCDomain IOCType = "domain"
|
||||
IOCHash IOCType = "hash"
|
||||
IOCEmail IOCType = "email"
|
||||
IOCURL IOCType = "url"
|
||||
)
|
||||
|
||||
// IOC is an individual indicator of compromise.
|
||||
type IOC struct {
|
||||
Value string `json:"value"`
|
||||
Type IOCType `json:"type"`
|
||||
Severity string `json:"severity"` // CRITICAL, HIGH, MEDIUM, LOW
|
||||
Source string `json:"source"` // Feed name
|
||||
Tags []string `json:"tags"`
|
||||
Description string `json:"description"`
|
||||
FirstSeen time.Time `json:"first_seen"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
HitCount int `json:"hit_count"`
|
||||
}
|
||||
|
||||
// Feed represents a threat intelligence source.
|
||||
type Feed struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"` // stix, csv, json
|
||||
Enabled bool `json:"enabled"`
|
||||
IOCCount int `json:"ioc_count"`
|
||||
LastSync time.Time `json:"last_sync"`
|
||||
SyncInterval string `json:"sync_interval"`
|
||||
}
|
||||
|
||||
// IOCHit records a match between an event and an IOC.
|
||||
type IOCHit struct {
|
||||
IOCValue string `json:"ioc_value"`
|
||||
IOCType IOCType `json:"ioc_type"`
|
||||
EventID string `json:"event_id"`
|
||||
Severity string `json:"severity"`
|
||||
Source string `json:"source"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// NewThreatIntelEngine creates the IOC matching engine with default feeds.
|
||||
func NewThreatIntelEngine() *ThreatIntelEngine {
|
||||
t := &ThreatIntelEngine{
|
||||
iocs: make(map[string]*IOC),
|
||||
max: 1000,
|
||||
}
|
||||
t.loadDefaultFeeds()
|
||||
t.loadSampleIOCs()
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *ThreatIntelEngine) loadDefaultFeeds() {
|
||||
t.feeds = []Feed{
|
||||
{Name: "AlienVault OTX", URL: "https://otx.alienvault.com/api/v1/pulses/subscribed", Type: "json", Enabled: true, SyncInterval: "1h"},
|
||||
{Name: "Abuse.ch URLhaus", URL: "https://urlhaus.abuse.ch/downloads/csv_recent/", Type: "csv", Enabled: true, SyncInterval: "30m"},
|
||||
{Name: "CIRCL MISP", URL: "https://www.circl.lu/doc/misp/feed-osint/", Type: "stix", Enabled: false, SyncInterval: "6h"},
|
||||
{Name: "Internal STIX", URL: "file:///var/sentinel/iocs/internal.stix", Type: "stix", Enabled: true, SyncInterval: "5m"},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ThreatIntelEngine) loadSampleIOCs() {
|
||||
samples := []IOC{
|
||||
{Value: "185.220.101.35", Type: IOCIP, Severity: "HIGH", Source: "AlienVault OTX", Tags: []string{"tor-exit", "scanner"}, Description: "Known Tor exit node / mass scanner"},
|
||||
{Value: "evil-ai-jailbreak.com", Type: IOCDomain, Severity: "CRITICAL", Source: "Internal STIX", Tags: []string{"jailbreak", "c2"}, Description: "Jailbreak prompt C2 domain"},
|
||||
{Value: "d41d8cd98f00b204e9800998ecf8427e", Type: IOCHash, Severity: "MEDIUM", Source: "Abuse.ch URLhaus", Tags: []string{"malware-hash"}, Description: "Known malware hash (MD5)"},
|
||||
{Value: "attacker@malicious-prompts.org", Type: IOCEmail, Severity: "HIGH", Source: "Internal STIX", Tags: []string{"phishing", "social-engineering"}, Description: "Known prompt injection author"},
|
||||
}
|
||||
now := time.Now()
|
||||
for _, ioc := range samples {
|
||||
ioc := ioc // shadow to capture per-iteration (safe for Go <1.22)
|
||||
ioc.FirstSeen = now.Add(-72 * time.Hour)
|
||||
ioc.LastSeen = now
|
||||
t.iocs[ioc.Value] = &ioc
|
||||
}
|
||||
for i := range t.feeds {
|
||||
if t.feeds[i].Enabled {
|
||||
t.feeds[i].IOCCount = len(samples) / 2
|
||||
t.feeds[i].LastSync = now.Add(-15 * time.Minute)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match checks a string against the IOC database.
|
||||
// Returns matching IOC or nil.
|
||||
func (t *ThreatIntelEngine) Match(value string) *IOC {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(value))
|
||||
if ioc, ok := t.iocs[normalized]; ok {
|
||||
ioc.HitCount++
|
||||
ioc.LastSeen = time.Now()
|
||||
copy := *ioc // return safe copy, not mutable internal pointer
|
||||
return ©
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MatchEvent checks all fields of an event description for IOC matches.
|
||||
// Returns all hits.
|
||||
func (t *ThreatIntelEngine) MatchEvent(eventID, text string) []IOCHit {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
var hits []IOCHit
|
||||
lower := strings.ToLower(text)
|
||||
for _, ioc := range t.iocs {
|
||||
if strings.Contains(lower, strings.ToLower(ioc.Value)) {
|
||||
hit := IOCHit{
|
||||
IOCValue: ioc.Value,
|
||||
IOCType: ioc.Type,
|
||||
EventID: eventID,
|
||||
Severity: ioc.Severity,
|
||||
Source: ioc.Source,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
ioc.HitCount++
|
||||
ioc.LastSeen = time.Now()
|
||||
hits = append(hits, hit)
|
||||
|
||||
if len(t.hits) >= t.max {
|
||||
copy(t.hits, t.hits[1:])
|
||||
t.hits[len(t.hits)-1] = hit
|
||||
} else {
|
||||
t.hits = append(t.hits, hit)
|
||||
}
|
||||
}
|
||||
}
|
||||
return hits
|
||||
}
|
||||
|
||||
// AddIOC adds a custom indicator of compromise.
|
||||
func (t *ThreatIntelEngine) AddIOC(ioc IOC) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if ioc.FirstSeen.IsZero() {
|
||||
ioc.FirstSeen = time.Now()
|
||||
}
|
||||
ioc.LastSeen = time.Now()
|
||||
t.iocs[strings.ToLower(ioc.Value)] = &ioc
|
||||
}
|
||||
|
||||
// ListIOCs returns all indicators.
|
||||
func (t *ThreatIntelEngine) ListIOCs() []IOC {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
result := make([]IOC, 0, len(t.iocs))
|
||||
for _, ioc := range t.iocs {
|
||||
result = append(result, *ioc)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ListFeeds returns configured threat intel feeds.
|
||||
func (t *ThreatIntelEngine) ListFeeds() []Feed {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
result := make([]Feed, len(t.feeds))
|
||||
copy(result, t.feeds)
|
||||
return result
|
||||
}
|
||||
|
||||
// RecentHits returns recent IOC match hits.
|
||||
func (t *ThreatIntelEngine) RecentHits(limit int) []IOCHit {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
if limit <= 0 || limit > len(t.hits) {
|
||||
limit = len(t.hits)
|
||||
}
|
||||
start := len(t.hits) - limit
|
||||
result := make([]IOCHit, limit)
|
||||
copy(result, t.hits[start:])
|
||||
return result
|
||||
}
|
||||
|
||||
// Stats returns threat intel statistics.
|
||||
func (t *ThreatIntelEngine) ThreatIntelStats() map[string]any {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
enabledFeeds := 0
|
||||
for _, f := range t.feeds {
|
||||
if f.Enabled {
|
||||
enabledFeeds++
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"total_iocs": len(t.iocs),
|
||||
"total_feeds": len(t.feeds),
|
||||
"enabled_feeds": enabledFeeds,
|
||||
"total_hits": len(t.hits),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue