Files
splunk-security_content/bin/content_changer.py
T
2021-04-13 10:20:29 +02:00

218 lines
7.5 KiB
Python

import glob
import yaml
import sys
import re
import argparse
import requests
import csv
from requests.auth import HTTPBasicAuth
from urllib3.exceptions import InsecureRequestWarning
from os import path
BASE_URL = f"https://ip:8089"
SEARCH_PARSER_ENDPOINT = f"/services/search/parser"
USER = f"admin"
PASSWORD = f"password"
parsed_fields = dict()
def load_objects(file_path, REPO_PATH):
files = []
manifest_files = path.join(path.expanduser(REPO_PATH), file_path)
for file in sorted(glob.glob(manifest_files)):
files.append(load_file(file))
return files
def load_file(file_path):
with open(file_path, 'r', encoding="utf-8") as stream:
try:
file = list(yaml.safe_load_all(stream))[0]
except yaml.YAMLError as exc:
print(exc)
sys.exit("ERROR: reading {0}".format(file_path))
return file
def load_content(old_project):
# process all detections
detections = []
detections = load_objects("detections/*/*.yml", old_project)
detections.extend(load_objects("detections/*/*/*.yml", old_project))
#print(len(detections))
return detections
def analysis_detection(detections):
for detection in detections:#
if detection['type'] != 'streaming':
#if detection['name'] == 'Attempted Credential Dump From Registry via Reg exe':
print('Analysis Detection: ' + detection['name'])
call_splunk_parser_api(detection)
# sort parsed fields by occurence
sorted_dict = {k: v for k, v in sorted(parsed_fields.items(), key=lambda item: item[1], reverse=True)}
with open('output_fields_ordered_by_usage.csv', mode='w') as csv_file:
writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
writer.writerow(['field_name', 'occurence'])
for field_name in sorted_dict:
writer.writerow([field_name, sorted_dict[field_name]])
# sort parsed fields by name
sorted_dict_2 = sorted(parsed_fields.items())
with open('output_fields_ordered_by_keys.csv', mode='w') as csv_file:
writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
writer.writerow(['field_name', 'occurence'])
for field_name in sorted_dict_2:
writer.writerow([field_name[0], field_name[1]])
def call_splunk_parser_api(detection):
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
spl = ''
if detection['search'].startswith('| tstats'):
spl = detection['search']
else:
spl = 'search ' + detection['search']
data = {
"output_mode": "json",
"q": spl,
"parse_only": "true"
}
response = requests.post(BASE_URL + SEARCH_PARSER_ENDPOINT, data=data, auth=(USER, PASSWORD), verify=False, headers={"Content-Type": "application/x-www-form-urlencoded"})
if response.status_code != 200:
print(response.json())
print('ERROR: parser endpoint problems')
return
parse_commands(response.json())
def parse_commands(api_response):
tmp_parsed_fields = {}
last_stat_command = ''
rename_command_after_stats_arr = []
for command in api_response['commands']:
if command['command'] in ['tstats', 'stats', 'table']:
last_stat_command = command
if (command['command'] == 'rename') and last_stat_command:
rename_command_after_stats_arr.append(command)
if not last_stat_command:
print('ERROR: could not find stats table or tasts command')
return
# last command table
if last_stat_command['command'] == 'table':
matches = re.findall(r'([0-9a-zA-Z_]+)', last_stat_command['rawargs'])
for match in matches:
if match in tmp_parsed_fields:
tmp_parsed_fields[match] = tmp_parsed_fields[match] + 1
else:
tmp_parsed_fields[match] = 1
# last command stats
if last_stat_command['command'] == 'stats':
match = re.match(r'(.*)by', last_stat_command['rawargs'])
if match:
args_one = match.group(1)
matches = re.findall(r'(?:min|max|values)\(([0-9a-zA-Z_]+)\)', args_one)
if matches:
for match in matches:
if match in tmp_parsed_fields:
tmp_parsed_fields[match] = tmp_parsed_fields[match] + 1
else:
tmp_parsed_fields[match] = 1
match = re.match(r'.*by(.*)$', last_stat_command['rawargs'])
if match:
args_two = match.group(1)
matches = re.findall(r'([0-9a-zA-Z_]+)', args_two)
if matches:
for match in matches:
if match in tmp_parsed_fields:
tmp_parsed_fields[match] = tmp_parsed_fields[match] + 1
else:
tmp_parsed_fields[match] = 1
# tstats command
if last_stat_command['command'] == 'tstats':
match = re.match(r'(.*)(?:from|FROM)', last_stat_command['rawargs'])
if match:
args_one = match.group(1)
matches = re.findall(r'(?:min|max|values)\(([0-9a-zA-Z_]+)\.([0-9a-zA-Z_]+)\)', args_one)
if matches:
for match in matches:
field = match[0] + '.' + match[1]
if field in tmp_parsed_fields:
tmp_parsed_fields[field] = tmp_parsed_fields[field] + 1
else:
tmp_parsed_fields[field] = 1
match = re.match(r'.*by(.*)$', last_stat_command['rawargs'])
if match:
args_two = match.group(1)
matches = re.findall(r'([0-9_a-zA-Z]+)\.([0-9a-zA-Z_]+)', args_two)
if matches:
for match in matches:
field = match[0] + '.' + match[1]
if field in tmp_parsed_fields:
tmp_parsed_fields[field] = tmp_parsed_fields[field] + 1
else:
tmp_parsed_fields[field] = 1
match = re.match(r'.*where(.*)by.*$', last_stat_command['rawargs'])
if match:
args_three = match.group(1)
matches = re.findall(r'([0-9_a-zA-Z]+)\.([0-9a-zA-Z_]+)=', args_three)
if matches:
for match in matches:
field = match[0] + '.' + match[1]
if field in tmp_parsed_fields:
tmp_parsed_fields[field] = tmp_parsed_fields[field] + 1
else:
tmp_parsed_fields[field] = 1
# rename occured
for rename_command_after_stats in rename_command_after_stats_arr:
if rename_command_after_stats:
renamed_field = {}
matches = re.findall(r'(?:(([0-9a-zA-Z_]+)\s+as\s+([0-9a-zA-Z_]+)))', rename_command_after_stats['rawargs'])
for match in matches:
renamed_field[match[1]] = match[2]
for key in renamed_field:
if key in tmp_parsed_fields:
tmp_parsed_fields[renamed_field[key]] = tmp_parsed_fields.pop(key)
# write to global parsed fields var
for key in tmp_parsed_fields:
if key in parsed_fields:
parsed_fields[key] = parsed_fields[key] + tmp_parsed_fields[key]
else:
parsed_fields[key] = tmp_parsed_fields[key]
def main(project):
detections = load_content(project)
analysis_detection(detections)
if __name__ == "__main__":
main("../")