Skip to content

Commit c59d000

Browse files
committed
feat: implement alert correlation and context building for enhanced alert analysis
1 parent a8329a1 commit c59d000

File tree

4 files changed

+164
-2
lines changed

4 files changed

+164
-2
lines changed

soc-ai/configurations/const.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ var (
5959
"email": {Regexp: `([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})`, FakeValue: "jhondoe@gmail.com"},
6060
//"ipv4": `(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`,
6161
}
62-
GPT_INSTRUCTION = "You are an expert security engineer. Perform a deep analysis of an alert created by a SIEM and the logs related to it. Determine if the alert could be an actual potential threat or not and explain why. Provide a description that shows a deep understanding of the alert based on a deep analysis of its logs and estimate the risk to the systems affected. Classify the alert in the following manner: if the alert information is sufficient to determine that the security, availability, confidentiality, or integrity of the systems has being compromised, then classify it as \"possible incident\". If the alert does not pose a security risk to the organization or has no security relevance, classify it as \"possible false positive\". If the alert does not pose an imminent risk to the systems, requires no urgent action from an administrator, or requires not urgent review by an administrator, it should be classified as a \"standard alert\". You will also provide context-specific instructions for remediation, mitigation, or further investigation, related to the alert and logs analyzed. Your answer should be provided using the following JSON format and the total number of characters in your answer must not exceed 1500 words. Your entire answer must be inside this json format. {\"activity_id\":\"<activity_id>\",\"classification\":\"<classification>\",\"reasoning\":[\"<deep_reasoning>\"],\"nextSteps\":[{\"step\":1,\"action\":\"<action_1>\",\"details\":\"<action_1_details>\"},{\"step\":2,\"action\":\"<action_2>\",\"details\":\"<action_2_details>\"},{\"step\":3,\"action\":\"<action_3>\"]}Ensure that your entire answer adheres to the provided JSON format. The response should be valid JSON syntax and schema."
63-
GPT_FALSE_POSITIVE = "This alert is categorized as a potential false positive due to two key factors. Firstly, it originates from an automated system, which may occasionally produce alerts without direct human validation. Additionally, the absence of any correlated logs further raises suspicion, as a genuine incident typically leaves a trail of relevant log entries. Hence, the combination of its system-generated nature and the lack of associated logs suggests a likelihood of being a false positive rather than a genuine security incident."
62+
GPT_INSTRUCTION = "You are an expert security engineer. Perform a deep analysis of an alert created by a SIEM and the logs related to it. Determine if the alert could be an actual potential threat or not and explain why. Provide a description that shows a deep understanding of the alert based on a deep analysis of its logs and estimate the risk to the systems affected. Classify the alert in the following manner: if the alert information is sufficient to determine that the security, availability, confidentiality, or integrity of the systems has being compromised, then classify it as \"possible incident\". If the alert does not pose a security risk to the organization or has no security relevance, classify it as \"possible false positive\". If the alert does not pose an imminent risk to the systems, requires no urgent action from an administrator, or requires not urgent review by an administrator, it should be classified as a \"standard alert\". You will also provide context-specific instructions for remediation, mitigation, or further investigation, related to the alert and logs analyzed. Your answer should be provided using the following JSON format and the total number of characters in your answer must not exceed 1500 words. Your entire answer must be inside this json format. {\"activity_id\":\"<activity_id>\",\"classification\":\"<classification>\",\"reasoning\":[\"<deep_reasoning>\"],\"nextSteps\":[{\"step\":1,\"action\":\"<action_1>\",\"details\":\"<action_1_details>\"},{\"step\":2,\"action\":\"<action_2>\",\"details\":\"<action_2_details>\"},{\"step\":3,\"action\":\"<action_3>\"]}Ensure that your entire answer adheres to the provided JSON format. The response should be valid JSON syntax and schema."
63+
GPT_FALSE_POSITIVE = "This alert is categorized as a potential false positive due to two key factors. Firstly, it originates from an automated system, which may occasionally produce alerts without direct human validation. Additionally, the absence of any correlated logs further raises suspicion, as a genuine incident typically leaves a trail of relevant log entries. Hence, the combination of its system-generated nature and the lack of associated logs suggests a likelihood of being a false positive rather than a genuine security incident."
64+
CORRELATION_CONTEXT = "\n\nAlert Context: The current alert has historical correlation with previous alerts:\n%s"
6465
)
6566

6667
func GetInternalKey() string {

soc-ai/elastic/alerts.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"strings"
78

89
"github.com/utmstack/soc-ai/configurations"
910
"github.com/utmstack/soc-ai/schema"
@@ -65,3 +66,141 @@ func ChangeAlertStatus(id string, status int, observations string) error {
6566

6667
return nil
6768
}
69+
70+
type AlertCorrelation struct {
71+
CurrentAlert schema.Alert
72+
RelatedAlerts []schema.Alert
73+
Classifications []string
74+
}
75+
76+
func GetRelatedAlerts() ([]schema.GPTAlertResponse, error) {
77+
result, err := ElasticSearch(configurations.ALERT_INDEX_PATTERN, "*", "*")
78+
if err != nil {
79+
return nil, fmt.Errorf("error getting historical alerts: %v", err)
80+
}
81+
82+
var alerts []schema.GPTAlertResponse
83+
err = json.Unmarshal(result, &alerts)
84+
if err != nil {
85+
return nil, fmt.Errorf("error unmarshalling alerts: %v", err)
86+
}
87+
88+
return alerts, nil
89+
}
90+
91+
func FindRelatedAlerts(currentAlert schema.Alert) (*AlertCorrelation, error) {
92+
correlation := &AlertCorrelation{
93+
CurrentAlert: currentAlert,
94+
RelatedAlerts: []schema.Alert{},
95+
Classifications: []string{},
96+
}
97+
98+
historicalResponses, err := GetRelatedAlerts()
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
var alertIDs []string
104+
for _, resp := range historicalResponses {
105+
alertIDs = append(alertIDs, resp.ActivityID)
106+
}
107+
108+
for _, id := range alertIDs {
109+
alert, err := GetAlertsInfo(id)
110+
if err != nil {
111+
continue
112+
}
113+
114+
if isAlertRelated(currentAlert, alert) {
115+
correlation.RelatedAlerts = append(correlation.RelatedAlerts, alert)
116+
117+
for _, resp := range historicalResponses {
118+
if resp.ActivityID == alert.ID {
119+
correlation.Classifications = append(correlation.Classifications, resp.Classification)
120+
break
121+
}
122+
}
123+
}
124+
}
125+
126+
return correlation, nil
127+
}
128+
129+
func isAlertRelated(current, historical schema.Alert) bool {
130+
if current.Destination.IP != "" && current.Destination.IP == historical.Destination.IP {
131+
return true
132+
}
133+
if current.Destination.Port != 0 && current.Destination.Port == historical.Destination.Port {
134+
return true
135+
}
136+
if current.Destination.Host != "" && current.Destination.Host == historical.Destination.Host {
137+
return true
138+
}
139+
if current.Destination.User != "" && current.Destination.User == historical.Destination.User {
140+
return true
141+
}
142+
143+
if current.Source.IP != "" && current.Source.IP == historical.Source.IP {
144+
return true
145+
}
146+
if current.Source.Port != 0 && current.Source.Port == historical.Source.Port {
147+
return true
148+
}
149+
if current.Source.Host != "" && current.Source.Host == historical.Source.Host {
150+
return true
151+
}
152+
if current.Source.User != "" && current.Source.User == historical.Source.User {
153+
return true
154+
}
155+
156+
return false
157+
}
158+
159+
func BuildCorrelationContext(correlation *AlertCorrelation) string {
160+
var context strings.Builder
161+
162+
context.WriteString("\nHistorical Context:\n")
163+
context.WriteString(fmt.Sprintf("Found %d related alerts with similar characteristics:\n", len(correlation.RelatedAlerts)))
164+
165+
for i, alert := range correlation.RelatedAlerts {
166+
context.WriteString(fmt.Sprintf("\nRelated Alert %d:\n", i+1))
167+
context.WriteString(fmt.Sprintf("- Name: %s\n", alert.Name))
168+
context.WriteString(fmt.Sprintf("- Severity: %s\n", alert.SeverityLabel))
169+
context.WriteString(fmt.Sprintf("- Category: %s\n", alert.Category))
170+
context.WriteString(fmt.Sprintf("- Classification: %s\n", correlation.Classifications[i]))
171+
context.WriteString(fmt.Sprintf("- Time: %s\n", alert.Timestamp))
172+
173+
if alert.Source.IP != "" {
174+
context.WriteString(fmt.Sprintf("- Source IP: %s\n", alert.Source.IP))
175+
}
176+
if alert.Destination.IP != "" {
177+
context.WriteString(fmt.Sprintf("- Destination IP: %s\n", alert.Destination.IP))
178+
}
179+
if alert.Source.Host != "" {
180+
context.WriteString(fmt.Sprintf("- Source Host: %s\n", alert.Source.Host))
181+
}
182+
if alert.Destination.Host != "" {
183+
context.WriteString(fmt.Sprintf("- Destination Host: %s\n", alert.Destination.Host))
184+
}
185+
if alert.Source.User != "" {
186+
context.WriteString(fmt.Sprintf("- Source User: %s\n", alert.Source.User))
187+
}
188+
if alert.Destination.User != "" {
189+
context.WriteString(fmt.Sprintf("- Destination User: %s\n", alert.Destination.User))
190+
}
191+
if alert.Source.Port != 0 {
192+
context.WriteString(fmt.Sprintf("- Source Port: %d\n", alert.Source.Port))
193+
}
194+
if alert.Destination.Port != 0 {
195+
context.WriteString(fmt.Sprintf("- Destination Port: %d\n", alert.Destination.Port))
196+
}
197+
if alert.Protocol != "" {
198+
context.WriteString(fmt.Sprintf("- Protocol: %s\n", alert.Protocol))
199+
}
200+
if alert.Severity != 0 {
201+
context.WriteString(fmt.Sprintf("- Severity: %d\n", alert.Severity))
202+
}
203+
}
204+
205+
return context.String()
206+
}

soc-ai/gpt/client.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gpt
33
import (
44
"encoding/json"
55
"fmt"
6+
"strings"
67
"sync"
78

89
"github.com/utmstack/soc-ai/configurations"
@@ -26,6 +27,15 @@ func GetGPTClient() *GPTClient {
2627

2728
func (c *GPTClient) Request(alert schema.AlertGPTDetails) (string, error) {
2829
content := configurations.GPT_INSTRUCTION
30+
31+
if alert.Description != "" {
32+
correlationContext := strings.Split(alert.Description, "\nHistorical Context:")
33+
if len(correlationContext) > 1 {
34+
content = fmt.Sprintf("%s%s",
35+
content, fmt.Sprintf(configurations.CORRELATION_CONTEXT, correlationContext[1]))
36+
}
37+
}
38+
2939
if alert.Logs == "" || alert.Logs == " " {
3040
content += content + ". " + configurations.GPT_FALSE_POSITIVE
3141
}

soc-ai/processor/alertProcessor.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/utmstack/soc-ai/elastic"
77
"github.com/utmstack/soc-ai/schema"
8+
"github.com/utmstack/soc-ai/utils"
89
)
910

1011
func (p *Processor) processAlertsInfo() {
@@ -15,7 +16,18 @@ func (p *Processor) processAlertsInfo() {
1516
continue
1617
}
1718

19+
correlation, err := elastic.FindRelatedAlerts(alertInfo)
20+
if err != nil {
21+
utils.Logger.ErrorF("error finding related alerts: %v", err)
22+
}
23+
1824
details := schema.ConvertFromAlertToAlertDB(alertInfo)
25+
26+
if correlation != nil && len(correlation.RelatedAlerts) > 0 {
27+
correlationContext := elastic.BuildCorrelationContext(correlation)
28+
details.Description = details.Description + "\n\n" + correlationContext
29+
}
30+
1931
p.GPTQueue <- cleanAlerts(&details)
2032
}
2133
}

0 commit comments

Comments
 (0)