Files
splunk-security_content/bin/newcontent.py
T

468 lines
17 KiB
Python

#!/usr/bin/python
'''
Helps you create new Splunk Security Content.
'''
from pathlib import Path
from PyInquirer import prompt, Separator
import os
import getpass
from jinja2 import Environment, FileSystemLoader
import uuid
from datetime import date
from os import path
import sys
def detection_wizard(security_content_path,type,TEMPLATE_PATH):
questions = [
{
# get provider
'type': 'list',
'message': 'what kind of detection is this',
'name': 'detection_kind',
'choices': [
{
'name': 'endpoint'
},
{
'name': 'cloud'
},
{
'name': 'application'
},
{
'name': 'network'
},
{
'name': 'web'
},
{
'name': 'experimental'
},
],
'default': 'endpoint'
},
{
'type': 'input',
'message': 'enter detection name',
'name': 'detection_name',
'default': 'Powershell Encoded Command',
},
{
'type': 'input',
'message': 'enter author name',
'name': 'detection_author',
},
{
# get provider
'type': 'list',
'message': 'select a detection type',
'name': 'detection_type',
'choices': [
{
'name': 'TTP'
},
{
'name': 'Anomaly'
},
{
'name': 'Hunting'
},
{
'name': 'Baseline'
},
{
'name': 'Investigation'
},
{
'name': 'Correlation'
}
],
'default': 'TTP'
},
{
# get provider
'type': 'checkbox',
'message': 'select the datamodels used in the detection',
'name': 'datamodels',
'choices': [
{
'name': 'Endpoint',
'checked': True
},
{
'name': 'Authentication'
},
{
'name': 'Change'
},
{
'name': 'Email'
},
{
'name': 'Network_Resolution'
},
{
'name': 'Network_Traffic'
},
{
'name': 'Network_Sessions'
},
{
'name': 'Updates'
},
{
'name': 'Vulnerabilities'
},
{
'name': 'Web'
},
{
'name': 'Risk'
},
],
},
{
# get api_key
'type': 'input',
'message': 'enter search (spl)',
'name': 'detection_search',
'default': '| UPDATE_SPL'
},
{
# get api_key
'type': 'input',
'message': 'enter MITRE ATT&CK Technique IDs related to the detection, comma delimited for multiple',
'name': 'mitre_attack_ids',
'default': 'T1003.002'
},
{
# get provider
'type': 'checkbox',
'message': 'select kill chain phases related to the detection',
'name': 'kill_chain_phases',
'choices': [
{
'name': 'Reconnaissance'
},
{
'name': 'Intrusion'
},
{
'name': 'Exploitation',
'checked': True
},
{
'name': 'Privilege Escalation'
},
{
'name': 'Lateral Movement'
},
{
'name': 'Obfuscation'
},
{
'name': 'Denial of Service'
},
{
'name': 'Exfiltration'
},
],
},
{
# get provider
'type': 'list',
'message': 'security_domain for detection',
'name': 'security_domain',
'choices': [
{
'name': 'access'
},
{
'name': 'endpoint'
},
{
'name': 'network'
},
{
'name': 'threat'
},
{
'name': 'identity'
},
{
'name': 'audit'
},
],
'default': 'endpoint'
},
]
answers = prompt(questions)
mitre_attack_id = [x.strip() for x in answers['mitre_attack_ids'].split(',')]
print(mitre_attack_id)
j2_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH), # nosemgrep
trim_blocks=True)
answers['products'] = ['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud']
answers['references'] = []
# grab some vars for the test
detection_kind = answers['detection_kind']
# write a detection example
template = j2_env.get_template('detection.j2')
detection_name = answers['detection_name']
detection_file_name = detection_name.replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower()
output_path = path.join(security_content_path, 'detections/' + detection_kind + '/' + detection_file_name + '.yml')
output = template.render(uuid=uuid.uuid1(), date=date.today().strftime('%Y-%m-%d'),
author=answers['detection_author'], name=answers['detection_name'],
description='\n\tUPDATE_DESCRIPTION\n\tWHAT IS THIS?\n\tWHAT DOES IT LOOK LIKE?\n\tHOW DO YOU TRIAGE IT?', how_to_implement='UPDATE_HOW_TO_IMPLEMENT', known_false_positives='UPDATE_KNOWN_FALSE_POSITIVES',
references=' ',datamodels=answers['datamodels'],
search= answers['detection_search'] + ' | `' + detection_file_name + '_filter`',
type=answers['detection_type'], analytic_story_name='UPDATE_STORY_NAME', mitre_attack_id=mitre_attack_id,
kill_chain_phases=answers['kill_chain_phases'], dataset_url='UPDATE_DATASET_URL',
products=answers['products'], security_domain=answers['security_domain'])
with open(output_path, 'w', encoding="utf-8") as f:
f.write(output)
print("\n> contentctl wrote the detection to: {0}\n".format(output_path))
questions = [
{
'type': 'confirm',
'message': 'would you like to configure the test file for detection: {0}'.format(answers['detection_name']),
'name': 'continue',
'default': True,
},
{
'type': 'input',
'message': 'enter pass condition for the test of detection: {0}'.format(answers['detection_name']),
'name': 'pass_condition',
'default': '| stats count | where count > 0',
'when': lambda answers: answers['continue'],
},
{
'type': 'input',
'message': 'enter earliest_time for the test of detection: {0}'.format(answers['detection_name']),
'name': 'earliest_time',
'default': '-24h',
'when': lambda answers: answers['continue'],
},
{
'type': 'input',
'message': 'enter latest_time for the test of detection: {0}'.format(answers['detection_name']),
'name': 'latest_time',
'default': 'now',
'when': lambda answers: answers['continue'],
},
]
answers = prompt(questions)
if answers['continue']:
# and a corresponding test files
template = j2_env.get_template('test.j2')
test_name = detection_file_name + '.test.yml'
output_path = path.join(security_content_path, 'tests/' + detection_kind + '/' + test_name)
output = template.render(name=detection_name + ' Unit Test',
detection_name=detection_name,
detection_path= detection_kind + '/' + detection_file_name + '.yml', pass_condition=answers['pass_condition'],
earliest_time=answers['earliest_time'], latest_time=answers['latest_time'], file_name='UPDATE_FILE_NAME',
splunk_source='UPDATE_SPLUNK_SOURCE',splunk_sourcetype='UPDATE_SPLUNK_SOURCETYPE',dataset_url='UPDATE_DATASET_URL')
with open(output_path, 'w', encoding="utf-8") as f:
f.write(output)
else:
# and a corresponding test files
template = j2_env.get_template('test.j2')
test_name = detection_file_name + '.test.yml'
output_path = path.join(security_content_path, 'tests/' + detection_kind + '/' + test_name)
output = template.render(name=detection_name + ' Unit Test',
detection_name=detection_name,
detection_path=detection_kind + '/' + detection_file_name + '.yml', pass_condition='| stats count | where count > 0',
earliest_time='-24h', latest_time='now',file_name='UPDATE_FILE_NAME', splunk_source='UPDATE_SPLUNK_SOURCE',
splunk_sourcetype='UPDATE_SPLUNK_SOURCETYPE', dataset_url='UPDATE_DATASET_URL' )
with open(output_path, 'w', encoding="utf-8") as f:
f.write(output)
print("\n> contentctl wrote the test for this detection to: {0}\n".format(output_path))
def story_wizard(security_content_path,type, TEMPLATE_PATH):
questions = [
{
'type': 'input',
'message': 'enter story name',
'name': 'story_name',
'default': 'Suspicious Powershell Behavior',
},
{
'type': 'input',
'message': 'enter author name',
'name': 'story_author',
},
{
'type': 'checkbox',
'message': 'select a category',
'name': 'category',
'choices': [
{
'name': 'Adversary Tactics',
'checked': True
},
{
'name': 'Account Compromise'
},
{
'name': 'Unauthorized Software'
},
{
'name': 'Best Practices'
},
{
'name': 'Cloud Security'
},
{
'name': 'Command and Control'
},
{
'name': 'Lateral Movement'
},
{
'name': 'Ransomware'
},
{
'name': 'Privilege Escalation'
},
],
},
{
# get provider
'type': 'list',
'message': 'select a use case',
'name': 'usecase',
'choices': [
{
'name': 'Advanced Threat Detection',
'checked': True
},
{
'name': 'Security Monitoring'
},
{
'name': 'Compliance'
},
{
'name': 'Insider Threat'
},
{
'name': 'Application Security'
},
{
'name': 'Other'
},
],
},
]
answers = prompt(questions)
j2_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH), # nosemgrep
trim_blocks=True)
template = j2_env.get_template('story.j2')
answers['products'] = ['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud']
answers['references'] = []
story_name = answers['story_name']
story_file_name = story_name.replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower()
output_path = path.join(security_content_path, 'stories/' + story_file_name + '.yml')
output = template.render(uuid=uuid.uuid1(), date=date.today().strftime('%Y-%m-%d'),
author=answers['story_author'], name=answers['story_name'], description='UPDATE_DESCRIPTION',
narrative='UPDATE_NARRATIVE', references=['https://www.destroyallsoftware.com/talks/wat'],analytic_story_name=answers['story_name'],
categories=answers['category'], usecase=answers['usecase'], products=answers['products'])
with open(output_path, 'w', encoding="utf-8") as f:
f.write(output)
print("contentctl wrote a example story to: {0}".format(output_path))
def create_example(security_content_path,type, TEMPLATE_PATH):
getpass.getuser()
j2_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH), # nosemgrep
trim_blocks=True)
if type == 'detection':
# write a detection example
template = j2_env.get_template('detection.j2')
detection_name = getpass.getuser() + '_' + type + '.yml.example'
output_path = path.join(security_content_path, 'detections/endpoint/' + detection_name)
output = template.render(uuid=uuid.uuid1(), date=date.today().strftime('%Y-%m-%d'),
author='UPDATE_AUTHOR', name=getpass.getuser().capitalize() + ' ' + type.capitalize(),
description='|\n\tUPDATE_DESCRIPTION\n\tWHAT IS THIS?\n\tWHAT DOES IT LOOK LIKE?\n\tHOW DO YOU TRIAGE IT?',
how_to_implement='UPDATE_HOW_TO_IMPLENT',
known_false_positives='UPDATE_KNOWN_FALSE_POSITIVES',
references=['https://html5zombo.com/'],
datamodels=['Endpoint'], search='| UPDATE_SPL | `' + getpass.getuser() + '_' + type + '_filter`',
type='TTP', analytic_story_name=' UPDATE_STORY_NAME', mitre_attack_id = 'T1003.01',
kill_chain_phases=['Exploitation'], dataset_url='UPDATE_DATASET_URL',
products=['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud'])
with open(output_path, 'w', encoding="utf-8") as f:
f.write(output)
print("contentctl wrote a example detection to: {0}".format(output_path))
# and a corresponding test files
template = j2_env.get_template('test.j2')
test_name = getpass.getuser() + '_' + type + '.test.yml.example'
output_path = path.join(security_content_path, 'tests/endpoint/' + test_name)
output = template.render(name=getpass.getuser().capitalize() + ' ' + type.capitalize() + ' Unit Test',
detection_name=getpass.getuser().capitalize() + ' ' + type.capitalize(),
detection_path='endpoint/' + detection_name, pass_condition='| stats count | where count > 0',
earliest_time='-24h', latest_time='now', file_name='UPDATE_FILE_NAME', splunk_source='UPDATE_SPLUNK_SOURCE',
splunk_sourcetype='UPDATE_SPLUNK_SOURCETYPE',dataset_url='UPDATE_DATASET_URL')
with open(output_path, 'w', encoding="utf-8") as f:
f.write(output)
print("contentctl wrote a example test for this detection to: {0}".format(output_path))
elif type == 'story':
# write a story example
template = j2_env.get_template('story.j2')
story_name = getpass.getuser() + '_' + type + '.yml.example'
output_path = path.join(security_content_path, 'stories/' + story_name)
output = template.render(uuid=uuid.uuid1(), date=date.today().strftime('%Y-%m-%d'),
author='UPDATE_AUTHOR', name=getpass.getuser().capitalize() + ' ' + type.capitalize(),
description='UPDATE_DESCRIPTION',
narrative='UPDATE_NARRATIVE',
references=['https://www.destroyallsoftware.com/talks/wat'], analytic_story_name=getpass.getuser().capitalize() + ' ' + type.capitalize(),
categories=['Adversary Tactics'], usecase='Advanced Threat Detection', products=['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud'])
with open(output_path, 'w', encoding="utf-8") as f:
f.write(output)
print("contentctl wrote a example story to: {0}".format(output_path))
def new(security_content_path, VERBOSE, type, example_only):
valid_content_objects = ['detection','story']
if type not in valid_content_objects:
print("ERROR: content type: {0} is not valid, please use: {1}".format(type, str(valid_content_objects)))
sys.exit(1)
TEMPLATE_PATH = path.join(security_content_path, 'bin/jinja2_templates')
if example_only:
create_example(security_content_path,type, TEMPLATE_PATH)
sys.exit(0)
if type == 'detection':
detection_wizard(security_content_path, type, TEMPLATE_PATH)
elif type == 'story':
story_wizard(security_content_path, type, TEMPLATE_PATH)
print("WARNING do not forget to replace the UPDATE_* values with the correct information on the files!\ncompleted..")