cyber-security-resources/SCOR/threat-response-api-example.py
Christian Clauss 369155c276
TR_query() function signature matches TR_inspect()
`observable` is not being used in this function but `text_block` is so change the function signature of `TR_query()` to match `TR_inspect()` just below.  This resolves both the unused variable name `observable` and the undefined variable name `text_block`.

The user of this example will still need to define their own `TR_enrich()` function.
2020-03-08 07:26:09 +01:00

263 lines
12 KiB
Python

#!/usr/bin/python
import requests, sys, json, copy, fileinput
TR_SESSION = requests.session()
def get_config():
global config
config={}
#options
config['threat_response_token_file']='TR-token.txt'
#credentials
config['threat_response_api_client_id']="<INSERT YOUR API CLIENT ID HERE>"
config['threat_response_api_client_pass']="<INSERT YOUR API CLIENT PASSWORD HERE>"
#server (modify only to select region)
config['threat_response_server']="visibility.amp.cisco.com"
# EU - config['threat_response_server']="visibility.eu.amp.cisco.com
# APJ - config['threat_response_server']="visibility.apjc.amp.cisco.com
#paths (should not need to be modified)
config['threat_response_api_root']="iroh/"
config['threat_response_token_path']="oauth2/token"
config['threat_response_inspect_path']="iroh-inspect/inspect"
config['threat_response_deliberate_path']="iroh-enrich/deliberate/observables"
config['threat_response_observe_path']="iroh-enrich/observe/observables"
#make some useful variables now
config['TRroot']='https://'+config['threat_response_server']+'/'+config['threat_response_api_root']
config['inspect_url'] = config['TRroot']+config['threat_response_inspect_path']
config['token_url']=config['TRroot']+config['threat_response_token_path']
config['deliberate_url'] = config['TRroot']+config['threat_response_deliberate_path']
config['observe_url'] = config['TRroot']+config['threat_response_observe_path']
return(config)
def TR_generate_token():
''' Generate a new access token and write it to disk'''
headers = {'Content-Type':'application/x-www-form-urlencoded', 'Accept':'application/json'}
payload = {'grant_type':'client_credentials'}
response = requests.post(config['token_url'], headers=headers,
auth=(config['threat_response_api_client_id'],
config['threat_response_api_client_pass']),
data=payload)
if TR_unauthorized(response):
sys.exit('Unable to generate new token!\nCheck your CLIENT_ID and CLIENT_PASSWORD')
response_json = response.json()
access_token = response_json['access_token']
with open(config['threat_response_token_file'], 'w') as token_file:
token_file.write(access_token)
return(access_token)
def TR_get_token():
''' Get the access token from disk, or from auth API
'''
for i in range(2):
while True:
try:
with open(config['threat_response_token_file'], 'r') as token_file:
access_token = token_file.read()
return access_token
except FileNotFoundError:
return TR_generate_token()
break
def TR_unauthorized(response):
''' Check the status code of the response
'''
if response.status_code == 401:
return True
return False
def TR_check_auth(function, param):
''' Query the API and validate authentication was successful
If authentication fails, generate a new token and try again
'''
response = function(param)
if TR_unauthorized(response):
print('Auth failed, generating new token.')
config['access_token']=TR_generate_token()
return function(param)
return response
def TR_query(text_block):
''' Pass the functions and parameters to check_auth to query the API
Return the final response
'''
response = TR_check_auth(TR_inspect, text_block)
inspect_output = response.text
response = TR_check_auth(TR_enrich, inspect_output) # TR_enrich() is undefined
return response
def TR_inspect(text_block):
'''Inspect the provided text block and extract observables
'''
headers = {'Authorization':'Bearer {}'.format(config['access_token']),
'Content-Type':'application/json',
'Accept':'application/json'}
inspect_payload = {'content':text_block}
inspect_payload = json.dumps(inspect_payload)
response = TR_SESSION.post(config['inspect_url'], headers=headers, data=inspect_payload)
return response
def TR_deliberate(observables):
''' Query the deliberate API for observable(s)
'''
headers = {'Authorization':'Bearer {}'.format(config['access_token']),
'Content-Type':'application/json',
'Accept':'application/json'}
response = TR_SESSION.post(config['deliberate_url'], headers=headers, data=json.dumps(observables))
return response
def TR_observe(observables):
''' Query the deliberate API for observable(s)
'''
headers = {'Authorization':'Bearer {}'.format(config['access_token']),
'Content-Type':'application/json',
'Accept':'application/json'}
response = TR_SESSION.post(config['observe_url'], headers=headers, data=json.dumps(observables))
return response
def uniq_observables(observables):
uniqd=[]
for obs in observables:
if obs not in uniqd:
uniqd.append(obs)
return(uniqd)
def filter_cleans(results):
cleans = [] #init list of observables with any clean verdict
for obs in results: #loop through observables
if 'verdicts' in obs: #if it has verdicts
for verdict in obs['verdicts']: #loop through verdicts
if verdict['verdict'] == "Clean": #if this one is clean
cleans.append(obs) # add the observable to list of cleans
break #and exit, since it takes only one 'clean' and we found one
for obs in cleans: #go through our list
results.remove(obs) #and for each entry, remove it from the initial dataset
return(results) #return what's left
def main():
#collect settings dict
get_config()
# get the token to use to start
config['access_token']=TR_get_token()
# process input
# init some vars
[text_chunk,observables, judgements, sightings]=['',[],[],[]]
line_idx=0
#chunking loop
for line in fileinput.input():
line_idx=line_idx+1
text_chunk=text_chunk+line
if len(text_chunk) > 2000: #if we hit the 2000 char max guideline, send the chunk for inspection
these_observables = json.loads(TR_check_auth(TR_inspect,text_chunk).text)
#append results to existing data
observables=observables+these_observables
# clear the chunk for reuse
text_chunk=''
#if we're here, we ran out of lines
#inspect the last chunk if it has content
if text_chunk != '':
these_observables = json.loads(TR_check_auth(TR_inspect,text_chunk).text)
#append results to existing data
observables=observables+these_observables
#uniq our observables
observables= uniq_observables(observables)
if len(observables) >50: # semi-arbitrary limit for performance reasons.
# This is a cop-out. We could break the enrichment process into segments the same way we did the Inspection process
# for this example, we'll keep it simple.
sys.exit('NOT doing enrichment! {} is too many observables. Break that input up.'.format(len(observables)))
# init results data structure
results = copy.deepcopy(observables)
# Deliberate
deliberations=json.loads(TR_check_auth(TR_deliberate,observables).text)
for module_results in deliberations['data']:
#add deliberations to results
if 'verdicts' in module_results['data']:
for verdict in module_results['data']['verdicts']['docs']: #loop through the verdicts in this module's output
try:
for obs in results: #loop through our list of observables
if obs['value'] == verdict['observable']['value'] and obs['type'] == verdict['observable']['type']: #if this observable is the same as the one for the current verdict
this_verdict= {'module_name': module_results['module'], 'verdict': verdict['disposition_name'] }
if 'verdicts' not in obs: #if this is the first verdict on this observable,
obs['verdicts']=[] # then make a new blank list of verdicts
obs['verdicts'].append(this_verdict)
break
except Exception as e:
print('{}:{}'.format(str(e)), json.dumps(obs, indent=4))
#filter out anything with any "Clean" verdict
results=filter_cleans(results)
# Observe
observations=json.loads(TR_check_auth(TR_observe,observables).text)#make the observe API call
for observation in observations['data']:#parse the results one observation at a time; actually each module's output at a time
if 'sightings' in observation['data']:#if it had any sightings
for sighting in observation['data']['sightings']['docs']:#go through those sightings
if 'targets' in sighting: # we are only interested in sightings with targets
for sighted_observable in sighting['observables']:#for each observable that was sighted
for obs in results:# look at each observable in our initial list
if obs['value'] == sighted_observable['value'] and obs['type'] == sighted_observable['type']: #if this observable is the same as the one for the current verdict
this_sighting= {'module_name': sighting['source'], 'sighting_count': sighting['count']} #create a new data element in our results set for this sighting
if 'sightings' not in obs: #if this is the first sighting on this observable,
obs['sightings']=[] # then make a new blank list of sightings
obs['sightings'].append(this_sighting) # add this sighting to the list for this observable
break #found our match in a uniq'd list; no need to continue
#summarizing/aggregating
for item in results:
#aggregate verdicts
item['verdicts_count']=0 #init counter
item['verdicts_module_list']=[] #init list of modules
if 'verdicts' in item: #if there are verdicts
for verdict in item['verdicts']: #for each one
item['verdicts_count']=item['verdicts_count']+1 #increment counter
item['verdicts_module_list'].append(verdict['module_name']) #add modulename to list
item['verdicts_module_list']=list(set(item['verdicts_module_list'])) #when done, uniq list of modules
#aggregate sightings
item['sightings_count']=0#init counter
item['sightings_module_list']=[]#init list of modules
if 'sightings' in item: #if there are sightings
for sighting in item['sightings']:#for each one
item['sightings_count']=item['sightings_count']+sighting['sighting_count']#increment counter
item['sightings_module_list'].append(sighting['module_name'])#add modulename to list
item['sightings_module_list']=list(set(item['sightings_module_list']))#when done, uniq list of modules
#filter out unseen observables
results[:]= [item for item in results if item['sightings_count'] > 0] #make new list using only elements where there is at least one sighting
if len(results)>0: #if there are 1+ entries left in the list of results
print('the following observables were found in the input and were seen in your environment:')
url='https://{}/#/investigate?q='.format(config['threat_response_server'])#init url
for item in results: #for each remaining observable
print('{} ({}): {} sightings, {} verdicts'.format(item['value'],item['type'],item['sightings_count'],item['verdicts_count'])) #print the sumamry information
url=url+'{}%3A{}%0A'.format(item['type'], item['value']) #and add it to the CTR URL for an investigation
print('To get more information and investigate these observables in Threat Response, go to the following location:')
print(url)
if __name__ == '__main__':
main()