howler-sentinel-plugin 0.2.0.dev92__tar.gz → 0.2.0.dev96__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/PKG-INFO +1 -1
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/pyproject.toml +1 -1
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/actions/send_to_sentinel.py +0 -1
- howler_sentinel_plugin-0.2.0.dev96/sentinel/actions/update_defender_xdr_alert.py +184 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/LICENSE +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/README.md +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/__init__.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/actions/ingestion.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/actions/synchronization.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/mapping/sentinel_incident.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/mapping/xdr_alert.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/mapping/xdr_alert_evidence.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/odm/hit.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/odm/models/sentinel.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/routes/__init__.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/routes/ingest.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/utils/tenant_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "howler-sentinel-plugin"
|
|
3
|
-
version = "0.2.0.
|
|
3
|
+
version = "0.2.0.dev96"
|
|
4
4
|
description = "A howler plugin for integration with Microsoft's Sentinel API"
|
|
5
5
|
authors = [{ name = "CCCS", email = "analysis-development@cyber.gc.ca" }]
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from howler.common.exceptions import HowlerRuntimeError
|
|
3
|
+
from howler.common.loader import datastore
|
|
4
|
+
from howler.common.logging import get_logger
|
|
5
|
+
from howler.odm.models.action import VALID_TRIGGERS
|
|
6
|
+
from howler.odm.models.hit import Hit
|
|
7
|
+
from howler.odm.models.howler_data import Assessment, HitStatus
|
|
8
|
+
|
|
9
|
+
from sentinel.utils.tenant_utils import get_token
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__file__)
|
|
12
|
+
|
|
13
|
+
OPERATION_ID = "update_defender_xdr_alert"
|
|
14
|
+
|
|
15
|
+
properties_map = {
|
|
16
|
+
"graph": {
|
|
17
|
+
"status": {
|
|
18
|
+
HitStatus.OPEN: "new",
|
|
19
|
+
HitStatus.IN_PROGRESS: "inProgress",
|
|
20
|
+
HitStatus.ON_HOLD: "inProgress",
|
|
21
|
+
HitStatus.RESOLVED: "resolved",
|
|
22
|
+
},
|
|
23
|
+
"classification": {
|
|
24
|
+
Assessment.AMBIGUOUS: "unknown",
|
|
25
|
+
Assessment.SECURITY: "informationalExpectedActivity",
|
|
26
|
+
Assessment.DEVELOPMENT: "informationalExpectedActivity",
|
|
27
|
+
Assessment.FALSE_POSITIVE: "falsePositive",
|
|
28
|
+
Assessment.LEGITIMATE: "informationalExpectedActivity",
|
|
29
|
+
Assessment.TRIVIAL: "falsePositive",
|
|
30
|
+
Assessment.RECON: "truePositive",
|
|
31
|
+
Assessment.ATTEMPT: "truePositive",
|
|
32
|
+
Assessment.COMPROMISE: "truePositive",
|
|
33
|
+
Assessment.MITIGATED: "truePositive",
|
|
34
|
+
None: "unknown",
|
|
35
|
+
},
|
|
36
|
+
"determination": {
|
|
37
|
+
Assessment.AMBIGUOUS: "unknown",
|
|
38
|
+
Assessment.SECURITY: "securityTesting",
|
|
39
|
+
Assessment.DEVELOPMENT: "confirmedUserActivity",
|
|
40
|
+
Assessment.FALSE_POSITIVE: "other",
|
|
41
|
+
Assessment.LEGITIMATE: "lineOfBusinessApplication",
|
|
42
|
+
Assessment.TRIVIAL: "other",
|
|
43
|
+
Assessment.RECON: "multiStagedAttack",
|
|
44
|
+
Assessment.ATTEMPT: "other",
|
|
45
|
+
Assessment.COMPROMISE: "maliciousUserActivity",
|
|
46
|
+
Assessment.MITIGATED: "other",
|
|
47
|
+
None: "unknown",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def execute(query: str, **kwargs):
|
|
54
|
+
"""Update Microsoft Defender XDR alert.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
query (str): The query on which to apply this automation.
|
|
58
|
+
"""
|
|
59
|
+
report = []
|
|
60
|
+
ds = datastore()
|
|
61
|
+
|
|
62
|
+
hits: list[Hit] = ds.hit.search(query, as_obj=True)["items"]
|
|
63
|
+
|
|
64
|
+
if not hits:
|
|
65
|
+
report.append(
|
|
66
|
+
{
|
|
67
|
+
"query": query,
|
|
68
|
+
"outcome": "error",
|
|
69
|
+
"title": "No hits returned by query",
|
|
70
|
+
"message": f"No hits returned by '{query}'",
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
return report
|
|
74
|
+
|
|
75
|
+
for hit in hits:
|
|
76
|
+
tenant_id = hit.azure.tenant_id
|
|
77
|
+
if not tenant_id and hit.organization.id:
|
|
78
|
+
tenant_id = hit.organization.id
|
|
79
|
+
elif not tenant_id:
|
|
80
|
+
report.append(
|
|
81
|
+
{
|
|
82
|
+
"query": f"howler.id:{hit.howler.id}",
|
|
83
|
+
"outcome": "skipped",
|
|
84
|
+
"title": "Azure Tenant ID is missing",
|
|
85
|
+
"message": "This alert does not have a set tenant ID.",
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
token = get_token(hit.azure.tenant_id)[0]
|
|
92
|
+
except HowlerRuntimeError as err:
|
|
93
|
+
logger.exception("Error on token fetching")
|
|
94
|
+
report.append(
|
|
95
|
+
{
|
|
96
|
+
"query": f"howler.id:{hit.howler.id}",
|
|
97
|
+
"outcome": "error",
|
|
98
|
+
"title": "Invalid Credentials",
|
|
99
|
+
"message": err.message,
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Fetch alert details
|
|
105
|
+
alert_url = f"https://graph.microsoft.com/v1.0/security/alerts_v2/{hit.rule.id}"
|
|
106
|
+
response = requests.get(alert_url, headers={"Authorization": f"Bearer {token}"}, timeout=5.0)
|
|
107
|
+
if not response.ok:
|
|
108
|
+
logger.warning("GET request to Microsoft Graph failed with status code %s.", response.status_code)
|
|
109
|
+
report.append(
|
|
110
|
+
{
|
|
111
|
+
"query": query,
|
|
112
|
+
"outcome": "error",
|
|
113
|
+
"title": "Microsoft Graph API request failed",
|
|
114
|
+
"message": f"GET request to Microsoft Graph failed with status code {response.status_code}.",
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
alert_data = response.json()
|
|
120
|
+
|
|
121
|
+
# Update alert
|
|
122
|
+
if (
|
|
123
|
+
"assessment" in hit.howler
|
|
124
|
+
and hit.howler.assessment in properties_map["graph"]["classification"]
|
|
125
|
+
and hit.howler.assessment in properties_map["graph"]["determination"]
|
|
126
|
+
):
|
|
127
|
+
classification = properties_map["graph"]["classification"][hit.howler.assessment]
|
|
128
|
+
determination = properties_map["graph"]["determination"][hit.howler.assessment]
|
|
129
|
+
else:
|
|
130
|
+
classification = alert_data["classification"]
|
|
131
|
+
determination = alert_data["determination"]
|
|
132
|
+
|
|
133
|
+
status = properties_map["graph"]["status"][hit.howler.status]
|
|
134
|
+
assigned_to = alert_data["assignedTo"]
|
|
135
|
+
|
|
136
|
+
data = {
|
|
137
|
+
"assignedTo": assigned_to,
|
|
138
|
+
"classification": classification,
|
|
139
|
+
"determination": determination,
|
|
140
|
+
"status": status,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
response = requests.patch(
|
|
144
|
+
alert_url,
|
|
145
|
+
json=data,
|
|
146
|
+
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
|
147
|
+
timeout=5.0,
|
|
148
|
+
)
|
|
149
|
+
if not response.ok:
|
|
150
|
+
report.append(
|
|
151
|
+
{
|
|
152
|
+
"query": query,
|
|
153
|
+
"outcome": "error",
|
|
154
|
+
"title": "Microsoft Graph API request failed",
|
|
155
|
+
"message": f"PATCH request to Microsoft Graph failed with status code {response.status_code}.",
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
report.append(
|
|
160
|
+
{
|
|
161
|
+
"query": f"howler.id:{hit.howler.id}",
|
|
162
|
+
"outcome": "success",
|
|
163
|
+
"title": "Alert updated in XDR Defender",
|
|
164
|
+
"message": "Howler has successfuly propagated changes to this alert to XDR Defender.",
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return report
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def specification():
|
|
172
|
+
"Update Defender action specification"
|
|
173
|
+
return {
|
|
174
|
+
"id": OPERATION_ID,
|
|
175
|
+
"title": "Update Microsoft Defender XDR alert",
|
|
176
|
+
"priority": 8,
|
|
177
|
+
"description": {
|
|
178
|
+
"short": "Update Microsoft Defender XDR alert",
|
|
179
|
+
"long": execute.__doc__,
|
|
180
|
+
},
|
|
181
|
+
"roles": ["automation_basic"],
|
|
182
|
+
"steps": [{"args": {}, "options": {}, "validation": {}}],
|
|
183
|
+
"triggers": VALID_TRIGGERS,
|
|
184
|
+
}
|
|
File without changes
|
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/odm/hit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev92 → howler_sentinel_plugin-0.2.0.dev96}/sentinel/routes/ingest.py
RENAMED
|
File without changes
|
|
File without changes
|