mirror of
https://github.com/splunk/security_content
synced 2026-06-08 17:32:49 +00:00
Branch was auto-updated.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# PR Template for new Detections
|
||||
|
||||
For Authors:
|
||||
1. Make sure that CI/CD [detection-testing and build-and-validate](https://github.com/splunk/security_content/actions) jobs passed ✔️.
|
||||
- [ ] Make sure that CI/CD [detection-testing and build-and-validate](https://github.com/splunk/security_content/actions) jobs passed ✔️.
|
||||
|
||||
For Reviewers:
|
||||
- [ ] Verify CI/CD jobs have passed without errors.
|
||||
- [ ] Validate SPL logic.
|
||||
- [ ] Validate tags, description, and how to implement.
|
||||
- [ ] Validate name patches `<platform>_<mitre att&ck technique>_<short description>`
|
||||
- [ ] Validate name matches `<platform>_<mitre att&ck technique>_<short description>`
|
||||
- [ ] Verify references match analytic.
|
||||
- [ ] Is there an Atomic Test?
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
cd bin/automated_detection_testing/ci/detection_testing_batch
|
||||
source .venv/bin/activate
|
||||
|
||||
python3 detection_testing_execution.py run -c prior_config/${{ matrix.manifest_filename}} --splunkbase_username ${{ secrets.SPLUNKBASE_TESTING_USERNAME }} --splunkbase_password ${{ secrets.SPLUNKBASE_TESTING_KEY }}
|
||||
python3 detection_testing_execution.py run -c prior_config/${{ matrix.manifest_filename}}
|
||||
|
||||
|
||||
- name: Upload Test Results Files
|
||||
|
||||
+169
-87
@@ -1,6 +1,7 @@
|
||||
import argparse
|
||||
import copy
|
||||
import csv
|
||||
from ctypes.wintypes import tagRECT
|
||||
import json
|
||||
import os
|
||||
import queue
|
||||
@@ -19,6 +20,8 @@ from tempfile import mkdtemp
|
||||
from timeit import default_timer as timer
|
||||
from typing import Union
|
||||
from urllib.parse import urlparse
|
||||
import signal
|
||||
|
||||
|
||||
import docker
|
||||
import requests
|
||||
@@ -30,7 +33,7 @@ import modules.new_arguments2
|
||||
from modules import (container_manager, new_arguments2,
|
||||
testing_service, validate_args)
|
||||
from modules.github_service import GithubService
|
||||
from modules.validate_args import validate, validate_and_write
|
||||
from modules.validate_args import validate, validate_and_write, ES_APP_NAME
|
||||
|
||||
SPLUNK_CONTAINER_APPS_DIR = "/opt/splunk/etc/apps"
|
||||
index_file_local_path = "indexes.conf.tar"
|
||||
@@ -45,27 +48,60 @@ datamodel_file_container_path = os.path.join(
|
||||
authorizations_file_local_path = "authorize.conf.tar"
|
||||
authorizations_file_container_path = "/opt/splunk/etc/system/local"
|
||||
|
||||
CONTAINER_APP_DIRECTORY = "apps"
|
||||
|
||||
MAX_RECOMMENDED_CONTAINERS_BEFORE_WARNING = 2
|
||||
|
||||
|
||||
def download_file_from_http(url:str, target:str)->None:
|
||||
#Will just overwrite an existing file
|
||||
|
||||
|
||||
|
||||
def download_file_from_http(url:str, destination_file:str, overwrite_file:bool=False)->None:
|
||||
if os.path.exists(destination_file) and overwrite_file is False:
|
||||
print(f"[{destination_file}] already exists...using cached version")
|
||||
return
|
||||
print(f"downloading to [{destination_file}]")
|
||||
file_to_download = requests.get(url, stream=True)
|
||||
with open(target, "wb") as output:
|
||||
with open(destination_file, "wb") as output:
|
||||
for piece in file_to_download.iter_content(chunk_size=(1024*1024)):
|
||||
output.write(piece)
|
||||
|
||||
|
||||
def copy_local_apps_to_directory(apps: dict[str, dict], target_directory) -> None:
|
||||
for key, item in apps.items():
|
||||
def copy_local_apps_to_directory(apps: dict[str, dict], splunkbase_username:tuple[str,None] = None, splunkbase_password:tuple[str,None] = None, mock:bool = False, target_directory:str = "apps") -> str:
|
||||
if mock is True:
|
||||
target_directory = os.path.join("prior_config", target_directory)
|
||||
|
||||
# Remove the apps directory or the prior config directory. If it's just an apps directory, then we don't want
|
||||
#to remove that.
|
||||
shutil.rmtree(target_directory, ignore_errors=True)
|
||||
try:
|
||||
# Make sure the directory exists. If it already did, that's okay. Don't delete anything from it
|
||||
# We want to re-use previously downloaded apps
|
||||
os.makedirs(target_directory, exist_ok = True)
|
||||
|
||||
except Exception as e:
|
||||
raise(Exception(f"Some error occured when trying to make the {target_directory}: [{str(e)}]"))
|
||||
|
||||
|
||||
for key, item in apps.items():
|
||||
|
||||
# These apps are URLs that will be passed. The apps will be downloaded and installed by the container
|
||||
# # Get the file from an http source
|
||||
splunkbase_info = True if ('app_number' in item and item['app_number'] is not None and
|
||||
'app_version' in item and item['app_version'] is not None) else False
|
||||
splunkbase_creds = True if (splunkbase_username is not None and
|
||||
splunkbase_password is not None) else False
|
||||
can_download_from_splunkbase = splunkbase_info and splunkbase_creds
|
||||
|
||||
|
||||
|
||||
#local apps can either have a local_path or an http_path
|
||||
if 'local_path' in item:
|
||||
source_path = os.path.abspath(os.path.expanduser(item['local_path']))
|
||||
base_name = os.path.basename(source_path)
|
||||
dest_path = os.path.join(target_directory, base_name)
|
||||
try:
|
||||
print(f"copying {os.path.relpath(source_path)} to {os.path.relpath(dest_path)}")
|
||||
shutil.copy(source_path, dest_path)
|
||||
item['local_path'] = dest_path
|
||||
except shutil.SameFileError as e:
|
||||
@@ -77,25 +113,34 @@ def copy_local_apps_to_directory(apps: dict[str, dict], target_directory) -> Non
|
||||
source_path, dest_path, str(e)), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# These apps are URLs that will be passed. The apps will be downloaded and installed by the container
|
||||
# # Get the file from an http source
|
||||
# elif 'http_path' in item:
|
||||
# http_path = item['http_path']
|
||||
# try:
|
||||
# url_parse_obj = urlparse(http_path)
|
||||
# path_after_host = url_parse_obj[2].rstrip('/') #removes / at the end, if applicable
|
||||
# base_name = path_after_host.rpartition('/')[-1] #just get the file name
|
||||
# dest_path = os.path.join(target_directory, base_name) #write the whole path
|
||||
# download_file_from_http(http_path, dest_path)
|
||||
# #we need to updat the local path because this is used to copy it into the container later
|
||||
# item['local_path'] = dest_path
|
||||
# except Exception as e:
|
||||
# print("Error trying to download %s @ %s: [%s]. This app is required.\n\tQuitting..."%(key, http_path, str(e)),file=sys.stderr)
|
||||
# sys.exit(1)
|
||||
# else:
|
||||
# print("Error - trying to install a local app that does not have 'local_path' or 'http_path'.\n\tQuitting...")
|
||||
# sys.exit(1)
|
||||
|
||||
elif can_download_from_splunkbase is True:
|
||||
#Don't do anything, this will be downloaded from splunkbase
|
||||
pass
|
||||
elif splunkbase_info is True and splunkbase_creds is False and mock is True:
|
||||
#Don't need to do anything, when this actually runs the apps will be downloaded from Splunkbase
|
||||
#There is another opportunity to provide the creds then
|
||||
pass
|
||||
elif 'http_path' in item and can_download_from_splunkbase is False:
|
||||
http_path = item['http_path']
|
||||
try:
|
||||
url_parse_obj = urlparse(http_path)
|
||||
path_after_host = url_parse_obj[2].rstrip('/') #removes / at the end, if applicable
|
||||
base_name = path_after_host.rpartition('/')[-1] #just get the file name
|
||||
dest_path = os.path.join(target_directory, base_name) #write the whole path
|
||||
download_file_from_http(http_path, dest_path)
|
||||
#we need to update the local path because this is used to copy it into the container later
|
||||
item['local_path'] = dest_path
|
||||
#Remove the HTTP Path, we will use the local_path instead
|
||||
except Exception as e:
|
||||
print("Error trying to download %s @ %s: [%s]. This app is required.\n\tQuitting..."%(key, http_path, str(e)),file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
elif splunkbase_info is False:
|
||||
print(f"Error - trying to install an app [{key}] that does not have 'local_path', 'http_path', "
|
||||
"or 'app_version' and 'app_number' for installing from Splunkbase.\n\tQuitting...")
|
||||
sys.exit(1)
|
||||
return target_directory
|
||||
|
||||
|
||||
def ensure_security_content(branch: str, commit_hash: Union[str,None], pr_number: Union[int, None], persist_security_content: bool) -> tuple[GithubService, bool]:
|
||||
@@ -183,7 +228,7 @@ def generate_escu_app(persist_security_content: bool = False) -> str:
|
||||
|
||||
else:
|
||||
os.mkdir("slim_packaging")
|
||||
os.mkdir("apps")
|
||||
|
||||
try:
|
||||
SPLUNK_PACKAGING_TOOLKIT_URL = "https://download.splunk.com/misc/packaging-toolkit/splunk-packaging-toolkit-0.9.0.tar.gz"
|
||||
SPLUNK_PACKAGING_TOOLKIT_FILENAME = 'splunk-packaging-toolkit-latest.tar.gz'
|
||||
@@ -216,7 +261,7 @@ def generate_escu_app(persist_security_content: bool = False) -> str:
|
||||
|
||||
ret = subprocess.run("; ".join(commands),
|
||||
shell=True, capture_output=True)
|
||||
if ret.returncode != 0:
|
||||
if ret.returncode != 0:
|
||||
print("Command List:\n%s" % (commands))
|
||||
print("Error generating new ESCU Package.\n\tQuitting and dumping error...\n[%s]" % (
|
||||
ret.stderr.decode('utf-8')), file=sys.stderr)
|
||||
@@ -226,27 +271,10 @@ def generate_escu_app(persist_security_content: bool = False) -> str:
|
||||
return output_file_path_from_root
|
||||
|
||||
|
||||
|
||||
def finish_mock(settings: dict, detections: list[str], output_file_template: str = "prior_config/config_tests_%d.json")->bool:
|
||||
num_containers = settings['num_containers']
|
||||
|
||||
try:
|
||||
# Remove the prior config directory if it exists. If not, continue
|
||||
shutil.rmtree("prior_config", ignore_errors=True)
|
||||
|
||||
# We want to make the prior_config directory and the prior_config/apps directory
|
||||
os.makedirs("prior_config/apps")
|
||||
except FileExistsError as e:
|
||||
print("Directory priorconfig/apps exists, but we just deleted it!\n\tQuitting...", file=sys.stderr)
|
||||
return False
|
||||
except Exception as e:
|
||||
print("Some error occured when trying to make the configs folder: [%s]\n\tQuitting..." % (
|
||||
str(e)), file=sys.stderr)
|
||||
return False
|
||||
|
||||
# Copy the apps to the appropriate local. This will also update
|
||||
# the app paths in settings['local_apps']
|
||||
copy_local_apps_to_directory(settings['local_apps'], "prior_config/apps")
|
||||
|
||||
for output_file_index in range(0, num_containers):
|
||||
fname = output_file_template % (output_file_index)
|
||||
|
||||
@@ -285,9 +313,8 @@ def finish_mock(settings: dict, detections: list[str], output_file_template: str
|
||||
# Make sure that it still validates after all of the changes
|
||||
|
||||
try:
|
||||
with open(fname, 'w') as cfg:
|
||||
validated_settings, b = validate_and_write(
|
||||
mock_settings, cfg)
|
||||
with open(fname, 'w') as outfile:
|
||||
validated_settings, b = validate_and_write(configuration=mock_settings, output_file = outfile, strip_credentials=True)
|
||||
if validated_settings is None:
|
||||
print(
|
||||
"There was an error validating the updated mock settings.\n\tQuitting...", file=sys.stderr)
|
||||
@@ -331,23 +358,40 @@ def main(args: list[str]):
|
||||
|
||||
credentials_needed = False
|
||||
credential_error = False
|
||||
if len(settings['splunkbase_apps']) > 0:
|
||||
credentials_needed = True
|
||||
|
||||
|
||||
if settings['splunkbase_username'] == None and credentials_needed:
|
||||
print("Error - you have listed apps to download from Splunkbase but have "\
|
||||
"not provided --splunkbase_username via the command line or config file.",file=sys.stderr)
|
||||
credential_error = True
|
||||
|
||||
if settings['splunkbase_username'] == None or settings['splunkbase_password'] == None:
|
||||
|
||||
missing_credentials = []
|
||||
if settings['splunkbase_username'] == None:
|
||||
missing_credentials.append("--splunkbase_username")
|
||||
if settings['splunkbase_password'] == None:
|
||||
missing_credentials.append("--splunkbase_password")
|
||||
|
||||
missing_credentials_string = '\n\t'.join(missing_credentials)
|
||||
|
||||
splunkbase_only_apps = []
|
||||
for app,content in settings['apps'].items():
|
||||
if 'local_path' not in content and 'http_path' not in content:
|
||||
splunkbase_only_apps.append(app)
|
||||
if len(splunkbase_only_apps) != 0:
|
||||
print(f"Error - you have attempted to install the following apps: {splunkbase_only_apps}, "
|
||||
"but you have not provided a local_path or an http_path in the config file. Normally, "
|
||||
"we would download these from Splunkbase, but the following credentials are "
|
||||
f"missing:\n\t{missing_credentials_string}\n Please provide them on the command line "
|
||||
"or in the config file.\n\tQuitting...")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"You have listed apps to install but have "\
|
||||
f"not provided\n\t{missing_credentials_string} \nvia the command line or config file. "
|
||||
f"We will download these files from S3 rather than Splunkbase.")
|
||||
else:
|
||||
|
||||
print(f"You have listed apps to install and provided Splunkbase credentials. "\
|
||||
f"These apps will be downloaded and installed from Splunkbase!")
|
||||
|
||||
if settings['splunkbase_password'] == None and credentials_needed:
|
||||
print("Error - you have listed apps to download from Splunkbase but have "\
|
||||
"not provided --splunkbase_password via the command line or config file.",file=sys.stderr)
|
||||
credential_error = True
|
||||
|
||||
if credential_error:
|
||||
print("Please supply the required credentials to continue.\n\tQuitting...",file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
FULL_DOCKER_HUB_CONTAINER_NAME = "splunk/splunk:%s" % settings['container_tag']
|
||||
@@ -405,42 +449,36 @@ def main(args: list[str]):
|
||||
print("***This run will test [%d] detections!***"%(len(all_test_files)))
|
||||
|
||||
|
||||
#Set up the directory that will be used to store the local apps/apps we build
|
||||
local_volume_absolute_path = os.path.abspath(
|
||||
os.path.join(os.getcwd(), "apps"))
|
||||
try:
|
||||
# remove the directory first
|
||||
shutil.rmtree(local_volume_absolute_path, ignore_errors=True)
|
||||
os.mkdir(local_volume_absolute_path)
|
||||
except FileExistsError as e:
|
||||
# Directory already exists, do nothing
|
||||
pass
|
||||
except Exception as e:
|
||||
print("Error creating the apps folder [%s]: [%s]\n\tQuitting..."
|
||||
% (local_volume_absolute_path, str(e)), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
#Add the info about the mount
|
||||
mounts = [{"local_path": local_volume_absolute_path,
|
||||
"container_path": "/tmp/apps", "type": "bind", "read_only": True}]
|
||||
|
||||
|
||||
|
||||
# Check to see if we want to install ESCU and whether it was preeviously generated and we should use that file
|
||||
if 'SPLUNK_ES_CONTENT_UPDATE' in settings['local_apps'] and settings['local_apps']['SPLUNK_ES_CONTENT_UPDATE']['local_path'] is not None:
|
||||
if ES_APP_NAME in settings['apps'] and settings['apps'][ES_APP_NAME]['local_path'] is not None:
|
||||
# Using a pregenerated ESCU, no need to build it
|
||||
pass
|
||||
elif 'SPLUNK_ES_CONTENT_UPDATE' not in settings['local_apps']:
|
||||
print("%s was not found in %s. We assume this is an error and shut down.\n\t"
|
||||
"Quitting..." % ('SPLUNK_ES_CONTENT_UPDATE', "settings['local_apps']"), file=sys.stderr)
|
||||
|
||||
elif ES_APP_NAME not in settings['apps']:
|
||||
print(f"{ES_APP_NAME} was not found in {settings['apps'].keys()}. We assume this is an error and shut down.\n\t"
|
||||
"Quitting...", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# Generate the ESCU package from this branch.
|
||||
source_path = generate_escu_app(settings['persist_security_content'])
|
||||
settings['local_apps']['SPLUNK_ES_CONTENT_UPDATE']['local_path'] = source_path
|
||||
settings['apps']['SPLUNK_ES_CONTENT_UPDATE']['local_path'] = source_path
|
||||
|
||||
|
||||
# Copy all the apps, to include ESCU (whether pregenerated or just generated)
|
||||
copy_local_apps_to_directory(
|
||||
settings['local_apps'], local_volume_absolute_path)
|
||||
try:
|
||||
relative_app_path = copy_local_apps_to_directory(settings['apps'],
|
||||
splunkbase_username = settings['splunkbase_username'],
|
||||
splunkbase_password = settings['splunkbase_password'],
|
||||
mock=settings['mock'], target_directory = CONTAINER_APP_DIRECTORY)
|
||||
|
||||
mounts = [{"local_path": os.path.abspath(relative_app_path),
|
||||
"container_path": "/tmp/apps", "type": "bind", "read_only": True}]
|
||||
except Exception as e:
|
||||
print(f"Error occurred when copying apps to app folder: [{str(e)}]\n\tQuitting...", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# If this is a mock run, finish it now
|
||||
@@ -467,13 +505,45 @@ def main(args: list[str]):
|
||||
|
||||
|
||||
|
||||
def shutdown_signal_handler_setup(sig, frame):
|
||||
|
||||
print(f"Signal {sig} received... stopping all [{settings['num_containers']}] containers and shutting down...")
|
||||
shutdown_client = docker.client.from_env()
|
||||
errorCount = 0
|
||||
for container_number in range(settings['num_containers']):
|
||||
container_name = settings['local_base_container_name']%container_number
|
||||
print(f"Shutting down {container_name}...", file=sys.stderr, end='')
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
container = shutdown_client.containers.get(container_name)
|
||||
#Note that stopping does not remove any of the volumes or logs,
|
||||
#so stopping can be useful if we want to debug any container failure
|
||||
container.stop(timeout=10)
|
||||
print("done", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f"Error trying to shut down {container_name}. It may have already shut down. Stop it youself with 'docker containter stop {container_name}", sys.stderr)
|
||||
errorCount += 1
|
||||
if errorCount == 0:
|
||||
print("All containers shut down successfully", file=sys.stderr)
|
||||
else:
|
||||
print(f"{errorCount} containers may still be running. Find out what is running with:\n\t'docker container ls'\nand shut them down with\n\t'docker container stop CONTAINER_NAME' ", file=sys.stderr)
|
||||
|
||||
print("Quitting...",file=sys.stderr)
|
||||
#We must use os._exit(1) because sys.exit(1) actually generates an exception which can be caught! And then we don't Quit!
|
||||
os._exit(1)
|
||||
|
||||
|
||||
|
||||
|
||||
#Setup requires a different teardown handler than during execution
|
||||
signal.signal(signal.SIGINT, shutdown_signal_handler_setup)
|
||||
|
||||
try:
|
||||
cm = container_manager.ContainerManager(all_test_files,
|
||||
FULL_DOCKER_HUB_CONTAINER_NAME,
|
||||
settings['local_base_container_name'],
|
||||
settings['num_containers'],
|
||||
settings['local_apps'],
|
||||
settings['splunkbase_apps'],
|
||||
settings['apps'],
|
||||
settings['branch'],
|
||||
settings['commit_hash'],
|
||||
reproduce_test_config,
|
||||
@@ -492,6 +562,15 @@ def main(args: list[str]):
|
||||
print("Error - unrecoverable error trying to set up the containers: [%s].\n\tQuitting..."%(str(e)),file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def shutdown_signal_handler_execution(sig, frame):
|
||||
#Set that a container has failed which will gracefully stop the other containers.
|
||||
#This way we get our full cleanup routine, too!
|
||||
print("Got a signal to shut down. Shutting down all containers, please wait...", file=sys.stderr)
|
||||
cm.synchronization_object.containerFailure()
|
||||
|
||||
#Update the signal handler
|
||||
|
||||
signal.signal(signal.SIGINT, shutdown_signal_handler_execution)
|
||||
try:
|
||||
result = cm.run_test()
|
||||
except Exception as e:
|
||||
@@ -508,7 +587,10 @@ def main(args: list[str]):
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("Test Execution Failed - review the logs for more details")
|
||||
sys.exit(1)
|
||||
#Because one or more of the threads could be stuck in a certain setup loop, like
|
||||
#trying to copy files to a containers (which igonores errors), we must os._exit
|
||||
#instead of sys.exit
|
||||
os._exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+38
-25
@@ -1,4 +1,5 @@
|
||||
from collections import OrderedDict
|
||||
from tabnanny import check
|
||||
import docker
|
||||
import datetime
|
||||
import docker.types
|
||||
@@ -25,8 +26,7 @@ class ContainerManager:
|
||||
full_docker_hub_name: str,
|
||||
container_name_template: str,
|
||||
num_containers: int,
|
||||
local_apps: OrderedDict,
|
||||
splunkbase_apps:OrderedDict,
|
||||
apps: OrderedDict,
|
||||
branch:str,
|
||||
commit_hash:str,
|
||||
summarization_reproduce_failure_config:dict,
|
||||
@@ -43,12 +43,15 @@ class ContainerManager:
|
||||
interactive:bool=False
|
||||
|
||||
):
|
||||
#Used to determine whether or not we should wait for container threads to finish when summarizing
|
||||
self.all_tests_completed = False
|
||||
|
||||
self.synchronization_object = test_driver.TestDriver(
|
||||
test_list, num_containers, summarization_reproduce_failure_config)
|
||||
|
||||
self.mounts = self.create_mounts(mounts)
|
||||
self.local_apps = local_apps
|
||||
self.splunkbase_apps = splunkbase_apps
|
||||
self.apps = apps
|
||||
|
||||
|
||||
if container_password is None:
|
||||
self.container_password = self.get_random_password()
|
||||
@@ -93,21 +96,29 @@ class ContainerManager:
|
||||
self.baseline['TEST_FINISH_TIME'] = "TO BE UPDATED"
|
||||
self.baseline['TEST_DURATION'] = "TO BE UPDATED"
|
||||
|
||||
for key in self.local_apps:
|
||||
self.baseline[key] = self.local_apps[key]
|
||||
for key in self.apps:
|
||||
self.baseline[key] = self.apps[key]
|
||||
|
||||
for key in self.splunkbase_apps:
|
||||
self.baseline[key] = self.splunkbase_apps[key]
|
||||
|
||||
|
||||
|
||||
def run_test(self)->bool:
|
||||
self.run_containers()
|
||||
self.run_status_thread()
|
||||
for container in self.containers:
|
||||
container.thread.join()
|
||||
print(container.get_container_summary())
|
||||
print("Waiting for next summary thread printout to finish...")
|
||||
self.run_containers()
|
||||
self.summary_thread.join()
|
||||
|
||||
|
||||
|
||||
|
||||
for container in self.containers:
|
||||
if self.all_tests_completed == True:
|
||||
container.thread.join()
|
||||
elif self.all_tests_completed == False:
|
||||
#For some reason, we stopped early. So don't wait on the child threads to finish. Don't join,
|
||||
#these threads may be stuck in their setup loops. Continue on.
|
||||
pass
|
||||
|
||||
print(container.get_container_summary())
|
||||
print("All containers completed testing!")
|
||||
|
||||
|
||||
@@ -170,8 +181,7 @@ class ContainerManager:
|
||||
self.synchronization_object,
|
||||
full_docker_hub_name,
|
||||
container_name,
|
||||
self.local_apps,
|
||||
self.splunkbase_apps,
|
||||
self.apps,
|
||||
web_port_tuple,
|
||||
management_port_tuple,
|
||||
self.container_password,
|
||||
@@ -219,16 +229,19 @@ class ContainerManager:
|
||||
password = "".join(password_list)
|
||||
return password
|
||||
|
||||
def queue_status_thread(self, status_interval:int=60)->None:
|
||||
def queue_status_thread(self, status_interval:int=60, num_steps:int=10)->None:
|
||||
|
||||
while True:
|
||||
if self.synchronization_object.checkContainerFailure():
|
||||
print("One of the containers has shut down prematurely and generated an exception. Shut down the rest of the containers.")
|
||||
for container in self.containers:
|
||||
container.stopContainer()
|
||||
print("All containers stopped")
|
||||
|
||||
return None
|
||||
#This for loop lets us run the summarize print less often, but check for failure more often
|
||||
for chunk in range(0, status_interval, int(status_interval/num_steps)):
|
||||
if self.synchronization_object.checkContainerFailure():
|
||||
print("One of the containers has shut down prematurely or the test was halted. Ensuring all containers are stopped.")
|
||||
for container in self.containers:
|
||||
container.stopContainer()
|
||||
print("All containers stopped")
|
||||
self.all_tests_completed = False
|
||||
return None
|
||||
time.sleep(status_interval/num_steps)
|
||||
|
||||
at_least_one_container_has_started_running_tests = False
|
||||
for container in self.containers:
|
||||
@@ -237,9 +250,9 @@ class ContainerManager:
|
||||
break
|
||||
if self.synchronization_object.summarize(testing_currently_active = at_least_one_container_has_started_running_tests) == False:
|
||||
#There are no more tests to run, so we can return from this thread
|
||||
|
||||
self.all_tests_completed = True
|
||||
return None
|
||||
time.sleep(status_interval)
|
||||
|
||||
|
||||
def setup_image(self, reuse_images: bool, container_name: str) -> None:
|
||||
client = docker.client.from_env()
|
||||
|
||||
+65
-34
@@ -23,15 +23,14 @@ SPLUNKBASE_URL = "https://splunkbase.splunk.com/app/%d/release/%s/download"
|
||||
SPLUNK_START_ARGS = "--accept-license"
|
||||
|
||||
#Give ten minutes to start - this is probably enough time
|
||||
MAX_CONTAINER_START_TIME_SECONDS = 60*10
|
||||
MAX_CONTAINER_START_TIME_SECONDS = 60*20
|
||||
class SplunkContainer:
|
||||
def __init__(
|
||||
self,
|
||||
synchronization_object: test_driver.TestDriver,
|
||||
full_docker_hub_path,
|
||||
container_name: str,
|
||||
local_apps: OrderedDict,
|
||||
splunkbase_apps: OrderedDict,
|
||||
apps: OrderedDict,
|
||||
web_port_tuple: tuple[str, int],
|
||||
management_port_tuple: tuple[str, int],
|
||||
container_password: str,
|
||||
@@ -49,15 +48,15 @@ class SplunkContainer:
|
||||
self.client = docker.client.from_env()
|
||||
self.full_docker_hub_path = full_docker_hub_path
|
||||
self.container_password = container_password
|
||||
self.local_apps = local_apps
|
||||
self.splunkbase_apps = splunkbase_apps
|
||||
|
||||
self.apps = apps
|
||||
|
||||
self.files_to_copy_to_container = files_to_copy_to_container
|
||||
self.splunk_ip = splunk_ip
|
||||
self.container_name = container_name
|
||||
self.mounts = mounts
|
||||
self.environment = self.make_environment(
|
||||
local_apps, splunkbase_apps, container_password, splunkbase_username, splunkbase_password
|
||||
apps, container_password, splunkbase_username, splunkbase_password
|
||||
)
|
||||
self.ports = self.make_ports(web_port_tuple, management_port_tuple)
|
||||
self.web_port = web_port_tuple[1]
|
||||
@@ -72,50 +71,56 @@ class SplunkContainer:
|
||||
self.num_tests_completed = 0
|
||||
|
||||
|
||||
|
||||
def prepare_apps_path(
|
||||
self,
|
||||
local_apps: OrderedDict,
|
||||
splunkbase_apps: OrderedDict,
|
||||
apps: OrderedDict,
|
||||
splunkbase_username: Union[str, None] = None,
|
||||
splunkbase_password: Union[str, None] = None,
|
||||
) -> tuple[str, bool]:
|
||||
apps_to_install = []
|
||||
|
||||
#We don't require credentials unless we install at least one splunkbase app
|
||||
require_credentials = False
|
||||
|
||||
for app_name, app_info in self.local_apps.items():
|
||||
|
||||
if 'local_path' in app_info:
|
||||
#If the username and password are supplied, then we will use splunkbase...
|
||||
#assuming that the app_name and app_number are supplied. Note that if a
|
||||
#local_path is supplied, then it should override this option!
|
||||
if splunkbase_username is not None and splunkbase_password is not None:
|
||||
use_splunkbase = True
|
||||
else:
|
||||
use_splunkbase = False
|
||||
|
||||
for app_name, app_info in self.apps.items():
|
||||
if use_splunkbase is True and 'local_path' not in app_info:
|
||||
target = SPLUNKBASE_URL % (app_info["app_number"], app_info["app_version"])
|
||||
apps_to_install.append(target)
|
||||
#We will require credentials since we are installing at least one splunkbase app
|
||||
require_credentials = True
|
||||
#Some paths may have a local_path and an HTTP path defined. Default to the local_path first,
|
||||
#mostly because we may have copied it before into the cache to speed up start time.
|
||||
elif 'local_path' in app_info:
|
||||
app_file_name = os.path.basename(app_info['local_path'])
|
||||
app_file_container_path = os.path.join("/tmp/apps", app_file_name)
|
||||
apps_to_install.append(app_file_container_path)
|
||||
apps_to_install.append(app_file_container_path)
|
||||
elif 'http_path' in app_info:
|
||||
apps_to_install.append(app_info['http_path'])
|
||||
|
||||
else:
|
||||
print("Error, the app %s: %s has no http_path or local_path.\n\tQuitting..."%(app_name,app_info), file=sys.stderr)
|
||||
if use_splunkbase is True:
|
||||
print("Error, the app %s: %s could not be installed from Splunkbase because "
|
||||
"--splunkbase_username and.or --splunkbase_password were not provided."
|
||||
"\n\tQuitting..."%(app_name,app_info), file=sys.stderr)
|
||||
else:
|
||||
print("Error, the app %s: %s has no http_path or local_path.\n\tQuitting..."%(app_name,app_info), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
for app_name, app_info in self.splunkbase_apps.items():
|
||||
|
||||
if splunkbase_username is None or splunkbase_password is None:
|
||||
raise Exception(
|
||||
"Error: Requested app from Splunkbase but Splunkbase username and/or password were not supplied."
|
||||
)
|
||||
target = SPLUNKBASE_URL % (
|
||||
app_info["app_number"], app_info["app_version"])
|
||||
apps_to_install.append(target)
|
||||
require_credentials = True
|
||||
# elif app["location"] == "local":
|
||||
# apps_to_install.append(app["container_path"])
|
||||
|
||||
#for printing out all the app paths we will install
|
||||
#for num, name in zip(range(len(apps_to_install)),apps_to_install):
|
||||
# print("%d: %s"%(num,name))
|
||||
return ",".join(apps_to_install), require_credentials
|
||||
|
||||
def make_environment(
|
||||
self,
|
||||
local_apps: OrderedDict,
|
||||
splunkbase_apps: OrderedDict,
|
||||
apps: OrderedDict,
|
||||
container_password: str,
|
||||
splunkbase_username: Union[str, None] = None,
|
||||
splunkbase_password: Union[str, None] = None,
|
||||
@@ -124,7 +129,7 @@ class SplunkContainer:
|
||||
env["SPLUNK_START_ARGS"] = SPLUNK_START_ARGS
|
||||
env["SPLUNK_PASSWORD"] = container_password
|
||||
splunk_apps_url, require_credentials = self.prepare_apps_path(
|
||||
local_apps, splunkbase_apps, splunkbase_username, splunkbase_password
|
||||
apps, splunkbase_username, splunkbase_password
|
||||
)
|
||||
|
||||
if require_credentials:
|
||||
@@ -314,10 +319,36 @@ class SplunkContainer:
|
||||
time.sleep(seconds_between_attempts)
|
||||
|
||||
|
||||
@wrapt_timeout_decorator.timeout(MAX_CONTAINER_START_TIME_SECONDS, timeout_exception=RuntimeError)
|
||||
#@wrapt_timeout_decorator.timeout(MAX_CONTAINER_START_TIME_SECONDS, timeout_exception=RuntimeError)
|
||||
def setup_container(self):
|
||||
|
||||
|
||||
self.container.start()
|
||||
|
||||
|
||||
# def shutdown_signal_handler(sig, frame):
|
||||
# shutdown_client = docker.client.from_env()
|
||||
# errorCount = 0
|
||||
|
||||
# print(f"Shutting down {self.container_name}...", file=sys.stderr)
|
||||
# try:
|
||||
# container = shutdown_client.containers.get(self.container_name)
|
||||
# #Note that stopping does not remove any of the volumes or logs,
|
||||
# #so stopping can be useful if we want to debug any container failure
|
||||
# container.stop(timeout=10)
|
||||
# print(f"{self.container_name} shut down successfully", file=sys.stderr)
|
||||
# except Exception as e:
|
||||
# print(f"Error trying to shut down {self.container_name}. It may have already shut down. Stop it youself with 'docker containter stop {self.container_name}", sys.stderr)
|
||||
|
||||
|
||||
# #We must use os._exit(1) because sys.exit(1) actually generates an exception which can be caught! And then we don't Quit!
|
||||
# import os
|
||||
# os._exit(1)
|
||||
|
||||
|
||||
|
||||
# import signal
|
||||
# signal.signal(signal.SIGINT, shutdown_signal_handler)
|
||||
|
||||
# By default, first copy the index file then the datamodel file
|
||||
for file_description, file_dict in self.files_to_copy_to_container.items():
|
||||
self.extract_tar_file_to_container(
|
||||
@@ -419,7 +450,7 @@ class SplunkContainer:
|
||||
% (detection_to_test, str(e))
|
||||
)
|
||||
|
||||
traceback.print_exc()
|
||||
#traceback.print_exc()
|
||||
#import pdb
|
||||
#pdb.set_trace()
|
||||
# Fill in all the "Empty" fields with default values. Otherwise, we will not be able to
|
||||
|
||||
@@ -235,7 +235,7 @@ class TestDriver:
|
||||
|
||||
|
||||
if self.checkContainerFailure():
|
||||
print("One or more containers crashed, so testing did not complete successfully. We wrote out all the results that we could")
|
||||
print("One or more containers crashed or the test was HALTED early, so testing did not complete successfully. We wrote out all the results that we could")
|
||||
return False
|
||||
else:
|
||||
return success
|
||||
@@ -269,16 +269,7 @@ class TestDriver:
|
||||
|
||||
|
||||
def summarize(self,testing_currently_active:bool=False)->bool:
|
||||
|
||||
if self.checkContainerFailure() == True:
|
||||
print("Error running containers... shutting down", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
self.lock.acquire()
|
||||
|
||||
try:
|
||||
|
||||
#Get a summary of some system stats
|
||||
|
||||
+61
-185
@@ -8,7 +8,7 @@ from typing import Union
|
||||
|
||||
|
||||
# If we want, we can easily add a description field to any of the objects here!
|
||||
|
||||
ES_APP_NAME = "SPLUNK_ES_CONTENT_UPDATE"
|
||||
setup_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -50,7 +50,7 @@ setup_schema = {
|
||||
},
|
||||
|
||||
|
||||
"local_apps": {
|
||||
"apps": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"patternProperties": {
|
||||
@@ -59,131 +59,101 @@ setup_schema = {
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"app_number": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
"type": ["integer","null"]
|
||||
},
|
||||
"app_version": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
"type": ["string","null"]
|
||||
},
|
||||
"local_path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
"type": ["string","null"]
|
||||
},
|
||||
"http_path": {
|
||||
"type": [
|
||||
"string"
|
||||
]
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
"anyOf": [
|
||||
{"required": ["local_path"]},
|
||||
{"required": ["http_path"]}
|
||||
{"required": ["http_path"] },
|
||||
{"required": ["app_number", "app_version"] },
|
||||
]
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"SPLUNK_ES_CONTENT_UPDATE": {
|
||||
ES_APP_NAME : {
|
||||
"app_number": 3449,
|
||||
"app_version": None,
|
||||
"local_path": None
|
||||
},
|
||||
#The default apps below were taken from the attack_range loadout: https://github.com/splunk/attack_range/blob/develop/attack_range.conf.template
|
||||
"SPLUNK_WINDOWS_TA": {
|
||||
"app_number": 0,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-microsoft-windows_812.tgz"
|
||||
"SPLUNK_ADD_ON_FOR_MICROSOFT_WINDOWS": {
|
||||
"app_number": 742,
|
||||
"app_version": "8.4.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-microsoft-windows_840.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_SYSMON_OLD": {
|
||||
"app_number": 1,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-microsoft-sysmon_1062.tgz"
|
||||
"ADD_ON_FOR_LINUX_SYSMON": {
|
||||
"app_number": 6176,
|
||||
"app_version": "1.0.4",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/add-on-for-linux-sysmon_104.tgz"
|
||||
},
|
||||
"SPLUNK_SYSMON_LINUX_TA_PATCHED": {
|
||||
"app_number": 2,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/add-on-for-linux-sysmon_103_PATCHED.tgz"
|
||||
"SPLUNK_ADD_ON_FOR_SYSMON": {
|
||||
"app_number": 5709,
|
||||
"app_version": "2.0.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-sysmon_200.tgz"
|
||||
},
|
||||
"SPLUNK_CIM_APP": {
|
||||
"app_number": 3,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-common-information-model-cim_4200.tgz"
|
||||
"SPLUNK_COMMON_INFORMATION_MODEL": {
|
||||
"app_number": 1621,
|
||||
"app_version": "5.0.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-common-information-model-cim_500.tgz"
|
||||
},
|
||||
"SPLUNK_AWS_TA": {
|
||||
"app_number": 4,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-amazon-web-services_510.tgz"
|
||||
"PYTHON_FOR_SCIENTIFIC_COMPUTING_FOR_LINUX_64_BIT": {
|
||||
"app_number": 2882,
|
||||
"app_version": "3.0.2",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/python-for-scientific-computing-for-linux-64-bit_302.tgz"
|
||||
},
|
||||
"SPLUNK_PYTHON_APP": {
|
||||
"app_number": 5,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/python-for-scientific-computing-for-linux-64-bit_202.tgz"
|
||||
"SPLUNK_MACHINE_LEARNING_TOOLKIT": {
|
||||
"app_number": 2890,
|
||||
"app_version": "5.3.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-machine-learning-toolkit_531.tgz"
|
||||
},
|
||||
"SPLUNK_ASX_APP": {
|
||||
"app_number": 17,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/Splunk_ASX-latest.tar.gz"
|
||||
},
|
||||
"SPLUNK_MLTK_APP": {
|
||||
"app_number": 6,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-machine-learning-toolkit_521.tgz"
|
||||
},
|
||||
"SPLUNK_STREAM_APP": {
|
||||
"app_number": 7,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-app-for-stream_730.tgz"
|
||||
"SPLUNK_APP_FOR_STREAM": {
|
||||
"app_number": 1809,
|
||||
"app_version": "8.0.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-app-for-stream_801.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_STREAM_WIRE_DATA": {
|
||||
"app_number": 8,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-stream-wire-data_730.tgz"
|
||||
"app_number": 5234,
|
||||
"app_version": "8.0.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-stream-wire-data_801.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_STREAM_FORWARDERS": {
|
||||
"app_number": 9,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-stream-forwarders_730.tgz"
|
||||
},
|
||||
"SPLUNK_SECURITY_ESSENTIALS": {
|
||||
"app_number": 10,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-security-essentials_333.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_ZEEK_AKA_BRO": {
|
||||
"app_number": 11,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-zeek-aka-bro_400.tgz"
|
||||
"app_number": 5238,
|
||||
"app_version": "8.0.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-stream-forwarders_801.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_AMAZON_KINESIS_FIREHOSE": {
|
||||
"app_number": 12,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-amazon-kinesis-firehose_131r7d1d093.tgz"
|
||||
"app_number": 3719,
|
||||
"app_version": "1.3.2",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-amazon-kinesis-firehose_132.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_MICROSOFT_OFFICE_365": {
|
||||
"app_number": 13,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-microsoft-office-365_202.tgz"
|
||||
"app_number": 4055,
|
||||
"app_version": "2.2.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-microsoft-office-365_220.tgz"
|
||||
},
|
||||
"SPLUNK_LINUX_TA": {
|
||||
"app_number": 14,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-unix-and-linux_820.tgz"
|
||||
"SPLUNK_ADD_ON_FOR_UNIX_AND_LINUX": {
|
||||
"app_number": 833,
|
||||
"app_version": "8.4.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-unix-and-linux_840.tgz"
|
||||
},
|
||||
"SPLUNK_NGINX_TA": {
|
||||
"app_number": 15,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-nginx_310.tgz"
|
||||
"SPLUNK_ADD_ON_FOR_NGINX": {
|
||||
"app_number": 3258,
|
||||
"app_version": "3.1.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-nginx_310.tgz"
|
||||
},
|
||||
"SPLUNK_TA_FOR_ZEEK": {
|
||||
"app_number": 16,
|
||||
"app_version": None,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/ta-for-zeek_105.tgz"
|
||||
"app_number": 5466,
|
||||
"app_version": "1.0.5",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/ta-for-zeek_105.tgz"
|
||||
},
|
||||
|
||||
}
|
||||
@@ -226,100 +196,6 @@ setup_schema = {
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
"splunkbase_apps": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"app_number": {
|
||||
"type": "integer"
|
||||
},
|
||||
"app_version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"default": {
|
||||
"SPLUNK_ADD_ON_FOR_SYSMON": {
|
||||
"app_number": 5709,
|
||||
"app_version": "1.0.1"
|
||||
}
|
||||
},
|
||||
|
||||
# "default": {
|
||||
# "ADD-ON_FOR_LINUX_SYSMON": {
|
||||
# "app_number": 6176,
|
||||
# "app_version": "1.0.3"
|
||||
# },
|
||||
# "SPLUNK_ADD_ON_FOR_AMAZON_WEB_SERVICES": {
|
||||
# "app_number": 1876,
|
||||
# "app_version": "5.2.1"
|
||||
# },
|
||||
|
||||
# "SPLUNK_ADD_ON_FOR_MICROSOFT_OFFICE_365":
|
||||
# {
|
||||
# "app_number": 4055,
|
||||
# "app_version": "2.2.0"
|
||||
# },
|
||||
|
||||
# "SPLUNK_ADD_ON_FOR_AMAZON_KINESIS_FIREHOSE": {
|
||||
# "app_number": 3719,
|
||||
# "app_version": "1.3.2"
|
||||
# },
|
||||
|
||||
# "SPLUNK_ANALYTIC_STORY_EXECUTION_APP": {
|
||||
# "app_number": 4971,
|
||||
# "app_version": "2.0.3"
|
||||
# },
|
||||
|
||||
# "PYTHON_FOR_SCIENTIC_COMPUTING_LINUX_64_BIT": {
|
||||
# "app_number": 2882,
|
||||
# "app_version": "3.0.1"
|
||||
# },
|
||||
|
||||
# "SPLUNK_MACHINE_LEARNING_TOOLKIT": {
|
||||
# "app_number": 2890,
|
||||
# "app_version": "5.3.0"
|
||||
# },
|
||||
|
||||
# "SPLUNK_APP_FOR_STREAM": {
|
||||
# "app_number": 1809,
|
||||
# "app_version": "8.0.1"
|
||||
# },
|
||||
# "SPLUNK_ADD_ON_FOR_STREAM_WIRE_DATA": {
|
||||
# "app_number": 5234,
|
||||
# "app_version": "8.0.1"
|
||||
# },
|
||||
# "SPLUNK_ADD_ON_FOR_STREAM_FORWARDERS": {
|
||||
# "app_number": 5238,
|
||||
# "app_version": "8.0.1"
|
||||
# },
|
||||
# "SPLUNK_ADD_ON_FOR_ZEEK_AKA_BRO": {
|
||||
# "app_number": 1617,
|
||||
# "app_version": "4.0.0"
|
||||
# },
|
||||
# "SPLUNK_ADD_ON_FOR_UNIX_AND_LINUX": {
|
||||
# "app_number": 833,
|
||||
# "app_version": "8.4.0"
|
||||
# },
|
||||
# "SPLUNK_ADD_ON_FOR_SYSMON": {
|
||||
# "app_number": 5709,
|
||||
# "app_version": "1.0.1"
|
||||
# },
|
||||
# # According to https://docs.splunk.com/Documentation/ES/6.6.2/Install/Datamodels, these are included in ES. Don't install separately.
|
||||
# "SPLUNK_COMMON_INFORMATION_MODEL": {
|
||||
# "app_number": 1621,
|
||||
# "app_version": "5.0.0"
|
||||
# }
|
||||
# }
|
||||
},
|
||||
|
||||
"splunkbase_username": {
|
||||
"type": ["string", "null"],
|
||||
"default": None
|
||||
|
||||
+85
-111
@@ -1,113 +1,93 @@
|
||||
{
|
||||
"apps": {
|
||||
"ADD_ON_FOR_LINUX_SYSMON": {
|
||||
"app_number": 6176,
|
||||
"app_version": "1.0.4",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/add-on-for-linux-sysmon_104.tgz"
|
||||
},
|
||||
"PYTHON_FOR_SCIENTIFIC_COMPUTING_FOR_LINUX_64_BIT": {
|
||||
"app_number": 2882,
|
||||
"app_version": "3.0.2",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/python-for-scientific-computing-for-linux-64-bit_302.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_AMAZON_KINESIS_FIREHOSE": {
|
||||
"app_number": 3719,
|
||||
"app_version": "1.3.2",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-amazon-kinesis-firehose_132.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_MICROSOFT_OFFICE_365": {
|
||||
"app_number": 4055,
|
||||
"app_version": "2.2.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-microsoft-office-365_220.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_MICROSOFT_WINDOWS": {
|
||||
"app_number": 742,
|
||||
"app_version": "8.4.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-microsoft-windows_840.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_NGINX": {
|
||||
"app_number": 3258,
|
||||
"app_version": "3.1.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-nginx_310.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_STREAM_FORWARDERS": {
|
||||
"app_number": 5238,
|
||||
"app_version": "8.0.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-stream-forwarders_801.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_STREAM_WIRE_DATA": {
|
||||
"app_number": 5234,
|
||||
"app_version": "8.0.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-stream-wire-data_801.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_SYSMON": {
|
||||
"app_number": 5709,
|
||||
"app_version": "2.0.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-sysmon_200.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_UNIX_AND_LINUX": {
|
||||
"app_number": 833,
|
||||
"app_version": "8.4.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-add-on-for-unix-and-linux_840.tgz"
|
||||
},
|
||||
"SPLUNK_APP_FOR_STREAM": {
|
||||
"app_number": 1809,
|
||||
"app_version": "8.0.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-app-for-stream_801.tgz"
|
||||
},
|
||||
"SPLUNK_COMMON_INFORMATION_MODEL": {
|
||||
"app_number": 1621,
|
||||
"app_version": "5.0.0",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-common-information-model-cim_500.tgz"
|
||||
},
|
||||
"SPLUNK_ES_CONTENT_UPDATE": {
|
||||
"app_number": 3449,
|
||||
"app_version": null,
|
||||
"local_path": null
|
||||
},
|
||||
"SPLUNK_MACHINE_LEARNING_TOOLKIT": {
|
||||
"app_number": 2890,
|
||||
"app_version": "5.3.1",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/splunk-machine-learning-toolkit_531.tgz"
|
||||
},
|
||||
"SPLUNK_TA_FOR_ZEEK": {
|
||||
"app_number": 5466,
|
||||
"app_version": "1.0.5",
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/Latest/ta-for-zeek_105.tgz"
|
||||
}
|
||||
},
|
||||
"branch": "BRANCH_DOES_NOT_EXIST_USE_CLI_ARGUMENT",
|
||||
"commit_hash": null,
|
||||
"container_tag": "latest",
|
||||
"detections_file": null,
|
||||
"detections_list": null,
|
||||
"folders": [
|
||||
"endpoint",
|
||||
"cloud",
|
||||
"network",
|
||||
"web"
|
||||
"endpoint",
|
||||
"cloud",
|
||||
"network",
|
||||
"web"
|
||||
],
|
||||
"interactive": false,
|
||||
"local_apps": {
|
||||
"SPLUNK_ADD_ON_FOR_AMAZON_KINESIS_FIREHOSE": {
|
||||
"app_number": 12,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-amazon-kinesis-firehose_131r7d1d093.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_MICROSOFT_OFFICE_365": {
|
||||
"app_number": 13,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-microsoft-office-365_202.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_STREAM_FORWARDERS": {
|
||||
"app_number": 9,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-stream-forwarders_730.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_STREAM_WIRE_DATA": {
|
||||
"app_number": 8,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-stream-wire-data_730.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_SYSMON_OLD": {
|
||||
"app_number": 1,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-microsoft-sysmon_1062.tgz"
|
||||
},
|
||||
"SPLUNK_ADD_ON_FOR_ZEEK_AKA_BRO": {
|
||||
"app_number": 11,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-zeek-aka-bro_400.tgz"
|
||||
},
|
||||
"SPLUNK_ASX_APP": {
|
||||
"app_number": 17,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/Splunk_ASX-latest.tar.gz"
|
||||
},
|
||||
"SPLUNK_AWS_TA": {
|
||||
"app_number": 4,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-amazon-web-services_510.tgz"
|
||||
},
|
||||
"SPLUNK_CIM_APP": {
|
||||
"app_number": 3,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-common-information-model-cim_4200.tgz"
|
||||
},
|
||||
"SPLUNK_ES_CONTENT_UPDATE": {
|
||||
"app_number": 3449,
|
||||
"app_version": null,
|
||||
"local_path": null
|
||||
},
|
||||
"SPLUNK_LINUX_TA": {
|
||||
"app_number": 14,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-unix-and-linux_820.tgz"
|
||||
},
|
||||
"SPLUNK_MLTK_APP": {
|
||||
"app_number": 6,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/splunk-machine-learning-toolkit_531.tgz"
|
||||
},
|
||||
"SPLUNK_NGINX_TA": {
|
||||
"app_number": 15,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-nginx_310.tgz"
|
||||
},
|
||||
"SPLUNK_PYTHON_APP": {
|
||||
"app_number": 5,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3.us-west-2.amazonaws.com/python-for-scientific-computing-for-linux-64-bit_302.tgz"
|
||||
},
|
||||
"SPLUNK_SECURITY_ESSENTIALS": {
|
||||
"app_number": 10,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-security-essentials_333.tgz"
|
||||
},
|
||||
"SPLUNK_STREAM_APP": {
|
||||
"app_number": 7,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-app-for-stream_730.tgz"
|
||||
},
|
||||
"SPLUNK_SYSMON_LINUX_TA_PATCHED": {
|
||||
"app_number": 2,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/add-on-for-linux-sysmon_103_PATCHED.tgz"
|
||||
},
|
||||
"SPLUNK_TA_FOR_ZEEK": {
|
||||
"app_number": 16,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/ta-for-zeek_105.tgz"
|
||||
},
|
||||
"SPLUNK_WINDOWS_TA": {
|
||||
"app_number": 0,
|
||||
"app_version": null,
|
||||
"http_path": "https://attack-range-appbinaries.s3-us-west-2.amazonaws.com/splunk-add-on-for-microsoft-windows_812.tgz"
|
||||
}
|
||||
},
|
||||
"local_base_container_name": "splunk_test_%d",
|
||||
"mock": false,
|
||||
"mode": "changes",
|
||||
@@ -119,17 +99,11 @@
|
||||
"show_splunk_app_password": false,
|
||||
"splunk_app_password": null,
|
||||
"splunk_container_apps_directory": "/opt/splunk/etc/apps",
|
||||
"splunkbase_apps": {
|
||||
"SPLUNK_ADD_ON_FOR_SYSMON": {
|
||||
"app_number": 5709,
|
||||
"app_version": "1.0.1"
|
||||
}
|
||||
},
|
||||
"splunkbase_password": null,
|
||||
"splunkbase_username": null,
|
||||
"types": [
|
||||
"Anomaly",
|
||||
"Hunting",
|
||||
"TTP"
|
||||
"Anomaly",
|
||||
"Hunting",
|
||||
"TTP"
|
||||
]
|
||||
}
|
||||
}
|
||||
+33
-2
@@ -1,3 +1,5 @@
|
||||
from threading import Thread
|
||||
from functools import cache
|
||||
import glob
|
||||
import yaml
|
||||
import argparse
|
||||
@@ -12,6 +14,7 @@ from stix2 import Filter
|
||||
from pycvesearch import CVESearch
|
||||
|
||||
CVESSEARCH_API_URL = 'https://cve.circl.lu'
|
||||
CVESSEARCH_API_TIMEOUT = 10
|
||||
|
||||
|
||||
def load_objects(REPO_PATH, TYPE):
|
||||
@@ -94,6 +97,16 @@ def parse_and_add_lookups(search_string, lookups):
|
||||
return lookup_objects
|
||||
|
||||
|
||||
#This function hits an API, which can be slow, especially if the API
|
||||
#or network connection is slow. This has sometimes caused issues
|
||||
#in our CI/CD taking a very long time. Here, we memoize/cache
|
||||
#the calls to this function - there will be many duplicates which
|
||||
#should all return the same results. This will gives us a significant
|
||||
#speedup.
|
||||
#Hackish way of keeping the original functionality and allowing it
|
||||
#to easily return a value when in a thread... making an optional
|
||||
#default argument with a type that is mutable!
|
||||
@cache
|
||||
def get_cve_enrichment_new(cve_id):
|
||||
cve = CVESearch(CVESSEARCH_API_URL)
|
||||
result = cve.id(cve_id)
|
||||
@@ -103,6 +116,15 @@ def get_cve_enrichment_new(cve_id):
|
||||
cve_enriched['summary'] = result['summary']
|
||||
return cve_enriched
|
||||
|
||||
#helper function to easily return a value from a thread that is
|
||||
#running a memoized/@cached function
|
||||
def get_cve_enrichment_new_wrapper(cve_id,mutable_list):
|
||||
mutable_list.append(get_cve_enrichment_new(cve_id))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_all_techniques(projects_path):
|
||||
path_cti = path.join(projects_path,'cti/enterprise-attack')
|
||||
fs = FileSystemSource(path_cti)
|
||||
@@ -327,8 +349,17 @@ def generate_doc_detections(REPO_PATH, OUTPUT_DIR, TEMPLATE_PATH, attack, messag
|
||||
cves = []
|
||||
if 'cve' in detection_yaml['tags']:
|
||||
for cve_id in detection_yaml['tags']['cve']:
|
||||
cve = get_cve_enrichment_new(cve_id)
|
||||
cves.append(cve)
|
||||
mutable_list = []
|
||||
try:
|
||||
cve_thread = Thread(target=get_cve_enrichment_new_wrapper, args=(cve_id, mutable_list))
|
||||
cve_thread.start()
|
||||
cve_thread.join(timeout=CVESSEARCH_API_TIMEOUT)
|
||||
if cve_thread.is_alive():
|
||||
raise(Exception(f"Timed out getting CVE Enrichment from {CVESSEARCH_API_URL} after {CVESSEARCH_API_TIMEOUT} seconds."))
|
||||
cves.append(mutable_list[0])
|
||||
except Exception as e:
|
||||
print(f"Error - {str(e)}\nQuitting...",file=sys.stderr)
|
||||
sys.exit(1)
|
||||
detection_yaml['cve'] = cves
|
||||
|
||||
# enrich with macros
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
name: O365 Excessive Authentication Failures Alert
|
||||
id: d441364c-349c-453b-b55f-12eccab67cf9
|
||||
version: 1
|
||||
date: '2020-12-16'
|
||||
version: 2
|
||||
date: '2022-02-18'
|
||||
author: Rod Soto, Splunk
|
||||
type: Anomaly
|
||||
datamodel: []
|
||||
description: This search detects when an excessive number of authentication failures
|
||||
occur this search also includes attempts against MFA prompt codes
|
||||
search: '`o365_management_activity` Workload=AzureActiveDirectory UserAuthenticationMethod=*
|
||||
status=Failed | stats count earliest(_time) as firstTime latest(_time) values(UserAuthenticationMethod)
|
||||
AS UserAuthenticationMethod values(UserAgent) AS UserAgent values(status) AS status
|
||||
values(src_ip) AS src_ip by user | where count > 10 |`security_content_ctime(firstTime)`
|
||||
|`security_content_ctime(lastTime)` | `o365_excessive_authentication_failures_alert_filter`'
|
||||
search: '`o365_management_activity` Workload=AzureActiveDirectory UserAuthenticationMethod=* status=failure
|
||||
| stats count earliest(_time) as firstTime latest(_time) values(UserAuthenticationMethod) AS UserAuthenticationMethod
|
||||
values(UserAgent) AS UserAgent values(status) AS status values(src_ip) AS src_ip by user
|
||||
| where count > 10
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `o365_excessive_authentication_failures_alert_filter`'
|
||||
how_to_implement: You must install splunk Microsoft Office 365 add-on. This search
|
||||
works with o365:management:activity
|
||||
known_false_positives: The threshold for alert is above 10 attempts and this should
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Detect Regasm with Network Connection
|
||||
id: 07921114-6db4-4e2e-ae58-3ea8a52ae93f
|
||||
version: 1
|
||||
date: '2021-02-16'
|
||||
version: 2
|
||||
date: '2022-02-18'
|
||||
author: Michael Haag, Splunk
|
||||
type: TTP
|
||||
datamodel: []
|
||||
@@ -15,10 +15,12 @@ description: The following analytic identifies regasm.exe with a network connect
|
||||
and review accordingly. Review the reputation of the remote IP or domain and block
|
||||
as needed. regsvcs.exe and regasm.exe are natively found in C:\Windows\Microsoft.NET\Framework\v*\regasm|regsvcs.exe
|
||||
and C:\Windows\Microsoft.NET\Framework64\v*\regasm|regsvcs.exe.
|
||||
search: '`sysmon` EventID=3 dest_ip!=10.0.0.0/12 dest_ip!=172.16.0.0/12 dest_ip!=192.168.0.0/16
|
||||
process_name=regasm.exe | rename Computer as dest | stats count min(_time) as firstTime
|
||||
max(_time) as lastTime by dest, User, process_name, src_ip, dest_host, dest_ip |
|
||||
`security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `detect_regasm_with_network_connection_filter`'
|
||||
search: '`sysmon` EventID=3 dest_ip!=10.0.0.0/12 dest_ip!=172.16.0.0/12 dest_ip!=192.168.0.0/16 process_name=regasm.exe
|
||||
| rename Computer as dest
|
||||
| stats count min(_time) as firstTime max(_time) as lastTime by dest, user, process_name, src_ip, dest_ip
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `detect_regasm_with_network_connection_filter`'
|
||||
how_to_implement: To successfully implement this search, you need to be ingesting
|
||||
logs with the process name, parent process, and command-line executions from your
|
||||
endpoints. If you are using Sysmon, you must have at least version 6.0.4 of the
|
||||
@@ -59,7 +61,7 @@ tags:
|
||||
type: User
|
||||
role:
|
||||
- Victim
|
||||
- name: Computer
|
||||
- name: dest
|
||||
type: Hostname
|
||||
role:
|
||||
- Victim
|
||||
@@ -77,7 +79,7 @@ tags:
|
||||
- dest_ip
|
||||
- process_name
|
||||
- Computer
|
||||
- User
|
||||
- user
|
||||
- src_ip
|
||||
- dest_host
|
||||
- dest_ip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Detect Regsvcs with Network Connection
|
||||
id: e3e7a1c0-f2b9-445c-8493-f30a63522d1a
|
||||
version: 1
|
||||
date: '2021-02-16'
|
||||
version: 2
|
||||
date: '2022-02-18'
|
||||
author: Michael Haag, Splunk
|
||||
type: TTP
|
||||
datamodel: []
|
||||
@@ -15,10 +15,12 @@ description: The following analytic identifies Regsvcs.exe with a network connec
|
||||
and review accordingly. Review the reputation of the remote IP or domain and block
|
||||
as needed. regsvcs.exe and regasm.exe are natively found in C:\Windows\Microsoft.NET\Framework\v*\regasm|regsvcs.exe
|
||||
and C:\Windows\Microsoft.NET\Framework64\v*\regasm|regsvcs.exe.
|
||||
search: '`sysmon` EventID=3 dest_ip!=10.0.0.0/12 dest_ip!=172.16.0.0/12 dest_ip!=192.168.0.0/16
|
||||
process_name=regsvcs.exe | rename Computer as dest | stats count min(_time) as firstTime
|
||||
max(_time) as lastTime by dest, User, process_name, src_ip, dest_host, dest_ip |
|
||||
`security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `detect_regsvcs_with_network_connection_filter`'
|
||||
search: '`sysmon` EventID=3 dest_ip!=10.0.0.0/12 dest_ip!=172.16.0.0/12 dest_ip!=192.168.0.0/16 process_name=regsvcs.exe
|
||||
| rename Computer as dest
|
||||
| stats count min(_time) as firstTime max(_time) as lastTime by dest, user, process_name, src_ip, dest_ip
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `detect_regsvcs_with_network_connection_filter`'
|
||||
how_to_implement: To successfully implement this search, you need to be ingesting
|
||||
logs with the process name, parent process, and command-line executions from your
|
||||
endpoints. If you are using Sysmon, you must have at least version 6.0.4 of the
|
||||
@@ -59,7 +61,7 @@ tags:
|
||||
type: User
|
||||
role:
|
||||
- Victim
|
||||
- name: Computer
|
||||
- name: dest
|
||||
type: Hostname
|
||||
role:
|
||||
- Victim
|
||||
@@ -77,7 +79,7 @@ tags:
|
||||
- dest_ip
|
||||
- process_name
|
||||
- Computer
|
||||
- User
|
||||
- user
|
||||
- src_ip
|
||||
- dest_host
|
||||
risk_score: 80
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Interactive Session on Remote Endpoint with PowerShell
|
||||
id: a4e8f3a4-48b2-11ec-bcfc-3e22fbd008af
|
||||
version: 1
|
||||
date: '2021-11-18'
|
||||
version: 2
|
||||
date: '2022-02-18'
|
||||
author: Mauricio Velazco, Splunk
|
||||
type: TTP
|
||||
datamodel: []
|
||||
@@ -10,9 +10,10 @@ description: The following analytic utilizes PowerShell Script Block Logging (Ev
|
||||
an interactive session on a remote endpoint leveraging the WinRM protocol. Red Teams
|
||||
and adversaries alike may abuse WinRM and `Enter-PSSession` for lateral movement
|
||||
and remote code execution.
|
||||
search: powershell` EventCode=4104 (Message="*Enter-PSSession*" AND Message="*-ComputerName*")
|
||||
| stats count min(_time) as firstTime max(_time) as lastTime by EventCode Message
|
||||
ComputerName User | `security_content_ctime(firstTime)` | `interactive_session_on_remote_endpoint_with_powershell_filter`
|
||||
search: '`powershell` EventCode=4104 (Message="*Enter-PSSession*" AND Message="*-ComputerName*")
|
||||
| stats count min(_time) as firstTime max(_time) as lastTime by EventCode Message ComputerName User
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `interactive_session_on_remote_endpoint_with_powershell_filter`'
|
||||
how_to_implement: To successfully implement this analytic, you will need to enable
|
||||
PowerShell Script Block Logging on some or all endpoints. Additional setup instructions
|
||||
can be found https://docs.splunk.com/Documentation/UBA/5.0.4.1/GetDataIn/AddPowerShell#Configure_module_logging_for_PowerShell.
|
||||
|
||||
@@ -13,7 +13,7 @@ description: The following analytic identifies a suspicious process creation of
|
||||
and after this process execution, when it was executed and what schedule task it
|
||||
will execute.
|
||||
search: '| tstats `security_content_summariesonly` count from datamodel=Endpoint.Processes
|
||||
where Processes.process_name = at OR Processes.parent_process_name = at by Processes.dest
|
||||
where Processes.process_name IN ("at", "atd") OR Processes.parent_process_name IN ("at", "atd") by Processes.dest
|
||||
Processes.user Processes.parent_process_name Processes.process_name Processes.process
|
||||
Processes.process_id Processes.parent_process_id Processes.process_guid | `drop_dm_object_name(Processes)`
|
||||
| `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `linux_at_application_execution_filter`'
|
||||
|
||||
@@ -16,10 +16,10 @@ search: '| tstats `security_content_summariesonly` count min(_time) as firstTime
|
||||
as lastTime from datamodel=Endpoint.Processes where Processes.process_name IN ("chmod",
|
||||
"chown", "fchmod", "fchmodat", "fchown", "fchownat", "fremovexattr", "fsetxattr",
|
||||
"lchown", "lremovexattr", "lsetxattr", "removexattr", "setuid", "setgid", "setreuid",
|
||||
"setregid") OR Processes.process IN ("*chmod *", "*chown *", "*fchmod *", "*fchmodat
|
||||
"setregid", "chattr") OR Processes.process IN ("*chmod *", "*chown *", "*fchmod *", "*fchmodat
|
||||
*", "*fchown *", "*fchownat *", "*fremovexattr *", "*fsetxattr *", "*lchown *",
|
||||
"*lremovexattr *", "*lsetxattr *", "*removexattr *", "*setuid *", "*setgid *", "*setreuid
|
||||
*", "*setregid *", "*setcap *") by Processes.dest Processes.user Processes.parent_process_name
|
||||
*", "*setregid *", "*setcap *", "*chattr *") by Processes.dest Processes.user Processes.parent_process_name
|
||||
Processes.process_name Processes.process Processes.process_id Processes.parent_process_id
|
||||
Processes.process_guid | `drop_dm_object_name(Processes)` | `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)` | `linux_common_process_for_elevation_control_filter`'
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Linux DD File Overwrite
|
||||
id: 9b6aae5e-8d85-11ec-b2ae-acde48001122
|
||||
version: 1
|
||||
date: '2022-02-14'
|
||||
author: Teoderick Contreras, Splunk
|
||||
type: TTP
|
||||
datamodel:
|
||||
- Endpoint
|
||||
description: This analytic is to look for dd command to overwrite file. This technique was abused by adversaries or
|
||||
threat actor to destroy files or data on specific system or in a large number of host within network to interrupt host avilability,
|
||||
services and many more. This is also used to destroy data where it make the file irrecoverable by forensic techniques through overwriting files,
|
||||
data or local and remote drives.
|
||||
search: '| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Endpoint.Processes
|
||||
where Processes.process_name = "dd" AND Processes.process = "*of=*"
|
||||
by Processes.dest Processes.user Processes.parent_process_name Processes.process_name Processes.process Processes.process_id Processes.parent_process_id Processes.process_guid
|
||||
| `drop_dm_object_name(Processes)`
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `linux_dd_file_overwrite_filter`'
|
||||
how_to_implement: To successfully implement this search, you need to be ingesting
|
||||
logs with the process name, parent process, and command-line executions from your
|
||||
endpoints. If you are using Sysmon, you can use the Add-on for Linux Sysmon from
|
||||
Splunkbase.
|
||||
known_false_positives: Administrator or network operator can execute this command.
|
||||
Please update the filter macros to remove false positives.
|
||||
references:
|
||||
- https://gtfobins.github.io/gtfobins/dd/
|
||||
- https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1485/T1485.md
|
||||
tags:
|
||||
analytic_story:
|
||||
- Data Destruction
|
||||
dataset:
|
||||
- https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1485/linux_dd_file_overwrite/sysmon_linux.log
|
||||
kill_chain_phases:
|
||||
- Exploitation
|
||||
mitre_attack_id:
|
||||
- T1485
|
||||
product:
|
||||
- Splunk Enterprise
|
||||
- Splunk Enterprise Security
|
||||
- Splunk Cloud
|
||||
required_fields:
|
||||
- _time
|
||||
- Processes.dest
|
||||
- Processes.user
|
||||
- Processes.parent_process_name
|
||||
- Processes.process_name
|
||||
- Processes.process
|
||||
- Processes.process_id
|
||||
- Processes.parent_process_id
|
||||
security_domain: endpoint
|
||||
impact: 80
|
||||
confidence: 80
|
||||
# (impact * confidence)/100
|
||||
risk_score: 64
|
||||
context:
|
||||
- Source:Endpoint
|
||||
- Stage:Impact
|
||||
message: A commandline $process$ executed on $dest$
|
||||
observable:
|
||||
- name: dest
|
||||
type: Hostname
|
||||
role:
|
||||
- Victim
|
||||
nist:
|
||||
- DE.CM
|
||||
cis20:
|
||||
- CIS 3
|
||||
- CIS 5
|
||||
- CIS 16
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Linux System Network Discovery
|
||||
id: 535cb214-8b47-11ec-a2c7-acde48001122
|
||||
version: 1
|
||||
date: '2022-02-11'
|
||||
author: Teoderick Contreras, Splunk
|
||||
type: Anomaly
|
||||
datamodel:
|
||||
- Endpoint
|
||||
description: This analytic is to look for possible enumeration of local network configuration.
|
||||
This technique is commonly used as part of recon of adversaries or threat actor to know some network information for its next or further
|
||||
attack. This anomaly detections may capture normal event made by administrator during auditing or testing network connection of specific
|
||||
host or network to network.
|
||||
search: '| tstats `security_content_summariesonly`
|
||||
count values(Processes.process_name) as process_name_list values(Processes.process) as process_list
|
||||
values(Processes.process_id) as process_id_list values(Processes.parent_process_id) as parent_process_id_list
|
||||
values(Processes.process_guid) as process_guid_list dc(Processes.process_name) as process_name_count from datamodel=Endpoint.Processes
|
||||
where Processes.process_name IN ("arp", "ifconfig", "ip", "netstat", "firewall-cmd", "ufw", "iptables", "ss", "route")
|
||||
by _time span=30m Processes.dest Processes.user
|
||||
| where process_name_count >=4
|
||||
| `drop_dm_object_name(Processes)`| `linux_system_network_discovery_filter`'
|
||||
how_to_implement: To successfully implement this search, you need to be ingesting
|
||||
logs with the process name, parent process, and command-line executions from your
|
||||
endpoints. If you are using Sysmon, you can use the Add-on for Linux Sysmon from
|
||||
Splunkbase.
|
||||
known_false_positives: Administrator or network operator can execute this command.
|
||||
Please update the filter macros to remove false positives.
|
||||
references:
|
||||
- https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1016/T1016.md
|
||||
tags:
|
||||
analytic_story:
|
||||
- Network Discovery
|
||||
dataset:
|
||||
- https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1016/atomic_red_team/linux_net_discovery/sysmon_linux.log
|
||||
kill_chain_phases:
|
||||
- Reconnaissance
|
||||
mitre_attack_id:
|
||||
- T1016
|
||||
product:
|
||||
- Splunk Enterprise
|
||||
- Splunk Enterprise Security
|
||||
- Splunk Cloud
|
||||
required_fields:
|
||||
- _time
|
||||
- Processes.dest
|
||||
- Processes.user
|
||||
- Processes.parent_process_name
|
||||
- Processes.process_name
|
||||
- Processes.process
|
||||
- Processes.process_id
|
||||
- Processes.parent_process_id
|
||||
security_domain: endpoint
|
||||
impact: 30
|
||||
confidence: 30
|
||||
# (impact * confidence)/100
|
||||
risk_score: 9
|
||||
context:
|
||||
- Source:Endpoint
|
||||
- Stage:Reconaissance
|
||||
message: A commandline $process$ executed on $dest$
|
||||
observable:
|
||||
- name: dest
|
||||
type: Hostname
|
||||
role:
|
||||
- Victim
|
||||
nist:
|
||||
- DE.CM
|
||||
cis20:
|
||||
- CIS 3
|
||||
- CIS 5
|
||||
- CIS 16
|
||||
@@ -1,7 +1,7 @@
|
||||
name: NET Profiler UAC bypass
|
||||
id: 0252ca80-e30d-11eb-8aa3-acde48001122
|
||||
version: 1
|
||||
date: '2021-07-12'
|
||||
version: 2
|
||||
date: '2022-02-18'
|
||||
author: Teoderick Contreras, Splunk
|
||||
type: TTP
|
||||
datamodel:
|
||||
@@ -12,11 +12,14 @@ description: This search is to detect modification of registry to bypass UAC win
|
||||
the registry key and values in the detection area. It may happened that windows
|
||||
update some dll related to mmc.exe and add dll path in this registry. In this case
|
||||
filtering is needed.
|
||||
search: '| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time)
|
||||
as lastTime from datamodel=Endpoint.Registry where Registry.registry_path= "*\\Environment\\COR_PROFILER_PATH"
|
||||
Registry.registry_value_name = "*.dll" by Registry.registry_path Registry.registry_key_name
|
||||
Registry.registry_value_name Registry.dest | `drop_dm_object_name(Registry)` | `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)` | `net_profiler_uac_bypass_filter`'
|
||||
search: '| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime
|
||||
from datamodel=Endpoint.Registry where
|
||||
Registry.registry_path= "*\\Environment\\COR_PROFILER_PATH" Registry.registry_value_data = "*.dll"
|
||||
by Registry.registry_path Registry.registry_key_name Registry.registry_value_data Registry.dest
|
||||
| `drop_dm_object_name(Registry)`
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `net_profiler_uac_bypass_filter`'
|
||||
how_to_implement: To successfully implement this search you need to be ingesting information
|
||||
on process that include the name of the process responsible for the changes from
|
||||
your endpoints into the `Endpoint` datamodel in the `Registry` node. Also make sure
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Powershell Remove Windows Defender Directory
|
||||
id: adf47620-79fa-11ec-b248-acde48001122
|
||||
version: 1
|
||||
date: '2022-01-20'
|
||||
version: 2
|
||||
date: '2022-01-18'
|
||||
author: Teoderick Contreras, Splunk
|
||||
type: TTP
|
||||
datamodel:
|
||||
@@ -11,10 +11,11 @@ description: This analytic will identify a suspicious PowerShell command used to
|
||||
campaign where it used Nirsofts advancedrun.exe to gain administrative privileges
|
||||
to then execute a PowerShell command to delete the Windows Defender folder. This
|
||||
is a good indicator the offending process is trying corrupt a Windows Defender installation.
|
||||
search: '`powershell` EventCode=4104 Message = "* rmdir *" AND Message = "*\\Microsoft\\Windows
|
||||
Defender*" | stats count min(_time) as firstTime max(_time) as lastTime by EventCode
|
||||
Message ComputerName User | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`
|
||||
| `powershell_remove_windows_defender_directory_filter`'
|
||||
search: '`powershell` EventCode=4104 Message = "*rmdir *" AND Message = "*\\Microsoft\\Windows Defender*"
|
||||
| stats count min(_time) as firstTime max(_time) as lastTime by EventCode Message ComputerName User
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `powershell_remove_windows_defender_directory_filter` '
|
||||
how_to_implement: To successfully implement this analytic, you will need to enable
|
||||
PowerShell Script Block Logging on some or all endpoints. Additional setup here
|
||||
https://docs.splunk.com/Documentation/UBA/5.0.4.1/GetDataIn/AddPowerShell#Configure_module_logging_for_PowerShell.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Process Deleting Its Process File Path
|
||||
id: f7eda4bc-871c-11eb-b110-acde48001122
|
||||
version: 1
|
||||
date: '2021-03-17'
|
||||
version: 2
|
||||
date: '2022-02-18'
|
||||
author: Teoderick Contreras
|
||||
type: TTP
|
||||
datamodel:
|
||||
@@ -11,11 +11,13 @@ description: This detection is to identify a suspicious process that tries to de
|
||||
evasion once a certain condition of malware is satisfied or not. Clop ransomware
|
||||
use this technique where it will try to delete its process file path using a .bat
|
||||
command if the keyboard layout is not the layout it tries to infect.
|
||||
search: '`sysmon` EventCode=1 cmdline = "* /c *" cmdline = "* del*" Image = "*\\cmd.exe"
|
||||
|eval result = if(like(process,"%".parent_process."%"), "Found", "Not Found") |
|
||||
stats min(_time) as firstTime max(_time) as lastTime count by Computer user ParentImage
|
||||
ParentCommandLine Image cmdline EventCode ProcessID result | where result = "Found"
|
||||
| `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `process_deleting_its_process_file_path_filter`'
|
||||
search: '`sysmon` EventCode=1 CommandLine = "* /c *" CommandLine = "* del*" Image = "*\\cmd.exe"
|
||||
| eval result = if(like(process,"%".parent_process."%"), "Found", "Not Found")
|
||||
| stats min(_time) as firstTime max(_time) as lastTime count by Computer user ParentImage ParentCommandLine Image CommandLine EventCode ProcessID result
|
||||
| where result = "Found"
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `process_deleting_its_process_file_path_filter`'
|
||||
how_to_implement: You must be ingesting data that records process activity from your
|
||||
hosts to populate the Endpoint data model in the Processes node. You must also be
|
||||
ingesting logs with both the process name and command line from your endpoints.
|
||||
@@ -34,8 +36,8 @@ tags:
|
||||
automated_detection_testing: passed
|
||||
confidence: 100
|
||||
context:
|
||||
- source:endpoint
|
||||
- stage: Credential Access
|
||||
- Source:Endpoint
|
||||
- Stage:Credential Access
|
||||
dataset:
|
||||
- https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/malware/clop/clop_a/windows-sysmon.log
|
||||
impact: 60
|
||||
@@ -51,7 +53,7 @@ tags:
|
||||
role:
|
||||
- Victim
|
||||
- name: user
|
||||
type: user
|
||||
type: User
|
||||
role:
|
||||
- Victim
|
||||
product:
|
||||
|
||||
@@ -2,14 +2,14 @@ name: Registry Keys Used For Persistence
|
||||
id: f5f6af30-7aa7-4295-bfe9-07fe87c01a4b
|
||||
version: 7
|
||||
date: '2022-01-26'
|
||||
author: Jose Hernandez, David Dorsey, Teoderick Contreras, Splunk
|
||||
author: Jose Hernandez, David Dorsey, Teoderick Contreras, Rod Soto, Splunk
|
||||
type: TTP
|
||||
datamodel:
|
||||
- Endpoint
|
||||
description: The search looks for modifications to registry keys that can be used
|
||||
to launch an application or service at system startup.
|
||||
search: '| tstats `security_content_summariesonly` count FROM datamodel=Endpoint.Registry
|
||||
where (Registry.registry_path=*\\currentversion\\run* OR Registry.registry_path=*\\currentVersion\\Windows\\Appinit_Dlls*
|
||||
where (Registry.registry_path=*\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce OR Registry.registry_path=*\\currentversion\\run* OR Registry.registry_path=*\\currentVersion\\Windows\\Appinit_Dlls*
|
||||
OR Registry.registry_path=*\\CurrentVersion\\Winlogon\\Shell* OR Registry.registry_path=*\\CurrentVersion\\Winlogon\\Notify*
|
||||
OR Registry.registry_path=*\\CurrentVersion\\Winlogon\\Userinit* OR Registry.registry_path=*\\CurrentVersion\\Winlogon\\VmApplet*
|
||||
OR Registry.registry_path=*\\currentversion\\policies\\explorer\\run* OR Registry.registry_path=*\\currentversion\\runservices*
|
||||
@@ -65,6 +65,7 @@ tags:
|
||||
- Privilege Escalation
|
||||
dataset:
|
||||
- https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1547.001/atomic_red_team/windows-sysmon.log
|
||||
- https://raw.githubusercontent.com/splunk/attack_data/master/datasets/attack_techniques/T1547.001/atomic_red_team/t1547001-runonce.log
|
||||
impact: 80
|
||||
kill_chain_phases:
|
||||
- Actions on Objectives
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Rundll32 DNSQuery
|
||||
id: f1483f5e-ee29-11eb-9d23-acde48001122
|
||||
version: 1
|
||||
date: '2021-07-26'
|
||||
version: 2
|
||||
date: '2022-02-18'
|
||||
author: Teoderick Contreras, Splunk
|
||||
type: TTP
|
||||
datamodel:
|
||||
@@ -11,9 +11,10 @@ description: This search is to detect a suspicious rundll32.exe process having a
|
||||
malware where the rundll32 that execute its payload will contact amazon.com to check
|
||||
internet connect and to communicate to its C&C server to download config and other
|
||||
file component.
|
||||
search: '`sysmon` EventCode=22 process_name="rundll32.exe" | stats count min(_time)
|
||||
as firstTime max(_time) as lastTime by Image QueryName QueryStatus ProcessId direction
|
||||
Computer | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`
|
||||
search: '`sysmon` EventCode=22 process_name="rundll32.exe"
|
||||
| stats count min(_time) as firstTime max(_time) as lastTime by Image QueryName QueryStatus ProcessId Computer
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `rundll32_dnsquery_filter`'
|
||||
how_to_implement: To successfully implement this search, you need to be ingesting
|
||||
logs with the process name and eventcode = 22 dnsquery executions from your endpoints.
|
||||
@@ -59,7 +60,6 @@ tags:
|
||||
- QueryName
|
||||
- QueryStatus
|
||||
- ProcessId
|
||||
- direction
|
||||
- Computer
|
||||
risk_score: 56
|
||||
security_domain: endpoint
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
name: Scheduled Task Deleted Or Created via CMD
|
||||
id: d5af132c-7c17-439c-9d31-13d55340f36c
|
||||
version: 5
|
||||
date: '2020-12-17'
|
||||
version: 6
|
||||
date: '2022-02-22'
|
||||
author: Bhavin Patel, Splunk
|
||||
type: TTP
|
||||
datamodel:
|
||||
- Endpoint
|
||||
description: This search looks for flags passed to schtasks.exe on the command-line
|
||||
that indicate a task was created via command like. This has been associated with
|
||||
description: The following analytic identifies the creation or deletion of a scheduled task using schtasks.exe with flags - create or delete being passed on the command-line. This has been associated with
|
||||
the Dragonfly threat actor, and the SUNBURST attack against Solarwinds.
|
||||
This analytic replaces "Scheduled Task used in BadRabbit Ransomware".
|
||||
search: '| tstats `security_content_summariesonly` count values(Processes.process)
|
||||
as process values(Processes.parent_process) as parent_process min(_time) as firstTime
|
||||
max(_time) as lastTime from datamodel=Endpoint.Processes where Processes.process_name=schtasks.exe
|
||||
(Processes.process=*delete* OR Processes.process=*create*) by Processes.user Processes.process_name
|
||||
Processes.parent_process_name Processes.dest | `drop_dm_object_name(Processes)`
|
||||
| `security_content_ctime(firstTime)`| `security_content_ctime(lastTime)` | `scheduled_task_deleted_or_created_via_cmd_filter` '
|
||||
how_to_implement: You must be ingesting endpoint data that tracks process activity,
|
||||
including parent-child relationships from your endpoints to populate the Endpoint
|
||||
data model in the Processes node. The command-line arguments are mapped to the "process"
|
||||
field in the Endpoint data model.
|
||||
known_false_positives: Tasks should not be manually created via CLI, this is rarely
|
||||
done by admins as well
|
||||
references: []
|
||||
how_to_implement: To successfully implement this search you need to be ingesting information on process that include the name of the process responsible for the changes from your endpoints into the `Endpoint` datamodel in the `Processes` node. In addition, confirm the latest CIM App 4.20 or higher is installed and the latest TA for the endpoint product.
|
||||
known_false_positives: It is possible scripts or administrators may trigger this analytic. Filter as needed based on parent process, application.
|
||||
references:
|
||||
- https://thedfirreport.com/2022/02/21/qbot-and-zerologon-lead-to-full-domain-compromise/
|
||||
tags:
|
||||
analytic_story:
|
||||
- DHS Report TA18-074A
|
||||
- NOBELIUM Group
|
||||
- Windows Persistence Techniques
|
||||
asset_type: Endpoint
|
||||
automated_detection_testing: passed
|
||||
cis20:
|
||||
|
||||
+9
-7
@@ -1,18 +1,20 @@
|
||||
name: Set Default PowerShell Execution Policy To Unrestricted or Bypass
|
||||
id: c2590137-0b08-4985-9ec5-6ae23d92f63d
|
||||
version: 6
|
||||
date: '2020-11-06'
|
||||
version: 7
|
||||
date: '2022-02-18'
|
||||
author: Patrick Bareiss, Splunk
|
||||
type: TTP
|
||||
datamodel:
|
||||
- Endpoint
|
||||
description: Monitor for changes of the ExecutionPolicy in the registry to the values
|
||||
"unrestricted" or "bypass," which allows the execution of malicious scripts.
|
||||
search: '| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time)
|
||||
as lastTime from datamodel=Endpoint.Registry where Registry.registry_path=*Software\\Microsoft\\Powershell\\1\\ShellIds\\Microsoft.PowerShell*
|
||||
Registry.registry_key_name=ExecutionPolicy (Registry.registry_value_name=Unrestricted
|
||||
OR Registry.registry_value_name=Bypass) by Registry.registry_path Registry.registry_key_name
|
||||
Registry.registry_value_name Registry.dest | `drop_dm_object_name(Registry)` | `security_content_ctime(firstTime)`|`security_content_ctime(lastTime)`
|
||||
search: '| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime from
|
||||
datamodel=Endpoint.Registry where Registry.registry_path=*Software\\Microsoft\\Powershell\\1\\ShellIds\\Microsoft.PowerShell*
|
||||
Registry.registry_value_name=ExecutionPolicy (Registry.registry_value_data=Unrestricted OR Registry.registry_value_data=Bypass)
|
||||
by Registry.registry_path Registry.registry_value_name Registry.registry_value_data Registry.dest
|
||||
| `drop_dm_object_name(Registry)`
|
||||
| `security_content_ctime(firstTime)`
|
||||
| `security_content_ctime(lastTime)`
|
||||
| `set_default_powershell_execution_policy_to_unrestricted_or_bypass_filter`'
|
||||
how_to_implement: You must be ingesting data that records process activity from your
|
||||
hosts to populate the Endpoint data model in the Registry node. You must also be
|
||||
|
||||
@@ -11,7 +11,6 @@ description: The following analytic identifies parent processes, browsers, Windo
|
||||
many applications spawn cmd.exe natively or built into macros. Much of this will
|
||||
need to be tuned to further enhance the risk.
|
||||
search: '| from read_ssa_enriched_events()
|
||||
|
||||
| eval timestamp=parse_long(ucast(map_get(input_event, "_time"), "string", null))
|
||||
| eval process_name=ucast(map_get(input_event, "process_name"), "string", null),
|
||||
parent_process=lower(ucast(map_get(input_event, "parent_process_name"), "string",
|
||||
@@ -23,7 +22,7 @@ search: '| from read_ssa_enriched_events()
|
||||
OR ParentBaseFileName="powerpnt.exe" OR ParentBaseFileName="visio.exe" OR ParentBaseFileName="mspub.exe"
|
||||
OR ParentBaseFileName="acrobat.exe" OR ParentBaseFileName="acrord32.exe" OR ParentBaseFileName="iexplore.exe"
|
||||
OR ParentBaseFileName="opera.exe" OR ParentBaseFileName="firefox.exe" OR (ParentBaseFileName="java.exe"
|
||||
AND (cmd_line IS NULL OR (cmd_line IS NOT NULL AND NOT like(cmd_line, "%patch1-Hotfix1a%"))))
|
||||
AND (cmd_line IS NULL OR (cmd_line IS NOT NULL AND match_regex(cmd_line, /(?i)patch1-Hotfix1a/)=false)))
|
||||
OR ParentBaseFileName="powershell.exe" OR (ParentBaseFileName="chrome.exe" AND (cmd_line
|
||||
IS NULL OR (cmd_line IS NOT NULL AND NOT like(cmd_line, "%chrome-extension%"))))
|
||||
| eval start_time=timestamp, end_time=timestamp, entities=mvappend(dest_device_id,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Windows Diskshadow Proxy Execution
|
||||
id: aa502688-9037-11ec-842d-acde48001122
|
||||
version: 1
|
||||
date: '2022-02-17'
|
||||
author: Lou Stella, Splunk
|
||||
type: Anomaly
|
||||
datamodel:
|
||||
- Endpoint_Processes
|
||||
description: DiskShadow.exe is a Microsoft Signed binary present on Windows Server. It has a scripting mode intended for complex scripted backup operations. This feature also allows for execution of arbitrary unsigned code. This analytic looks for the usage of the scripting mode flags in executions of DiskShadow. During triage, compare to known backup behavior in your environment and then review the scripts called by diskshadow.
|
||||
search: '| from read_ssa_enriched_events() | where "Endpoint_Processes" IN(_datamodels) | eval timestamp=parse_long(ucast(map_get(input_event, "_time"), "string", null)), cmd_line=lower(ucast(map_get(input_event, "process"), "string", null)), process_name=lower(ucast(map_get(input_event, "process_name"), "string", null)), process_path=ucast(map_get(input_event, "process_path"), "string", null), parent_process_name=ucast(map_get(input_event, "parent_process_name"), "string", null), event_id=ucast(map_get(input_event, "event_id"), "string", null) | where cmd_line IS NOT NULL AND process_name IS NOT NULL AND process_name="diskshadow.exe" AND (like (cmd_line, "%-s%") OR like (cmd_line, "%/s%")) | eval start_time=timestamp, end_time=timestamp, entities=mvappend(ucast(map_get(input_event, "dest_user_id"), "string", null), ucast(map_get(input_event, "dest_device_id"), "string", null)) | eval body=create_map(["event_id", event_id, "cmd_line", cmd_line, "process_name", process_name, "parent_process_name", parent_process_name, "process_path", process_path]) | into write_ssa_detected_events();'
|
||||
how_to_implement: To successfully implement this search you need to be ingesting information on processes that include the name of the process responsible for the changes from your endpoints into the `Endpoint_Processess` datamodel.
|
||||
known_false_positives: Administrators using the DiskShadow tool in their infrastructure as a main backup tool with scripts will cause false positives
|
||||
references:
|
||||
- https://bohops.com/2018/03/26/diskshadow-the-return-of-vss-evasion-persistence-and-active-directory-database-extraction/
|
||||
tags:
|
||||
analytic_story:
|
||||
- Living Off The Land
|
||||
dataset:
|
||||
- https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1218/diskshadow/windows-security.log
|
||||
kill_chain_phases:
|
||||
- Exploitation
|
||||
mitre_attack_id:
|
||||
- T1218
|
||||
product:
|
||||
- Splunk Behavioral Analytics
|
||||
required_fields:
|
||||
- _time
|
||||
- dest_device_id
|
||||
- process_name
|
||||
- parent_process_name
|
||||
- process_path
|
||||
- dest_user_id
|
||||
- process
|
||||
- cmd_line
|
||||
security_domain: endpoint
|
||||
impact: 70
|
||||
confidence: 70
|
||||
risk_score: 49
|
||||
context:
|
||||
- Source:Endpoint
|
||||
- Stage:Execution
|
||||
message: An instance of $parent_process_name$ spawning $process_name$ was identified on endpoint $dest_device_id$ by user $dest_user_id$ attempting to run a script.
|
||||
observable:
|
||||
- name: dest_device_id
|
||||
type: Hostname
|
||||
role:
|
||||
- Victim
|
||||
- name: dest_user_id
|
||||
type: User
|
||||
role:
|
||||
- Victim
|
||||
- name: parent_process_name
|
||||
type: Parent Process
|
||||
role:
|
||||
- Parent Process
|
||||
- name: process_name
|
||||
type: Process
|
||||
role:
|
||||
- Child Process
|
||||
nist:
|
||||
- DE.CM
|
||||
cis20:
|
||||
- CIS 8
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Data Destruction
|
||||
id: 4ae5c0d1-cebd-47d1-bfce-71bf096e38aa
|
||||
version: 1
|
||||
date: '2022-02-14'
|
||||
author: Teoderick Contreras, Splunk
|
||||
description: Leverage searches that allow you to detect and investigate unusual activities
|
||||
that might relate to the data destruction, including deleting files, overwriting files, wiping disk and encrypting files.
|
||||
narrative: Adversaries may use this technique to maximize the impact on the target organization in operations where network wide availability interruption
|
||||
is the goal.
|
||||
references:
|
||||
- https://attack.mitre.org/techniques/T1485/
|
||||
- https://researchcenter.paloaltonetworks.com/2018/09/unit42-xbash-combines-botnet-ransomware-coinmining-worm-targets-linux-windows/
|
||||
- https://www.picussecurity.com/blog/a-brief-history-and-further-technical-analysis-of-sodinokibi-ransomware
|
||||
tags:
|
||||
analytic_story: Data Destruction
|
||||
category:
|
||||
- Malware
|
||||
product:
|
||||
- Splunk Enterprise
|
||||
- Splunk Enterprise Security
|
||||
- Splunk Cloud
|
||||
usecase: Advanced Threat Detection
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Network Discovery
|
||||
id: af228995-f182-49d7-90b3-2a732944f00f
|
||||
version: 1
|
||||
date: '2022-02-14'
|
||||
author: Teoderick Contreras, Splunk
|
||||
description: Leverage searches that allow you to detect and investigate unusual activities
|
||||
that might relate to the network discovery, including looking for network configuration, settings such as IP, MAC address,
|
||||
firewall settings and many more.
|
||||
narrative: Adversaries may use the information from System Network Configuration Discovery during automated discovery to shape follow-on behaviors,
|
||||
including determining certain access within the target network and what actions to do next.
|
||||
references:
|
||||
- https://attack.mitre.org/techniques/T1016/
|
||||
- https://www.welivesecurity.com/wp-content/uploads/2021/01/ESET_Kobalos.pdf
|
||||
- https://researchcenter.paloaltonetworks.com/2018/09/unit42-xbash-combines-botnet-ransomware-coinmining-worm-targets-linux-windows/
|
||||
tags:
|
||||
analytic_story: Network Discovery
|
||||
category:
|
||||
- Malware
|
||||
product:
|
||||
- Splunk Enterprise
|
||||
- Splunk Enterprise Security
|
||||
- Splunk Cloud
|
||||
usecase: Advanced Threat Detection
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Linux DD File Overwrite Unit Test
|
||||
tests:
|
||||
- name: Linux DD File Overwrite
|
||||
file: endpoint/linux_dd_file_overwrite.yml
|
||||
pass_condition: '| stats count | where count > 0'
|
||||
earliest_time: '-24h'
|
||||
latest_time: 'now'
|
||||
attack_data:
|
||||
- file_name: sysmon_linux.log
|
||||
data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1485/linux_dd_file_overwrite/sysmon_linux.log
|
||||
source: Syslog:Linux-Sysmon/Operational
|
||||
sourcetype: sysmon_linux
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Linux System Network Discovery Unit Test
|
||||
tests:
|
||||
- name: Linux System Network Discovery
|
||||
file: endpoint/linux_system_network_discovery.yml
|
||||
pass_condition: '| stats count | where count > 0'
|
||||
earliest_time: '-24h'
|
||||
latest_time: 'now'
|
||||
attack_data:
|
||||
- file_name: sysmon_linux.log
|
||||
data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1016/atomic_red_team/linux_net_discovery/sysmon_linux.log
|
||||
source: Syslog:Linux-Sysmon/Operational
|
||||
sourcetype: sysmon_linux
|
||||
@@ -2,7 +2,7 @@ name: Process execution via wmi Unit Test
|
||||
tests:
|
||||
- name: Process execution via wmi
|
||||
file: endpoint/process_execution_via_wmi.yml
|
||||
pass_condition: '| stats count | where count = 1'
|
||||
pass_condition: '| stats count | where count > 0'
|
||||
earliest_time: '-24h'
|
||||
latest_time: 'now'
|
||||
attack_data:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
name: BA Windows Diskshadow Proxy Execution Unit Test
|
||||
tests:
|
||||
- name: BA Windows Diskshadow Proxy Execution
|
||||
file: endpoint/ssa___windows_diskshadow_proxy_execution.yml
|
||||
pass_condition: '@count_gt(0)'
|
||||
attack_data:
|
||||
- file_name: windows-security.log
|
||||
data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1218/diskshadow/windows-security.log
|
||||
source: WinEventLog:Security
|
||||
Reference in New Issue
Block a user