66import json
77import 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+
935class 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+
0 commit comments