1+ """
2+ Accepts a hostname or device id as well as a file hash as input and add an indicator (IOC) for a device in Crowdstrike. We then generate an observable report as well as a Markdown formatted report. Both reports can be customized based on user preference.
3+ """
4+
5+
6+ import phantom .rules as phantom
7+ import json
8+ from datetime import datetime , timedelta
9+
10+
11+ @phantom .playbook_block ()
12+ def on_start (container ):
13+ phantom .debug ('on_start() called' )
14+
15+ # call 'input_filter' block
16+ input_filter (container = container )
17+
18+ return
19+
20+ @phantom .playbook_block ()
21+ def input_filter (action = None , success = None , container = None , results = None , handle = None , filtered_artifacts = None , filtered_results = None , custom_function = None , loop_state_json = None , ** kwargs ):
22+ phantom .debug ("input_filter() called" )
23+
24+ ################################################################################
25+ # Determines if the provided inputs are present in the dataset.
26+ ################################################################################
27+
28+ # collect filtered artifact ids and results for 'if' condition 1
29+ matched_artifacts_1 , matched_results_1 = phantom .condition (
30+ container = container ,
31+ logical_operator = "and" ,
32+ conditions = [
33+ ["playbook_input:device" , "!=" , "" ],
34+ ["playbook_input:hash" , "!=" , "" ]
35+ ],
36+ name = "input_filter:condition_1" ,
37+ delimiter = None )
38+
39+ # call connected blocks if filtered artifacts or results
40+ if matched_artifacts_1 or matched_results_1 :
41+ format_fql (action = action , success = success , container = container , results = results , handle = handle , filtered_artifacts = matched_artifacts_1 , filtered_results = matched_results_1 )
42+
43+ return
44+
45+
46+ @phantom .playbook_block ()
47+ def file_observables (action = None , success = None , container = None , results = None , handle = None , filtered_artifacts = None , filtered_results = None , custom_function = None , loop_state_json = None , ** kwargs ):
48+ phantom .debug ("file_observables() called" )
49+
50+ ################################################################################
51+ # Format a normalized output for each host
52+ ################################################################################
53+
54+ query_device_result_data = phantom .collect2 (container = container , datapath = ["query_device:action_result.data.*.device_id" ,"query_device:action_result.data.*.hostname" ], action_results = results )
55+ filtered_input_0_hash = phantom .collect2 (container = container , datapath = ["filtered-data:input_filter:condition_1:playbook_input:hash" ])
56+ upload_indicator_result_data = phantom .collect2 (container = container , datapath = ["upload_indicator:action_result.status" ,"upload_indicator:action_result.message" ], action_results = results )
57+
58+ query_device_result_item_0 = [item [0 ] for item in query_device_result_data ]
59+ query_device_result_item_1 = [item [1 ] for item in query_device_result_data ]
60+ filtered_input_0_hash_values = [item [0 ] for item in filtered_input_0_hash ]
61+ upload_indicator_result_item_0 = [item [0 ] for item in upload_indicator_result_data ]
62+ upload_indicator_result_message = [item [1 ] for item in upload_indicator_result_data ]
63+
64+ file_observables__observable_array = None
65+
66+ ################################################################################
67+ ## Custom Code Start
68+ ################################################################################
69+
70+ file_observables__observable_array = []
71+
72+ for device_id , hostname , file_hash , status , status_message in zip (query_device_result_item_0 , query_device_result_item_1 , filtered_input_0_hash_values , upload_indicator_result_item_0 , upload_indicator_result_message ):
73+ # Initialize the observable dictionary
74+ observable = {
75+ "source" : "Crowdstrike OAuth API" ,
76+ "type" : "Endpoint" ,
77+ "activity_name" : "File Execution Prevention" ,
78+ "uid" : device_id ,
79+ "hostname" : hostname ,
80+ "status" : status ,
81+ "status_detail" : status_message ,
82+ "file" : {
83+ "hashes" : [
84+ {
85+ "algorithm" : "SHA-256" ,
86+ "algorithm_id" : 3 ,
87+ "value" : file_hash
88+ }
89+ ]
90+ },
91+ "d3fend" : {
92+ "d3f_tactic" : "Isolate" ,
93+ "d3f_technique" : "D3-EDL" ,
94+ "version" : "1.0.0"
95+ }
96+ }
97+
98+ # Add the observable to the array
99+ file_observables__observable_array .append (observable )
100+
101+ # Debug output for verification
102+ phantom .debug (file_observables__observable_array )
103+
104+ ################################################################################
105+ ## Custom Code End
106+ ################################################################################
107+
108+ phantom .save_run_data (key = "file_observables:observable_array" , value = json .dumps (file_observables__observable_array ))
109+
110+ return
111+
112+
113+ @phantom .playbook_block ()
114+ def format_fql (action = None , success = None , container = None , results = None , handle = None , filtered_artifacts = None , filtered_results = None , custom_function = None , loop_state_json = None , ** kwargs ):
115+ phantom .debug ("format_fql() called" )
116+
117+ ################################################################################
118+ # Format the FQL query to get the input device information using its ID or hostname.
119+ ################################################################################
120+
121+ template = """%%\n hostname:['{0}'],device_id:['{0}']\n %%"""
122+
123+ # parameter list for template variable replacement
124+ parameters = [
125+ "playbook_input:device"
126+ ]
127+
128+ ################################################################################
129+ ## Custom Code Start
130+ ################################################################################
131+
132+ # Write your custom code here...
133+
134+ ################################################################################
135+ ## Custom Code End
136+ ################################################################################
137+
138+ phantom .format (container = container , template = template , parameters = parameters , name = "format_fql" )
139+
140+ query_device (container = container )
141+
142+ return
143+
144+
145+ @phantom .playbook_block ()
146+ def query_device (action = None , success = None , container = None , results = None , handle = None , filtered_artifacts = None , filtered_results = None , custom_function = None , loop_state_json = None , ** kwargs ):
147+ phantom .debug ("query_device() called" )
148+
149+ # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
150+
151+ ################################################################################
152+ # Get information about the device to unquarantine using its hostname or device
153+ # id.
154+ ################################################################################
155+
156+ format_fql__as_list = phantom .get_format_data (name = "format_fql__as_list" )
157+
158+ parameters = []
159+
160+ # build parameters list for 'query_device' call
161+ for format_fql__item in format_fql__as_list :
162+ parameters .append ({
163+ "limit" : 50 ,
164+ "filter" : format_fql__item ,
165+ })
166+
167+ ################################################################################
168+ ## Custom Code Start
169+ ################################################################################
170+
171+ # Write your custom code here...
172+
173+ ################################################################################
174+ ## Custom Code End
175+ ################################################################################
176+
177+ phantom .act ("query device" , parameters = parameters , name = "query_device" , assets = ["crowdstrike_oauth_api" ], callback = upload_indicator )
178+
179+ return
180+
181+
182+ @phantom .playbook_block ()
183+ def format_executable_denylisting_report (action = None , success = None , container = None , results = None , handle = None , filtered_artifacts = None , filtered_results = None , custom_function = None , loop_state_json = None , ** kwargs ):
184+ phantom .debug ("format_executable_denylisting_report() called" )
185+
186+ ################################################################################
187+ # Format a summary table with the information gathered from the playbook.
188+ ################################################################################
189+
190+ template = """Endpoint Files were denylisted by Splunk SOAR. The table below summarizes the information gathered.\n \n | Device ID | Executable Hash | Action | Denylisting Status | Message |\n | --- | --- | --- | --- | --- |\n %%\n | {0} | {1} | {2} | {3} | {4} |\n %%"""
191+
192+ # parameter list for template variable replacement
193+ parameters = [
194+ "query_device:action_result.data.*.device_id" ,
195+ "filtered-data:input_filter:condition_1:playbook_input:hash" ,
196+ "upload_indicator:action_result.parameter.action" ,
197+ "upload_indicator:action_result.status" ,
198+ "upload_indicator:action_result.message"
199+ ]
200+
201+ ################################################################################
202+ ## Custom Code Start
203+ ################################################################################
204+
205+ # Write your custom code here...
206+
207+ ################################################################################
208+ ## Custom Code End
209+ ################################################################################
210+
211+ phantom .format (container = container , template = template , parameters = parameters , name = "format_executable_denylisting_report" )
212+
213+ file_observables (container = container )
214+
215+ return
216+
217+
218+ @phantom .playbook_block ()
219+ def upload_indicator (action = None , success = None , container = None , results = None , handle = None , filtered_artifacts = None , filtered_results = None , custom_function = None , loop_state_json = None , ** kwargs ):
220+ phantom .debug ("upload_indicator() called" )
221+
222+ # phantom.debug('Action: {0} {1}'.format(action['name'], ('SUCCEEDED' if success else 'FAILED')))
223+
224+ ################################################################################
225+ # Upload indicator that we want CrowdStrike to prevent and watch for all platforms.
226+ ################################################################################
227+
228+ filtered_input_0_hash = phantom .collect2 (container = container , datapath = ["filtered-data:input_filter:condition_1:playbook_input:hash" ])
229+
230+ parameters = []
231+
232+ # build parameters list for 'upload_indicator' call
233+ for filtered_input_0_hash_item in filtered_input_0_hash :
234+ if filtered_input_0_hash_item [0 ] is not None :
235+ parameters .append ({
236+ "ioc" : filtered_input_0_hash_item [0 ],
237+ "action" : "prevent" ,
238+ "source" : "IOC uploaded via Splunk SOAR" ,
239+ "severity" : "MEDIUM" ,
240+ "platforms" : "linux,mac,windows" ,
241+ "description" : "File Indicator blocked from Splunk SOAR" ,
242+ })
243+
244+ ################################################################################
245+ ## Custom Code Start
246+ ################################################################################
247+
248+ # Write your custom code here...
249+
250+ ################################################################################
251+ ## Custom Code End
252+ ################################################################################
253+
254+ phantom .act ("upload indicator" , parameters = parameters , name = "upload_indicator" , assets = ["crowdstrike_oauth_api" ], callback = format_executable_denylisting_report )
255+
256+ return
257+
258+
259+ @phantom .playbook_block ()
260+ def on_finish (container , summary ):
261+ phantom .debug ("on_finish() called" )
262+
263+ format_executable_denylisting_report = phantom .get_format_data (name = "format_executable_denylisting_report" )
264+ file_observables__observable_array = json .loads (_ if (_ := phantom .get_run_data (key = "file_observables:observable_array" )) != "" else "null" ) # pylint: disable=used-before-assignment
265+
266+ output = {
267+ "observable" : file_observables__observable_array ,
268+ "markdown_report" : format_executable_denylisting_report ,
269+ }
270+
271+ ################################################################################
272+ ## Custom Code Start
273+ ################################################################################
274+
275+ # Write your custom code here...
276+
277+ ################################################################################
278+ ## Custom Code End
279+ ################################################################################
280+
281+ phantom .save_playbook_output_data (output = output )
282+
283+ return
0 commit comments