Skip to content

Commit 2918115

Browse files
Merge pull request #1416 from vpiserchia/msdefender-indicators-update
Update MS Defender Responders
2 parents 5649710 + cc7c362 commit 2918115

13 files changed

Lines changed: 341 additions & 79 deletions

responders/MSDefenderEndpoints/MSDefenderEndpoints.py

Lines changed: 89 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,32 @@
66
import json
77
import datetime
88

9+
# https://learn.microsoft.com/en-us/defender-endpoint/indicators-overview
10+
IP_HOSTNAME_ACTIONS = [
11+
'isolateMachine',
12+
'unisolateMachine',
13+
'runFullVirusScan',
14+
'restrictAppExecution',
15+
'unrestrictAppExecution',
16+
'startAutoInvestigation'
17+
]
18+
URL_DOMAIN_ACTIONS = [
19+
'pushIOCAllowed',
20+
'pushIOCAudit',
21+
'pushIOCWarn',
22+
'pushIOCBlock',
23+
'pushIOCAlert' # Deprecated: maps to Audit internally
24+
]
25+
26+
ACTIONS = {
27+
'hash': URL_DOMAIN_ACTIONS + ['pushIOCBlockAndRemediate'],
28+
'ip': URL_DOMAIN_ACTIONS + IP_HOSTNAME_ACTIONS,
29+
'url': URL_DOMAIN_ACTIONS,
30+
'domain': URL_DOMAIN_ACTIONS,
31+
'fqdn': URL_DOMAIN_ACTIONS,
32+
'hostname': IP_HOSTNAME_ACTIONS,
33+
}
34+
935
class MSDefenderEndpoints(Responder):
1036
def __init__(self):
1137
Responder.__init__(self)
@@ -48,10 +74,8 @@ def run(self):
4874
try:
4975
response = urllib.request.urlopen(req)
5076
except urllib.error.HTTPError as e:
51-
#print("message: HTTP ErrorCode {}. Reason: {}".format(e.code,e.reason))
5277
self.error({'message': "HTTP ErrorCode {}. Reason: {}".format(e.code,e.reason)})
5378
except urllib.error.URLError as e:
54-
#print("message: URL Error: {}".format(e.reason))
5579
self.error({'message': "URL Error: {}".format(e.reason)})
5680

5781
jsonResponse = json.loads(response.read())
@@ -69,8 +93,10 @@ def getMachineId(id):
6993

7094
if self.observableType == "ip":
7195
url = "{}/machines/findbyip(ip='{}',timestamp={})".format(self.msdefenderApiBaseUrl, id, time)
72-
else:
96+
elif self.observableType == "hostname":
7397
url = "{}/machines?$filter=computerDnsName+eq+'{}'".format(self.msdefenderApiBaseUrl, id)
98+
else:
99+
self.error({'message': f"Data type {self.observableType} not supported, accepted types are: 'ip', 'hostname'."})
74100

75101
try:
76102
response = self.msdefenderSession.get(url=url)
@@ -223,13 +249,12 @@ def startAutoInvestigation(machineId):
223249
self.error({'message': e})
224250

225251

226-
def pushCustomIocAlert(observable):
227-
252+
def pushCustomIoc(observable, mode='Block', severity='Medium', alert=True):
228253
if self.observableType == 'ip':
229254
indicatorType = 'IpAddress'
230255
elif self.observableType == 'url':
231256
indicatorType = 'Url'
232-
elif self.observableType == 'domain':
257+
elif self.observableType in ('domain', 'fqdn'):
233258
indicatorType = 'DomainName'
234259
elif self.observableType == 'hash':
235260
if len(observable) == 32:
@@ -241,83 +266,69 @@ def pushCustomIocAlert(observable):
241266
else:
242267
self.report({'message':"Observable is not a valid hash"})
243268
else:
244-
self.error({'message':"Observable type must be ip, url, domain or hash"})
269+
self.error({'message': "Observable type must be 'ip', 'url', 'domain', 'fqdn' or 'hash'"})
245270

246271
url = '{}/indicators'.format(self.msdefenderApiBaseUrl)
247272
body = {
248273
'indicatorValue': observable,
249274
'indicatorType': indicatorType,
250-
'action': 'Alert',
275+
'action': mode,
251276
'title': "TheHive IOC: {}".format(self.caseTitle),
252-
'severity': 'High',
277+
'severity': severity,
253278
'description': "TheHive case: {} - caseId {}".format(self.caseTitle,self.caseId),
254-
'recommendedActions': 'N/A'
279+
'recommendedActions': 'N/A',
280+
'generateAlert': alert
255281
}
256282

257283
try:
258284
response = self.msdefenderSession.post(url=url, json=body)
259285
if response.status_code == 200:
260-
self.report({'message': "Added IOC to Defender with Alert mode: " + self.observable })
286+
self.report({'message': "Added IOC to Defender with %s mode: " % mode + self.observable })
261287
except requests.exceptions.RequestException as e:
262288
self.error({'message': e})
263289

264-
def pushCustomIocBlock(observable):
265-
266-
if self.observableType == 'ip':
267-
indicatorType = 'IpAddress'
268-
elif self.observableType == 'url':
269-
indicatorType = 'Url'
270-
elif self.observableType == 'domain':
271-
indicatorType = 'DomainName'
272-
elif self.observableType == 'hash':
273-
if len(observable) == 32:
274-
indicatorType = 'FileMd5'
275-
elif len(observable) == 40:
276-
indicatorType = 'FileSha1'
277-
elif len(observable) == 64:
278-
indicatorType = 'FileSha256'
279-
else:
280-
self.report({'message':"Observable is not a valid hash"})
281-
else:
282-
self.error({'message':"Observable type must be ip, url, domain or hash"})
283-
284-
url = '{}/indicators'.format(self.msdefenderApiBaseUrl)
285-
body = {
286-
'indicatorValue' : observable,
287-
'indicatorType' : indicatorType,
288-
'action' : 'AlertAndBlock',
289-
'title' : "TheHive IOC: {}".format(self.caseTitle),
290-
'severity' : 'High',
291-
'description' : "TheHive case: {} - caseId {}".format(self.caseTitle,self.caseId),
292-
'recommendedActions' : 'N/A'
293-
}
294-
295-
try:
296-
response = self.msdefenderSession.post(url=url, json=body)
297-
if response.status_code == 200:
298-
self.report({'message': "Added IOC to Defender with Alert and Block mode: " + self.observable })
299-
except requests.exceptions.RequestException as e:
300-
self.error({'message': e})
301290

291+
# validate the observable type and service requested upon it
292+
# Note: "certificate thumbprint" is not supported by this responder
293+
# since it would require a custom observable type for it.
294+
#
295+
if self.observableType not in ACTIONS:
296+
self.error({'message': "Observable type must be 'hostname', 'ip', 'url', 'domain', 'fqdn', 'hash'"})
297+
elif self.service not in ACTIONS.get(self.observableType, []):
298+
self.error(
299+
{'message': f"Action '{self.service}' not supported for type '{self.observableType}'.\n"
300+
f"Valid actions are {ACTIONS[self.observableType]}" })
302301

303-
if self.service == "isolateMachine":
304-
isolateMachine(getMachineId(self.observable))
305-
elif self.service == "unisolateMachine":
306-
unisolateMachine(getMachineId(self.observable))
307-
elif self.service == "runFullVirusScan":
308-
runFullVirusScan(getMachineId(self.observable))
309-
elif self.service == "restrictAppExecution":
310-
restrictAppExecution(getMachineId(self.observable))
311-
elif self.service == "unrestrictAppExecution":
312-
unrestrictAppExecution(getMachineId(self.observable))
313-
elif self.service == "startAutoInvestigation":
314-
startAutoInvestigation(getMachineId(self.observable))
315-
elif self.service == "pushIOCBlock":
316-
pushCustomIocBlock(self.observable)
317-
elif self.service == "pushIOCAlert":
318-
pushCustomIocAlert(self.observable)
319-
else:
320-
self.error({'message': "Unidentified service"})
302+
# run action
303+
try:
304+
if self.service == "isolateMachine":
305+
isolateMachine(getMachineId(self.observable))
306+
elif self.service == "unisolateMachine":
307+
unisolateMachine(getMachineId(self.observable))
308+
elif self.service == "runFullVirusScan":
309+
runFullVirusScan(getMachineId(self.observable))
310+
elif self.service == "restrictAppExecution":
311+
restrictAppExecution(getMachineId(self.observable))
312+
elif self.service == "unrestrictAppExecution":
313+
unrestrictAppExecution(getMachineId(self.observable))
314+
elif self.service == "startAutoInvestigation":
315+
startAutoInvestigation(getMachineId(self.observable))
316+
elif self.service == "pushIOCBlock":
317+
pushCustomIoc(self.observable, 'Block', 'Low', False)
318+
elif self.service == "pushIOCAlert":
319+
pushCustomIoc(self.observable, 'Audit', 'Informational', True)
320+
elif self.service == "pushIOCAudit":
321+
pushCustomIoc(self.observable, 'Audit', 'Informational', True)
322+
elif self.service == "pushIOCAllowed":
323+
pushCustomIoc(self.observable, 'Allowed', 'Informational', False)
324+
elif self.service == "pushIOCBlockAndRemediate":
325+
pushCustomIoc(self.observable, 'BlockAndRemediate', 'High', True)
326+
elif self.service == "pushIOCWarn":
327+
pushCustomIoc(self.observable, 'Warn', 'Medium', True)
328+
else:
329+
self.error({'message': "Unidentified service"})
330+
except Exception as e:
331+
self.error({'message': e})
321332

322333
def operations(self, raw):
323334
self.build_operation('AddTagToCase', tag='MSDefenderResponder:run')
@@ -331,7 +342,18 @@ def operations(self, raw):
331342
return [self.build_operation("AddTagToArtifact", tag="MsDefender:restrictedAppExec")]
332343
elif self.service == "unrestrictAppExecution":
333344
return [self.build_operation("AddTagToArtifact", tag="MsDefender:unrestrictedAppExec")]
345+
elif self.service == "pushIOCBlock":
346+
return [self.build_operation("AddTagToArtifact", tag="MsDefender:pushIOCBlock")]
347+
elif self.service == "pushIOCAudit":
348+
return [self.build_operation("AddTagToArtifact", tag="MsDefender:pushIOCAudit")]
349+
elif self.service == "pushIOCAllowed":
350+
return [self.build_operation("AddTagToArtifact", tag="MsDefender:pushIOCAllowed")]
351+
elif self.service == "pushIOCBlockAndRemediate":
352+
return [self.build_operation("AddTagToArtifact", tag="MsDefender:pushIOCBlockAndRemediate")]
353+
elif self.service == "pushIOCWarn":
354+
return [self.build_operation("AddTagToArtifact", tag="MsDefender:pushIOCWarn")]
334355

335-
if __name__ == '__main__':
336356

357+
if __name__ == '__main__':
337358
MSDefenderEndpoints().run()
359+

responders/MSDefenderEndpoints/MSDefenderEndpoints_AutoInvestigation.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
{
4040
"name": "resourceAppIdUri",
41-
"description": "Security Center URI, usually doens't need to change",
41+
"description": "Security Center URI, usually doesn't need to change",
4242
"type": "string",
4343
"multi": false,
4444
"required": true,

responders/MSDefenderEndpoints/MSDefenderEndpoints_Isolate.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
{
4040
"name": "resourceAppIdUri",
41-
"description": "Security Center URI, usually doens't need to change",
41+
"description": "Security Center URI, usually doesn't need to change",
4242
"type": "string",
4343
"multi": false,
4444
"required": true,

responders/MSDefenderEndpoints/MSDefenderEndpoints_PushIOCAlert.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"author": "Keijo Korte, Louis-Maximilien Dupouy",
55
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
66
"license": "AGPL-V3",
7-
"description": "Push IOC to Defender client. Alert mode",
7+
"description": "Push IOC to Defender client. Alert mode. This mode is Deprecated and Audit should be used instead.",
88
"dataTypeList": ["thehive:case_artifact"],
99
"command": "MSDefenderEndpoints/MSDefenderEndpoints.py",
1010
"baseConfig": "MSDefenderforEndpoints",
@@ -38,7 +38,7 @@
3838
},
3939
{
4040
"name": "resourceAppIdUri",
41-
"description": "Security Center URI, usually doens't need to change",
41+
"description": "Security Center URI, usually doesn't need to change",
4242
"type": "string",
4343
"multi": false,
4444
"required": true,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"name": "MSDefender-PushIOC-Allowed",
3+
"version": "1.0",
4+
"author": "Vito Piserchia",
5+
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
6+
"license": "AGPL-V3",
7+
"description": "Push IOC to Defender client. Allowed mode",
8+
"dataTypeList": ["thehive:case_artifact"],
9+
"command": "MSDefenderEndpoints/MSDefenderEndpoints.py",
10+
"baseConfig": "MSDefenderforEndpoints",
11+
"config": {
12+
"service": "pushIOCAllowed"
13+
},
14+
"configurationItems": [
15+
{
16+
"name": "tenantId",
17+
"description": "Azure tenant ID",
18+
"type": "string",
19+
"multi": false,
20+
"required": true,
21+
"defaultValue": "abcdef12-ab12-abc12-ab12-abcdef123456"
22+
},
23+
{
24+
"name": "appId",
25+
"description": "Azure app ID",
26+
"type": "string",
27+
"multi": false,
28+
"required": true,
29+
"defaultValue": "abcdef12-ab12-abc12-ab12-abcdef123456"
30+
},
31+
{
32+
"name": "appSecret",
33+
"description": "Azure app secret",
34+
"type": "string",
35+
"multi": false,
36+
"required": true,
37+
"defaultValue": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890="
38+
},
39+
{
40+
"name": "resourceAppIdUri",
41+
"description": "Security Center URI, usually doesn't need to change",
42+
"type": "string",
43+
"multi": false,
44+
"required": true,
45+
"defaultValue": "https://api.security.microsoft.com"
46+
},
47+
{
48+
"name": "oAuthUri",
49+
"description": "Azure oAuth2 authentication endpoint",
50+
"type": "string",
51+
"multi": false,
52+
"required": true,
53+
"defaultValue": "https://login.microsoftonline.com"
54+
}
55+
],
56+
"registration_required": true,
57+
"subscription_required": true,
58+
"free_subscription": false,
59+
"service_homepage": "https://securitycenter.windows.com"
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"name": "MSDefender-PushIOC-Audit",
3+
"version": "1.0",
4+
"author": "Vito Piserchia",
5+
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
6+
"license": "AGPL-V3",
7+
"description": "Push IOC to Defender client. Audit mode",
8+
"dataTypeList": ["thehive:case_artifact"],
9+
"command": "MSDefenderEndpoints/MSDefenderEndpoints.py",
10+
"baseConfig": "MSDefenderforEndpoints",
11+
"config": {
12+
"service": "pushIOCAudit"
13+
},
14+
"configurationItems": [
15+
{
16+
"name": "tenantId",
17+
"description": "Azure tenant ID",
18+
"type": "string",
19+
"multi": false,
20+
"required": true,
21+
"defaultValue": "abcdef12-ab12-abc12-ab12-abcdef123456"
22+
},
23+
{
24+
"name": "appId",
25+
"description": "Azure app ID",
26+
"type": "string",
27+
"multi": false,
28+
"required": true,
29+
"defaultValue": "abcdef12-ab12-abc12-ab12-abcdef123456"
30+
},
31+
{
32+
"name": "appSecret",
33+
"description": "Azure app secret",
34+
"type": "string",
35+
"multi": false,
36+
"required": true,
37+
"defaultValue": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890="
38+
},
39+
{
40+
"name": "resourceAppIdUri",
41+
"description": "Security Center URI, usually doesn't need to change",
42+
"type": "string",
43+
"multi": false,
44+
"required": true,
45+
"defaultValue": "https://api.security.microsoft.com"
46+
},
47+
{
48+
"name": "oAuthUri",
49+
"description": "Azure oAuth2 authentication endpoint",
50+
"type": "string",
51+
"multi": false,
52+
"required": true,
53+
"defaultValue": "https://login.microsoftonline.com"
54+
}
55+
],
56+
"registration_required": true,
57+
"subscription_required": true,
58+
"free_subscription": false,
59+
"service_homepage": "https://securitycenter.windows.com"
60+
}

0 commit comments

Comments
 (0)