from datetime import datetime, timedelta import argparse import requests import helpers import json import sys import os # As it stands, this analyzer does not support querying for mixed-case fields without disregarding case completely. # So the current version will only support querying for all-lowercase alphanumerical values. # default usage is: # python3 elasticsearch.py '{"artifactType":"hash", "value":"*"}' # To use outside of a Security Onion box, pass in '-c test.yaml' at the end # of the above command to give this analyzer some test values. You may edit the # values in the test.yaml file freely. def checkConfigRequirements(conf): # if the user hasn't given valid configurables, quit. if not conf['num_results']: sys.exit(126) if not conf['time_delta_minutes']: sys.exit(126) if (not conf['auth_user'] or not conf['auth_pwd']) and not conf['api_key']: sys.exit(126) if not conf['index']: sys.exit(126) if not conf['base_url']: sys.exit(126) if not conf['timestamp_field_name']: sys.exit(126) if not conf['cert_path']: sys.exit(126) return True def buildReq(conf, input): # structure a query to send to the Elasticsearch machine # based off of user configurable values num_results = conf['num_results'] if conf['map'] is not None: mappings = conf['map'] else: mappings = dict() cur_time = datetime.now() start_time = cur_time - timedelta(minutes=int(conf['time_delta_minutes'])) if input['artifactType'] in mappings: type = mappings[input['artifactType']] else: type = input['artifactType'] query = { "from": 0, "size": num_results, "query": { "bool": { "must": [{ "wildcard": { type: input['value'], }, } ], "filter": { "range": { conf['timestamp_field_name']: { "gte": start_time.strftime('%Y-%m-%dT%H:%M:%S'), "lte": cur_time.strftime('%Y-%m-%dT%H:%M:%S') } } } } } } return json.dumps(query) def sendReq(conf, query): # send configured query with even more user specification headers = {} url = conf['base_url'] + conf['index'] + '/_search' uname = conf['auth_user'] pwd = conf['auth_pwd'] apikey = conf['api_key'] cert_path = conf['cert_path'] if pwd and uname: headers = { 'Content-Type': 'application/json', } response = requests.post(str(url), auth=( uname, pwd), verify=cert_path, data=query, headers=headers) elif apikey: headers = { 'Content-Type': 'application/json', 'Authorization': f"Apikey {apikey}" } response = requests.post( str(url), verify=cert_path, data=query, headers=headers) return response.json() def prepareResults(raw): # returns raw API response, amount of hits found, and status of request in order summary = f"Documents returned: {len(raw['hits']['hits'])}" status = 'info' return {'response': raw, 'summary': summary, 'status': status} def analyze(conf, input): checkConfigRequirements(conf) data = json.loads(input) query = buildReq(conf, data) response = sendReq(conf, query) return prepareResults(response) def main(): dir = os.path.dirname(os.path.realpath(__file__)) parser = argparse.ArgumentParser( description='Search Elasticsearch for a given artifact?') parser.add_argument('artifact', help='required artifact') parser.add_argument('-c', '--config', metavar='CONFIG_FILE', default=dir + '/elasticsearch.yaml', help='optional config file to use instead of the default config file') args = parser.parse_args() if args.artifact: results = analyze(helpers.loadConfig(args.config), args.artifact) print(json.dumps(results)) if __name__ == '__main__': main()