Merge pull request #2077 from splunk/refactored_security_content_new

Refactored security content
This commit is contained in:
Bhavin Patel
2022-03-14 14:15:42 -07:00
committed by GitHub
3187 changed files with 655548 additions and 77729 deletions
+52 -104
View File
@@ -10,8 +10,6 @@
#https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts
#In CircleCI, this was different (store_artifacts vs persist_to_workspace)
name: build-and-validate
on:
push:
@@ -83,43 +81,41 @@ jobs:
python3 -m pip install --upgrade pip
python3 -m pip install wheel
python3 -m pip install -q -r requirements.txt
- name: run validate
run: |
source venv/bin/activate
python3 contentctl.py --path . --verbose validate
python3 contentctl.py -p . validate -pr ESCU
python3 contentctl.py -p . validate -pr SSA
- name: Get CTI Repo for Mitre context
uses: actions/checkout@v2
with:
repository: mitre/cti #check out https://github.com/mitre/cti.git, defaults to HEAD
path: "cti/"
# - name: Get CTI Repo for Mitre context
# uses: actions/checkout@v2
# with:
# repository: mitre/cti #check out https://github.com/mitre/cti.git, defaults to HEAD
# path: "cti/"
#Now generate the documentation (uses Node)
- uses: actions/setup-node@v2
with:
node-version: '14' #can easily be changed to a different version
- name: Generate documentation
run: |
ls -lah
#Enter the virtualenv and run the docgen
source venv/bin/activate
python3 bin/doc_gen.py --path . --output docs -v
python3 contentctl.py -p . docgen -o docs
#Now generate the spec docs
npm install -g @adobe/jsonschema2md
jsonschema2md -d spec -o docs/spec -f yaml -e spec.json -x -
#Clean up extra properties on docs
rm -rf docs/spec/*-*.md
echo "****** BRANCH INFORMATION ******"
git branch
git branch --show-current
build-sources:
runs-on: ubuntu-latest
needs: validate-content
@@ -127,11 +123,16 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9' #Available versions here - https://github.com/actions/python-versions/releases easy to change/make a matrix/use pypy
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- name: Install System Packages
run: |
sudo apt update -qq
sudo apt install jq -qq
- name: Install Python Dependencies
run: |
#Get the virtualenv set up
@@ -141,22 +142,13 @@ jobs:
python3 -m pip install --upgrade pip
python3 -m pip install wheel
python3 -m pip install -q -r requirements.txt
- name: Run Generate
run: |
source venv/bin/activate
python3 contentctl.py --path . --verbose generate --product ESCU --output dist/escu
python3 contentctl.py --path . --verbose generate --product SSA --output dist/ssa
- name: Copy lookups .csv files
run: |
# clean up current lookups
rm -rf dist/escu/lookups
mkdir dist/escu/lookups
#copy over lookups
cd lookups
cp -rv *.csv ../dist/escu/lookups
python3 contentctl.py --path . generate --product ESCU --output dist/escu
python3 contentctl.py --path . generate --product SSA --output dist/ssa
- name: Copy lookups .mlmodel files
run: |
cd lookups
@@ -165,7 +157,7 @@ jobs:
then cp -rv *.mlmodel ../dist/escu/lookups
else echo "No mlmodel files to copy"
fi
#Tag is '' for non-tagged push and the tag name for a tagged release
- name: Set tag
id: vars
@@ -179,7 +171,7 @@ jobs:
echo "Release is NOT TAGGED!"
echo "::set-output name=tag::"
fi
- name: Update Version and Build number
run : |
# check if tag is set, get build number from the tag if set
@@ -196,10 +188,9 @@ jobs:
sed -i "s/\"version\": .*$/\"version\": \"$CONTENT_VERSION\"/g" dist/escu/app.manifest
sed -i "s/version = .*$/version = $CONTENT_VERSION/g" dist/escu/default/content-version.conf
tar -czf content-pack-build-escu.tar.gz dist/escu/*
# update build number and version for ssa
tar -czf content-pack-build-ssa.tar.gz dist/ssa/*
- name: Persist to Workspace
uses: actions/upload-artifact@v2
with:
@@ -207,8 +198,7 @@ jobs:
path: |
content-pack-build-escu.tar.gz
content-pack-build-ssa.tar.gz
build-package:
runs-on: ubuntu-latest
needs: [validate-content, build-sources]
@@ -228,13 +218,13 @@ jobs:
- name: Get virtualenv for Python 2.7
run: |
sudo apt install virtualenv
- name: Grab Splunk Packaging Toolkit
run : |
curl -Ls https://download.splunk.com/misc/packaging-toolkit/splunk-packaging-toolkit-0.9.0.tar.gz -o splunk-packaging-toolkit-latest.tar.gz
mkdir slim-latest
tar -zxf splunk-packaging-toolkit-latest.tar.gz -C slim-latest --strip-components=1
- name: Install Splunk Packaging Toolkit (slim)
run: |
cd slim-latest
@@ -244,36 +234,32 @@ jobs:
python2 -m pip install wheel
python2 -m pip install semantic_version
python2 -m pip install .
- name: Create a .spl for this Build Using Slim
run: |
source slim-latest/venv/bin/activate
cd build
tar -zxf content-pack-build-escu.tar.gz
tar -zxf content-pack-build-ssa.tar.gz
mv dist/escu DA-ESS-ContentUpdate
mv dist/ssa SSA_Content
slim package -o upload DA-ESS-ContentUpdate
cp upload/DA-ESS-ContentUpdate-*.tar.gz DA-ESS-ContentUpdate-latest.tar.gz
sha256sum DA-ESS-ContentUpdate-latest.tar.gz > checksum.txt
#Do this copy so that we conform as much as possible, and have to make
#as few changes as possible, once we start generating this as a real,
#properly packaged app
tar -zcf upload/SSA_Content-NO_SLIM.tar.gz SSA_Content
cp upload/SSA_Content-*.tar.gz SSA_Content-latest.tar.gz
sha256sum SSA_Content-latest.tar.gz >> checksum.txt
- name: store_artifacts
uses: actions/upload-artifact@v2
with:
name: package
path: |
build/upload
- name: store_artifacts_two
uses: actions/upload-artifact@v2
with:
@@ -282,13 +268,8 @@ jobs:
build/DA-ESS-ContentUpdate-latest.tar.gz
build/SSA_Content-latest.tar.gz
build/checksum.txt
#Everything below this line should ONLY run on a tag and nothing else
#We still want all of the above checks to run and pass before running these
run-appinspect:
@@ -309,16 +290,11 @@ jobs:
name: content-latest
path: build/
- name: Install System Packages
run: |
sudo apt update -qq
sudo apt install jq -qq
- name: Submit ESCU to AppInspect API
env:
APPINSPECT_USERNAME: ${{ secrets.AppInspectUsername }}
@@ -328,20 +304,20 @@ jobs:
#Enclose in quotes in case there are any special characters in the username/password
#Better not to pass these arguments on the command line, if possible
./appinspect.sh ../ DA-ESS-ContentUpdate-latest.tar.gz "$APPINSPECT_USERNAME" "$APPINSPECT_PASSWORD"
- name: Create report artifact
if: always()
run: |
#Always create this, regardless of whether success or failure above
tar -cvzf report.tar.gz report/
- name: store_artifacts
uses: actions/upload-artifact@v2
with:
name: appinspect_reports
path: |
report.tar.gz
#Still store the report, even if we have failed (otherwise we don't know why/how we failed)
- name: store_artifacts_on_failure
uses: actions/upload-artifact@v2
@@ -350,7 +326,8 @@ jobs:
name: appinspect_reports_failure
path: |
report.tar.gz
create-report:
runs-on: ubuntu-latest
needs: [validate-content, build-sources, build-package, run-appinspect]
@@ -362,12 +339,11 @@ jobs:
with:
ref: 'develop'
- name: Install System Packages
run: |
sudo apt update -qq
sudo apt install jq -qq
- uses: actions/setup-python@v2
with:
python-version: '3.9' #Available versions here - https://github.com/actions/python-versions/releases easy to change/make a matrix/use pypy
@@ -382,12 +358,12 @@ jobs:
python3 -m pip install --upgrade pip
python3 -m pip install wheel
python3 -m pip install -q -r requirements.txt
- name: run reporting
run: |
source venv/bin/activate
python3 bin/reporting.py
python3 contentctl.py -p . reporting
#Official, Verified Amazon-AWS Github Account Provided Action
- uses: aws-actions/configure-aws-credentials@v1
with:
@@ -399,7 +375,7 @@ jobs:
- name: Upload Reporting
run: |
aws s3 cp bin/reporting s3://security-content/reporting --recursive --exclude "*" --include "*.svg"
update-sources-github:
runs-on: ubuntu-latest
needs: [validate-content, build-sources, build-package, run-appinspect, create-report]
@@ -422,13 +398,13 @@ jobs:
with:
name: content-latest
- name: Stage artifacts in proper directories
run: |
mkdir latest-escu
tar -zxf DA-ESS-ContentUpdate-latest.tar.gz -C latest-escu --strip-components=1
mkdir latest-ssa
tar -zxf SSA_Content-latest.tar.gz -C latest-ssa --strip-components=1
- name: Install Python Dependencies
run: |
#Get the virtualenv set up
@@ -439,21 +415,15 @@ jobs:
python3 -m pip install wheel
python3 -m pip install -q -r requirements.txt
- name: Get CTI Repo for Mitre context
uses: actions/checkout@v2
with:
repository: mitre/cti #check out https://github.com/mitre/cti.git, defaults to HEAD
path: "cti/"
- name: Get branch and PR required for detection testing main.py
id: vars
run: |
echo "::set-output name=branch::${GITHUB_REF#refs/heads/}"
- name: Run doc-gen
run: |
source venv/bin/activate
python3 bin/doc_gen.py --path . --output docs -v
python3 contentctl.py -p . docgen -o docs
# this step is creating some TOKEN issues while tagging a build, commenting this out for now
@@ -465,16 +435,6 @@ jobs:
# python3 -m pip install -r security_content_automation/requirements.txt
# python3 security_content_automation/enrich_detections.py
- name: Make YAMLs Pretty
run: |
source venv/bin/activate
python3 bin/pretty_yaml.py --path . -v
- name: Run generate-coverage-map
run: |
source venv/bin/activate
python3 bin/generate-coverage-map.py --projects_path . --output docs/mitre-map
- name: Update github with new docs and package bits
run: |
rm -rf dist
@@ -492,8 +452,7 @@ jobs:
# Push quietly to prevent showing the token in log
#No need to provide any credentials
git push
publish-github-release:
#Github-maintained release action is in archived state: https://github.com/actions/create-release
#They recommend several and we use the following with the most stars: https://github.com/softprops/action-gh-release
@@ -512,8 +471,6 @@ jobs:
with:
name: appinspect_reports
#Rename those artifacts appropriately
- name: Set tag
id: vars
@@ -523,12 +480,10 @@ jobs:
run: |
cp DA-ESS-ContentUpdate-latest.tar.gz DA-ESS-ContentUpdate-${{ steps.vars.outputs.tag }}.tar.gz
cp SSA_Content-latest.tar.gz SSA_Content-${{ steps.vars.outputs.tag }}.tar.gz
#No checksum on the reports
cp report.tar.gz report-${{ steps.vars.outputs.tag }}.tar.gz
cp checksum.txt checksum-${{ steps.vars.outputs.tag }}.txt
#Upload all of the artifacts that we have created using the third party
#action recommended bu Github
- name: Upload Release Artifacts
@@ -539,9 +494,7 @@ jobs:
SSA_Content-${{ steps.vars.outputs.tag }}.tar.gz
report-${{ steps.vars.outputs.tag }}.tar.gz
checksum-${{ steps.vars.outputs.tag }}.txt
attack-range-update:
runs-on: ubuntu-latest
needs: [validate-content, build-sources, build-package, run-appinspect, create-report, update-sources-github, publish-github-release]
@@ -554,7 +507,6 @@ jobs:
with:
name: content-latest
#Official, Verified Amazon-AWS Github Account Provided Action
- uses: aws-actions/configure-aws-credentials@v1
with:
@@ -568,7 +520,7 @@ jobs:
aws s3 cp DA-ESS-ContentUpdate-latest.tar.gz s3://attack-range-appbinaries/
# make the file public since it is not by default
aws s3api put-object-acl --bucket attack-range-appbinaries --key DA-ESS-ContentUpdate-latest.tar.gz --acl public-read
master-api-update:
runs-on: ubuntu-latest
needs: [validate-content, build-sources, build-package, run-appinspect, create-report, update-sources-github, publish-github-release, attack-range-update]
@@ -596,16 +548,11 @@ jobs:
python3 -m pip install wheel
python3 -m pip install -q -r requirements.txt
- name: Create Baseline Folder
run: |
source venv/bin/activate
python3 bin/create_baseline_folder.py
- name: Create YML to JSON Folder
run: |
source venv/bin/activate
python3 contentctl.py --path . --verbose generate --product API --output dist/api
python3 contentctl.py --path . generate --product API --output dist/api
#Official, Verified Amazon-AWS Github Account Provided Action
- uses: aws-actions/configure-aws-credentials@v1
with:
@@ -627,6 +574,7 @@ jobs:
aws s3 cp macros s3://security-content/macros --recursive --exclude "*" --include "*.yml"
aws s3 cp deployments s3://security-content/deployments --recursive --exclude "*" --include "*.yml"
aws s3 cp dist/api s3://security-content/json --recursive --exclude "*" --include "*.json"
- name: Security Content API Smoke Test
run: |
API_URL='https://content.splunkresearch.com/detections'
@@ -635,4 +583,4 @@ jobs:
if [ "$API_STATUS" != "200" ]; then
echo "Error [Security Content API status: $API_STATUS]"
exit 1
fi
fi
+38
View File
@@ -0,0 +1,38 @@
name: code-testing
on:
push:
paths:
- 'bin/contentctl_project/**'
jobs:
code-testing:
runs-on: ubuntu-latest
steps:
- name: Check out the repository code
uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9' #Available versions here - https://github.com/actions/python-versions/releases easy to change/make a matrix/use pypy
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
- name: Install System Packages
run: |
sudo apt update -qq
sudo apt install jq -qq
- name: Install Python Dependencies
run: |
#Get the virtualenv set up
rm -rf venv
python3 -m venv --clear venv
source venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install wheel
python3 -m pip install -q -r requirements.txt
- name: test code with pytest
run: |
source venv/bin/activate
export PYTHONPATH=$PYTHONPATH:/home/runner/work/security_content
pytest -s bin/contentctl_project
-45
View File
@@ -1,45 +0,0 @@
# This file uses .gitignore syntax:
#
# To ignore a file anywhere it occurs in your project, enter a
# glob pattern here. E.g. "*.min.js".
#
# To ignore a directory anywhere it occurs in your project, add
# a trailing slash to the file name. E.g. "dist/".
#
# To ignore a file or directory only relative to the project root,
# include a slash anywhere except the last character. E.g.
# "/dist/", or "src/generated".
#
# Some parts of .gitignore syntax are not supported, and patterns
# using this syntax will be dropped from the ignore list:
# - Explicit "include syntax", e.g. "!kept/".
# - Multi-character expansion syntax, e.g. "*.py[cod]"
#
# To include ignore patterns from another file, start a line
# with ':include', followed by the path of the file. E.g.
# ":include path/to/other/ignore/file".
#
# To ignore a file with a literal ':' character, escape it with
# a backslash, e.g. "\:foo".
# Ignore git items
.gitignore
.git/
:include .gitignore
#spl files may contain eval and other statements that should NOT trigger semgrep warnings
*.spl
#Ignore Markdown and Wiki Pages
*.md
*.wiki
#Temporarily ignoring this directory as we discuss a path moving forward
#for Splunk Packaging Toolkit Update Strategy
/dist/
#Don't check yaml files in these directories
/response_tasks
/detections
+12 -11
View File
@@ -46,11 +46,12 @@ curl -s https://content.splunkresearch.com | jq
### contentctl.py
The Content Control tool allows you to manipulate Splunk Security Content via the following actions:
1. **new** - Creates new content (detection, story, baseline)
1. **new_content** - Creates new content (detection, story, baseline)
2. **validate** - Validates written content
3. **generate** - Generates a deployment package for different platforms (splunk_app)
### pre-requisites
Make sure you use python version 3.9 or higher.
```
git clone git@github.com:splunk/security_content.git
@@ -60,19 +61,19 @@ virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
```
### Architecture details for the tooling
- [WIKI](https://github.com/splunk/security_content/wiki/Security-Content-Code)
### create a new detection
`python contentctl.py new`
`python contentctl.py -p . new_content -t detection`
for a more indepth write up on how to write content see our [guide](https://github.com/splunk/security_content/wiki/Developing-Content).
### create a new analytic story
`python contentctl.py new -t story`
### validate written content a new analytic story
`python contentctl.py --verbose validate`
### validate security content
`python contentctl.py -p . validate -pr ESCU`
### generate a splunk app from current content
`python contentctl.py --path . --verbose generate --output package`
`python contentctl.py -p . generate -o dist/escu -pr ESCU`
# MITRE ATT&CK ⚔️
### Detection Coverage
@@ -91,9 +92,9 @@ A complete use case, specifically built to detect, investigate, and respond to a
* [detections/](detections/): Contains all 209 detection searches to-date and growing.
* [stories/](stories/): All Analytic Stories that are group detections or also known as Use Cases
* [deployments/](deployments/): Configuration for the schedule and alert action for all content
* [responses/](responses/): Incident Response Playbooks/Workflow for responding to a specific Use Case or Threat.
* [response_tasks/](response_tasks/): Individual steps in responses that help the user investigate via a Splunk search, automate via a phantom playbook, and visualize via dashboards threats.
* [playbooks/](playbooks/): Incident Response Playbooks/Workflow for responding to a specific Use Case or Threat.
* [baselines/](baselines/): Searches that must be executed before a detection runs. It is specifically useful for collecting data on a system before running your detection on the collected data.
* [investigations/](investigations/): Investigations to further analysis the output from detections.
* [dashboards/](dashboards/): JSON definitions of Mission Control dashboards, to be used as a response task. Currently not used.
* [macros/](macros/): Implements Splunks search macros, shortcuts to commonly used search patterns like sysmon source type. More on how macros are used to customize content below.
* [lookups/](lookups/): Implements Splunks lookup, usually to provide a list of static values like commonly used ransomware extensions.
@@ -115,7 +116,7 @@ If you have questions or need support, you can:
## License
Copyright 2020 Splunk Inc.
Copyright 2022 Splunk Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -26,8 +26,8 @@ references: []
tags:
analytic_story:
- AWS Network ACL Activity
- Command and Control
- Suspicious AWS Traffic
- Command and Control
deployments:
- Daily Cache Updates
detections:
@@ -39,7 +39,6 @@ tags:
detections:
- Abnormally High Number Of Cloud Infrastructure API Calls
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -42,7 +42,6 @@ tags:
detections:
- Abnormally High Number Of Cloud Instances Destroyed
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -42,7 +42,6 @@ tags:
detections:
- Abnormally High Number Of Cloud Instances Launched
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -38,7 +38,6 @@ tags:
detections:
- Abnormally High Number Of Cloud Security Group API Calls
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -27,9 +27,9 @@ known_false_positives: none
references: []
tags:
analytic_story:
- Command and Control
- Hidden Cobra Malware
- Suspicious DNS Traffic
- Command and Control
deployments:
- Daily Cache Updates
detections:
@@ -1,5 +1,5 @@
name: Baseline of S3 Bucket deletion activity by ARN
id: fc0edd96-ff2b-48b0-9f1f-63eq3783fd63
id: 841b102c-8866-494b-a704-87b674fe9b09
version: 1
date: '2018-07-17'
author: Bhavin Patel, Splunk
@@ -16,8 +16,14 @@ how_to_implement: To successfully implement this search, you must be ingesting n
known_false_positives: none
references: []
tags:
analytic_story:
- Prohibited Traffic Allowed or Protocol Mismatch
- Ransomware
- Command and Control
deployments:
- Daily Cache Updates
detections:
- Prohibited Network Traffic Allowed
product:
- Splunk Enterprise
- Splunk Enterprise Security
@@ -1,5 +1,5 @@
name: Create a list of approved AWS service accounts
id: fc0edc95-ff2b-48b1-5f6f-63ga3789fd43
id: 08ef80f5-6555-474b-bb2d-22e2aa4206a4
version: 2
date: '2018-12-03'
author: Bhavin Patel, Splunk
@@ -20,6 +20,7 @@ tags:
- 'Emotet Malware DHS Report TA18-201A '
- Monitor for Unauthorized Software
- SamSam Ransomware
asset_type: Endpoint
deployments:
- Daily Cache Updates
detections:
@@ -31,3 +32,16 @@ tags:
required_fields:
- _time
security_domain: endpoint
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -35,3 +35,16 @@ tags:
- eventType
- userIdentity.arn
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -47,3 +47,16 @@ tags:
- errorCode
- src_user
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -47,3 +47,16 @@ tags:
- errorCode
- src_user
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -1,5 +1,5 @@
name: Previously seen API call per user roles in CloudTrail
id: fc0edc95-fq2c-48b0-9f6f-63da3289fd03
id: 02add098-efa3-428d-b2e2-4ed0831c92f4
version: 1
date: '2018-04-16'
author: Bhavin Patel, Splunk
@@ -38,3 +38,16 @@ tags:
- userName
- eventName
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -36,3 +36,16 @@ tags:
- eventName
- sourceIPAddress
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -32,3 +32,16 @@ tags:
- errorCode
- requestParameters.instancesSet.items{}.imageId
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -32,3 +32,16 @@ tags:
- errorCode
- requestParameters.instanceType
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -33,3 +33,16 @@ tags:
- errorCode
- requestParameters.instanceType
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -40,3 +40,16 @@ tags:
- userIdentity.arn
- src
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -42,3 +42,16 @@ tags:
- userIdentity.arn
- src
security_domain: network
kill_chain_phases:
- Exploitation
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: field
type: Unknown
role:
- Unknown
@@ -19,6 +19,7 @@ tags:
analytic_story:
- Brand Monitoring
- Suspicious Emails
asset_type: Endpoint
deployments:
- Daily Cache Updates
detections:
@@ -29,6 +30,19 @@ tags:
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
kill_chain_phases:
- Exploitation
required_fields:
- _time
security_domain: network
confidence: 50
impact: 50
risk_score: 25
context:
- Unknown
message: tbd
observable:
- name: dest
type: Other
role:
- Other
@@ -16,8 +16,15 @@ how_to_implement: To successfully implement this search, you must ingest network
known_false_positives: none
references: []
tags:
analytic_story:
- SamSam Ransomware
- Ryuk Ransomware
- Hidden Cobra Malware
- Active Directory Lateral Movement
deployments:
- Daily Cache Updates
detections:
- Remote Desktop Network Traffic
product:
- Splunk Enterprise
- Splunk Enterprise Security
@@ -17,8 +17,15 @@ how_to_implement: To successfully implement this search you must ingest network
known_false_positives: none
references: []
tags:
analytic_story:
- SamSam Ransomware
- Ryuk Ransomware
- Hidden Cobra Malware
- Active Directory Lateral Movement
deployments:
- Daily Cache Updates
detections:
- Remote Desktop Network Traffic
product:
- Splunk Enterprise
- Splunk Enterprise Security
@@ -16,8 +16,15 @@ how_to_implement: To successfully implement this search you must be ingesting en
known_false_positives: none
references: []
tags:
analytic_story:
- SamSam Ransomware
- Ryuk Ransomware
- Hidden Cobra Malware
- Active Directory Lateral Movement
deployments:
- Daily Cache Updates
detections:
- Remote Desktop Network Traffic
product:
- Splunk Enterprise
- Splunk Enterprise Security
@@ -31,7 +31,6 @@ tags:
detections:
- AWS Cross Account Activity From Previously Unseen Account
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -32,7 +32,6 @@ tags:
detections:
- AWS Cross Account Activity From Previously Unseen Account
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -27,7 +27,6 @@ tags:
detections:
- Cloud API Calls From Previously Unseen User Roles
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -29,7 +29,6 @@ tags:
detections:
- Cloud API Calls From Previously Unseen User Roles
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -24,7 +24,6 @@ tags:
detections:
- Cloud Compute Instance Created By Previously Unseen User
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -27,7 +27,6 @@ tags:
detections:
- Cloud Compute Instance Created By Previously Unseen User
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -26,7 +26,6 @@ tags:
detections:
- Cloud Compute Instance Created With Previously Unseen Image
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -27,7 +27,6 @@ tags:
detections:
- Cloud Compute Instance Created With Previously Unseen Image
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -25,7 +25,6 @@ tags:
detections:
- Cloud Compute Instance Created With Previously Unseen Instance Type
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -27,7 +27,6 @@ tags:
detections:
- Cloud Compute Instance Created With Previously Unseen Instance Type
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -25,7 +25,6 @@ tags:
detections:
- Cloud Instance Modified By Previously Unseen User
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -29,7 +29,6 @@ tags:
detections:
- Cloud Instance Modified By Previously Unseen User
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -32,7 +32,6 @@ tags:
- Cloud Provisioning Activity From Previously Unseen Country
- Cloud Provisioning Activity From Previously Unseen Region
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -37,7 +37,6 @@ tags:
- Cloud Provisioning Activity From Previously Unseen Country
- Cloud Provisioning Activity From Previously Unseen Region
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -27,7 +27,6 @@ tags:
detections:
- Cloud Compute Instance Created In Previously Unused Region
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -30,7 +30,6 @@ tags:
detections:
- Cloud Compute Instance Created In Previously Unused Region
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -1,5 +1,5 @@
name: Previously seen S3 bucket access by remote IP
id: fc0edc15-fq2c-48b0-9f6f-63qa1281fd03
id: 54c40c6a-9a5b-4a79-9291-85977f713961
version: 1
date: '2018-06-28'
author: Bhavin Patel, Splunk
@@ -33,7 +33,6 @@ tags:
- Detect AWS Console Login by User from New City
- Detect AWS Console Login by New User
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -33,7 +33,6 @@ tags:
- Detect AWS Console Login by User from New City
- Detect AWS Console Login by New User
product:
- Splunk Security Analytics for AWS
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
@@ -15,8 +15,12 @@ how_to_implement: You must be ingesting your Windows Update Logs
known_false_positives: none
references: []
tags:
analytic_story:
- Monitor for Updates
deployments:
- Daily Cache Updates
detections:
- No Windows Updates in a time frame
product:
- Splunk Enterprise
- Splunk Enterprise Security
@@ -15,8 +15,12 @@ how_to_implement: You must be ingesting your Windows Update Logs
known_false_positives: none
references: []
tags:
analytic_story:
- Monitor for Updates
deployments:
- Daily Cache Updates
detections:
- No Windows Updates in a time frame
product:
- Splunk Enterprise
- Splunk Enterprise Security
-75
View File
@@ -1,75 +0,0 @@
import glob
import yaml
import sys
import re
import argparse
from os import path
def load_objects(file_path, REPO_PATH):
files = []
manifest_files = path.join(path.expanduser(REPO_PATH), file_path)
for file in sorted(glob.glob(manifest_files)):
files.append(load_file(file))
return files
def load_file(file_path):
with open(file_path, 'r', encoding="utf-8") as stream:
try:
file = list(yaml.safe_load_all(stream))[0]
except yaml.YAMLError as exc:
print(exc)
sys.exit("ERROR: reading {0}".format(file_path))
return file
def load_content(old_project):
stories = load_objects("stories/*.yml", old_project)
macros = load_objects("macros/*.yml", old_project)
lookups = load_objects("lookups/*.yml", old_project)
baselines = load_objects("baselines/*.yml", old_project)
responses = load_objects("responses/*.yml", old_project)
response_tasks = load_objects("response_tasks/*.yml", old_project)
deployments = load_objects("deployments/*.yml", old_project)
# process all detections
detections = []
detections = load_objects("detections/*/*.yml", old_project)
detections.extend(load_objects("detections/*/*/*.yml", old_project))
#print(len(detections))
return detections, stories, macros, lookups, baselines, responses, response_tasks, deployments
def add_required_field(detections, new_project):
#for detection in detections:
matches = re.findall(r'(?<key>[^\s]*)=', detections[0])
for match in matches:
print(match)
def main(new_project, old_project, change):
detections, stories, macros, lookups, baselines, responses, response_tasks, deployments = load_content(old_project)
if change == "add_required_field":
add_required_field(detections, new_project)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="applies security content changes to the whole project")
parser.add_argument("-np", "--new_project", required=True, help="the security content project to write the new configs in to")
parser.add_argument("-op", "--old_project", required=True, help="the security content project to read the files from")
parser.add_argument("-c", "--change", required=True, help="the name of your change")
# parse them
args = parser.parse_args()
new_project = args.new_project
old_project = args.old_project
change = args.change
main(new_project, old_project, change)
-218
View File
@@ -1,218 +0,0 @@
import glob
import yaml
import sys
import re
import argparse
import requests
import csv
from requests.auth import HTTPBasicAuth
from urllib3.exceptions import InsecureRequestWarning
from os import path
BASE_URL = f"https://ip:8089"
SEARCH_PARSER_ENDPOINT = f"/services/search/parser"
USER = f"admin"
PASSWORD = f"password"
parsed_fields = dict()
def load_objects(file_path, REPO_PATH):
files = []
manifest_files = path.join(path.expanduser(REPO_PATH), file_path)
for file in sorted(glob.glob(manifest_files)):
files.append(load_file(file))
return files
def load_file(file_path):
with open(file_path, 'r', encoding="utf-8") as stream:
try:
file = list(yaml.safe_load_all(stream))[0]
except yaml.YAMLError as exc:
print(exc)
sys.exit("ERROR: reading {0}".format(file_path))
return file
def load_content(old_project):
# process all detections
detections = []
detections = load_objects("detections/*/*.yml", old_project)
detections.extend(load_objects("detections/*/*/*.yml", old_project))
#print(len(detections))
return detections
def analysis_detection(detections):
for detection in detections:#
if detection['type'] != 'streaming':
#if detection['name'] == 'Attempted Credential Dump From Registry via Reg exe':
print('Analysis Detection: ' + detection['name'])
call_splunk_parser_api(detection)
# sort parsed fields by occurence
sorted_dict = {k: v for k, v in sorted(parsed_fields.items(), key=lambda item: item[1], reverse=True)}
with open('output_fields_ordered_by_usage.csv', mode='w') as csv_file:
writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
writer.writerow(['field_name', 'occurence'])
for field_name in sorted_dict:
writer.writerow([field_name, sorted_dict[field_name]])
# sort parsed fields by name
sorted_dict_2 = sorted(parsed_fields.items())
with open('output_fields_ordered_by_keys.csv', mode='w') as csv_file:
writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
writer.writerow(['field_name', 'occurence'])
for field_name in sorted_dict_2:
writer.writerow([field_name[0], field_name[1]])
def call_splunk_parser_api(detection):
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
spl = ''
if detection['search'].startswith('| tstats'):
spl = detection['search']
else:
spl = 'search ' + detection['search']
data = {
"output_mode": "json",
"q": spl,
"parse_only": "true"
}
#Have semgrep ignore the following line. It will complain about the verify=false, but the server is hosted on localhost
response = requests.post(BASE_URL + SEARCH_PARSER_ENDPOINT, data=data, auth=(USER, PASSWORD), verify=False, headers={"Content-Type": "application/x-www-form-urlencoded"}) # nosemgrep
if response.status_code != 200:
print(response.json())
print('ERROR: parser endpoint problems')
return
parse_commands(response.json())
def parse_commands(api_response):
tmp_parsed_fields = {}
last_stat_command = ''
rename_command_after_stats_arr = []
for command in api_response['commands']:
if command['command'] in ['tstats', 'stats', 'table']:
last_stat_command = command
if (command['command'] == 'rename') and last_stat_command:
rename_command_after_stats_arr.append(command)
if not last_stat_command:
print('ERROR: could not find stats table or tasts command')
return
# last command table
if last_stat_command['command'] == 'table':
matches = re.findall(r'([0-9a-zA-Z_]+)', last_stat_command['rawargs'])
for match in matches:
if match in tmp_parsed_fields:
tmp_parsed_fields[match] = tmp_parsed_fields[match] + 1
else:
tmp_parsed_fields[match] = 1
# last command stats
if last_stat_command['command'] == 'stats':
match = re.match(r'(.*)by', last_stat_command['rawargs'])
if match:
args_one = match.group(1)
matches = re.findall(r'(?:min|max|values)\(([0-9a-zA-Z_]+)\)', args_one)
if matches:
for match in matches:
if match in tmp_parsed_fields:
tmp_parsed_fields[match] = tmp_parsed_fields[match] + 1
else:
tmp_parsed_fields[match] = 1
match = re.match(r'.*by(.*)$', last_stat_command['rawargs'])
if match:
args_two = match.group(1)
matches = re.findall(r'([0-9a-zA-Z_]+)', args_two)
if matches:
for match in matches:
if match in tmp_parsed_fields:
tmp_parsed_fields[match] = tmp_parsed_fields[match] + 1
else:
tmp_parsed_fields[match] = 1
# tstats command
if last_stat_command['command'] == 'tstats':
match = re.match(r'(.*)(?:from|FROM)', last_stat_command['rawargs'])
if match:
args_one = match.group(1)
matches = re.findall(r'(?:min|max|values)\(([0-9a-zA-Z_]+)\.([0-9a-zA-Z_]+)\)', args_one)
if matches:
for match in matches:
field = match[0] + '.' + match[1]
if field in tmp_parsed_fields:
tmp_parsed_fields[field] = tmp_parsed_fields[field] + 1
else:
tmp_parsed_fields[field] = 1
match = re.match(r'.*by(.*)$', last_stat_command['rawargs'])
if match:
args_two = match.group(1)
matches = re.findall(r'([0-9_a-zA-Z]+)\.([0-9a-zA-Z_]+)', args_two)
if matches:
for match in matches:
field = match[0] + '.' + match[1]
if field in tmp_parsed_fields:
tmp_parsed_fields[field] = tmp_parsed_fields[field] + 1
else:
tmp_parsed_fields[field] = 1
match = re.match(r'.*where(.*)by.*$', last_stat_command['rawargs'])
if match:
args_three = match.group(1)
matches = re.findall(r'([0-9_a-zA-Z]+)\.([0-9a-zA-Z_]+)=', args_three)
if matches:
for match in matches:
field = match[0] + '.' + match[1]
if field in tmp_parsed_fields:
tmp_parsed_fields[field] = tmp_parsed_fields[field] + 1
else:
tmp_parsed_fields[field] = 1
# rename occured
for rename_command_after_stats in rename_command_after_stats_arr:
if rename_command_after_stats:
renamed_field = {}
matches = re.findall(r'(?:(([0-9a-zA-Z_]+)\s+as\s+([0-9a-zA-Z_]+)))', rename_command_after_stats['rawargs'])
for match in matches:
renamed_field[match[1]] = match[2]
for key in renamed_field:
if key in tmp_parsed_fields:
tmp_parsed_fields[renamed_field[key]] = tmp_parsed_fields.pop(key)
# write to global parsed fields var
for key in tmp_parsed_fields:
if key in parsed_fields:
parsed_fields[key] = parsed_fields[key] + tmp_parsed_fields[key]
else:
parsed_fields[key] = tmp_parsed_fields[key]
def main(project):
detections = load_content(project)
analysis_detection(detections)
if __name__ == "__main__":
main("../")
@@ -0,0 +1,9 @@
import abc
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
class Adapter(abc.ABC):
@abc.abstractmethod
def writeObjects(self, objects: list, output_path: str, type: SecurityContentType = None) -> None:
pass
@@ -0,0 +1,25 @@
import abc
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
from bin.contentctl_project.contentctl_core.domain.entities.baseline import Baseline
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentProduct
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
class BaselineBuilder(abc.ABC):
@abc.abstractmethod
def addDeployment(self, deployments: list) -> None:
pass
@abc.abstractmethod
def setObject(self, path: str) -> None:
pass
@abc.abstractmethod
def reset(self) -> None:
pass
@abc.abstractmethod
def getObject(self) -> SecurityContentObject:
pass
@@ -0,0 +1,22 @@
import abc
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
# https://refactoring.guru/design-patterns/builder
class BasicBuilder(abc.ABC):
@abc.abstractmethod
def setObject(self, path: str, type: SecurityContentType) -> None:
pass
@abc.abstractmethod
def reset(self) -> None:
pass
@abc.abstractmethod
def getObject(self) -> SecurityContentObject:
pass
@@ -0,0 +1,69 @@
import abc
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentProduct
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
# https://refactoring.guru/design-patterns/builder
class DetectionBuilder(abc.ABC):
@abc.abstractmethod
def addDeployment(self, deployments: list) -> None:
pass
@abc.abstractmethod
def addRBA(self) -> None:
pass
@abc.abstractmethod
def addNesFields(self) -> None:
pass
@abc.abstractmethod
def addMappings(self) -> None:
pass
@abc.abstractmethod
def addAnnotations(self) -> None:
pass
@abc.abstractmethod
def addPlaybook(self, playbooks: list) -> None:
pass
@abc.abstractmethod
def addBaseline(self, baselines: list) -> None:
pass
@abc.abstractmethod
def addUnitTest(self, tests: list) -> None:
pass
@abc.abstractmethod
def addMitreAttackEnrichment(self) -> None:
pass
@abc.abstractmethod
def addMacros(self, macros: list) -> None:
pass
@abc.abstractmethod
def addLookups(self, lookups: list) -> None:
pass
@abc.abstractmethod
def addCve(self) -> None:
pass
@abc.abstractmethod
def setObject(self, path: str) -> None:
pass
@abc.abstractmethod
def reset(self) -> None:
pass
@abc.abstractmethod
def getObject(self) -> SecurityContentObject:
pass
@@ -0,0 +1,51 @@
import abc
from bin.contentctl_project.contentctl_core.application.builder.basic_builder import BasicBuilder
from bin.contentctl_project.contentctl_core.application.builder.detection_builder import DetectionBuilder
from bin.contentctl_project.contentctl_core.application.builder.baseline_builder import BaselineBuilder
from bin.contentctl_project.contentctl_core.application.builder.investigation_builder import InvestigationBuilder
from bin.contentctl_project.contentctl_core.application.builder.story_builder import StoryBuilder
from bin.contentctl_project.contentctl_core.application.builder.playbook_builder import PlaybookBuilder
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentProduct
class Director(abc.ABC):
@abc.abstractmethod
def constructDetection(self, builder: DetectionBuilder, path: str, deployments: list, playbooks: list, baselines: list, tests: list, attack_enrichment: dict, macros: list) -> None:
pass
@abc.abstractmethod
def constructBaseline(self, builder: BaselineBuilder, path: str, deployments: list) -> None:
pass
@abc.abstractmethod
def constructDeployment(self, builder: BasicBuilder, path: str) -> None:
pass
@abc.abstractmethod
def constructLookup(self, builder: BasicBuilder, path: str) -> None:
pass
@abc.abstractmethod
def constructMacro(self, builder: BasicBuilder, path: str) -> None:
pass
@abc.abstractmethod
def constructPlaybook(self, builder: PlaybookBuilder, path: str, detections: list) -> None:
pass
@abc.abstractmethod
def constructTest(self, builder: BasicBuilder, path: str) -> None:
pass
@abc.abstractmethod
def constructStory(self, builder: StoryBuilder, path: str, detections: list, baselines: list, investigations: list) -> None:
pass
@abc.abstractmethod
def constructInvestigation(self, builder: InvestigationBuilder, path: str) -> None:
pass
@abc.abstractmethod
def constructObjects(self, builder: BasicBuilder, path: str) -> None:
pass
@@ -0,0 +1,25 @@
import abc
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
class InvestigationBuilder(abc.ABC):
@abc.abstractmethod
def setObject(self, path: str) -> None:
pass
@abc.abstractmethod
def reset(self) -> None:
pass
@abc.abstractmethod
def getObject(self) -> SecurityContentObject:
pass
@abc.abstractmethod
def addInputs(self) -> None:
pass
@abc.abstractmethod
def addLowercaseName(self) -> None:
pass
@@ -0,0 +1,23 @@
import abc
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
# https://refactoring.guru/design-patterns/builder
class PlaybookBuilder(abc.ABC):
@abc.abstractmethod
def setObject(self, path: str) -> None:
pass
@abc.abstractmethod
def addDetections(self, detections : list) -> None:
pass
@abc.abstractmethod
def reset(self) -> None:
pass
@abc.abstractmethod
def getObject(self) -> SecurityContentObject:
pass
@@ -0,0 +1,39 @@
import abc
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
class StoryBuilder(abc.ABC):
@abc.abstractmethod
def addDetections(self, detections: list) -> None:
pass
@abc.abstractmethod
def addInvestigations(self, investigations: list) -> None:
pass
@abc.abstractmethod
def addAuthorCompanyName(self) -> None:
pass
@abc.abstractmethod
def addBaselines(self, baselines: list) -> None:
pass
@abc.abstractmethod
def addInvestigations(self, investigations: list) -> None:
pass
@abc.abstractmethod
def setObject(self, path: str) -> None:
pass
@abc.abstractmethod
def reset(self) -> None:
pass
@abc.abstractmethod
def getObject(self) -> SecurityContentObject:
pass
@@ -0,0 +1,58 @@
import os
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
from bin.contentctl_project.contentctl_core.application.builder.basic_builder import BasicBuilder
from bin.contentctl_project.contentctl_core.application.builder.detection_builder import DetectionBuilder
from bin.contentctl_project.contentctl_core.application.builder.story_builder import StoryBuilder
from bin.contentctl_project.contentctl_core.application.builder.director import Director
from bin.contentctl_project.contentctl_core.application.factory.utils.utils import Utils
@dataclass(frozen=True)
class BAFactoryInputDto:
input_path: str
basic_builder: BasicBuilder
detection_builder: DetectionBuilder
director: Director
@dataclass(frozen=True)
class BAFactoryOutputDto:
detections: list
tests: list
class BAFactory():
input_dto: BAFactoryInputDto
output_dto: BAFactoryOutputDto
def __init__(self, output_dto: BAFactoryOutputDto) -> None:
self.output_dto = output_dto
def execute(self, input_dto: BAFactoryInputDto) -> None:
self.input_dto = input_dto
self.createSecurityContent(SecurityContentType.unit_tests)
self.createSecurityContent(SecurityContentType.detections)
def createSecurityContent(self, type: SecurityContentType) -> list:
objects = []
if type == SecurityContentType.unit_tests:
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, 'tests'))
else:
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, str(type.name)))
for file in files:
if 'ssa__' in file:
if type == SecurityContentType.detections:
self.input_dto.director.constructDetection(self.input_dto.detection_builder, file, [], [], [], self.output_dto.tests, {}, [], [])
detection = self.input_dto.detection_builder.getObject()
if not detection.deprecated and not detection.experimental:
self.output_dto.detections.append(detection)
elif type == SecurityContentType.unit_tests:
self.input_dto.director.constructTest(self.input_dto.basic_builder, file)
test = self.input_dto.basic_builder.getObject()
self.output_dto.tests.append(test)
@@ -0,0 +1,124 @@
import os
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentProduct
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
from bin.contentctl_project.contentctl_core.application.builder.basic_builder import BasicBuilder
from bin.contentctl_project.contentctl_core.application.builder.detection_builder import DetectionBuilder
from bin.contentctl_project.contentctl_core.application.builder.story_builder import StoryBuilder
from bin.contentctl_project.contentctl_core.application.builder.baseline_builder import BaselineBuilder
from bin.contentctl_project.contentctl_core.application.builder.investigation_builder import InvestigationBuilder
from bin.contentctl_project.contentctl_core.application.builder.playbook_builder import PlaybookBuilder
from bin.contentctl_project.contentctl_core.application.builder.director import Director
from bin.contentctl_project.contentctl_core.application.factory.utils.utils import Utils
@dataclass(frozen=True)
class FactoryInputDto:
input_path: str
basic_builder: BasicBuilder
detection_builder: DetectionBuilder
story_builder: StoryBuilder
baseline_builder: BaselineBuilder
investigation_builder: InvestigationBuilder
playbook_builder: PlaybookBuilder
director: Director
attack_enrichment: dict
@dataclass()
class FactoryOutputDto:
detections: list
stories: list
baselines: list
investigations: list
playbooks: list
deployments: list
macros: list
lookups: list
tests: list
class Factory():
input_dto: FactoryInputDto
output_dto: FactoryOutputDto
def __init__(self, output_dto: FactoryOutputDto) -> None:
self.output_dto = output_dto
def execute(self, input_dto: FactoryInputDto) -> None:
self.input_dto = input_dto
# order matters to load and enrich security content types
self.createSecurityContent(SecurityContentType.unit_tests)
self.createSecurityContent(SecurityContentType.lookups)
self.createSecurityContent(SecurityContentType.macros)
self.createSecurityContent(SecurityContentType.deployments)
self.createSecurityContent(SecurityContentType.baselines)
self.createSecurityContent(SecurityContentType.investigations)
self.createSecurityContent(SecurityContentType.detections) # execution only for playbook enrichment
self.createSecurityContent(SecurityContentType.playbooks)
self.output_dto.detections = []
self.createSecurityContent(SecurityContentType.detections)
self.createSecurityContent(SecurityContentType.stories)
def createSecurityContent(self, type: SecurityContentType) -> list:
objects = []
if type == SecurityContentType.deployments:
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, str(type.name), 'ESCU'))
elif type == SecurityContentType.unit_tests:
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, 'tests'))
else:
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, str(type.name)))
for file in files:
if not 'ssa__' in file:
if type == SecurityContentType.lookups:
self.input_dto.director.constructLookup(self.input_dto.basic_builder, file)
self.output_dto.lookups.append(self.input_dto.basic_builder.getObject())
elif type == SecurityContentType.macros:
self.input_dto.director.constructMacro(self.input_dto.basic_builder, file)
self.output_dto.macros.append(self.input_dto.basic_builder.getObject())
elif type == SecurityContentType.deployments:
self.input_dto.director.constructDeployment(self.input_dto.basic_builder, file)
self.output_dto.deployments.append(self.input_dto.basic_builder.getObject())
elif type == SecurityContentType.playbooks:
self.input_dto.director.constructPlaybook(self.input_dto.playbook_builder, file, self.output_dto.detections)
self.output_dto.playbooks.append(self.input_dto.playbook_builder.getObject())
elif type == SecurityContentType.baselines:
self.input_dto.director.constructBaseline(self.input_dto.baseline_builder, file, self.output_dto.deployments)
baseline = self.input_dto.baseline_builder.getObject()
self.output_dto.baselines.append(baseline)
elif type == SecurityContentType.investigations:
self.input_dto.director.constructInvestigation(self.input_dto.investigation_builder, file)
investigation = self.input_dto.investigation_builder.getObject()
self.output_dto.investigations.append(investigation)
elif type == SecurityContentType.stories:
self.input_dto.director.constructStory(self.input_dto.story_builder, file,
self.output_dto.detections, self.output_dto.baselines, self.output_dto.investigations)
story = self.input_dto.story_builder.getObject()
self.output_dto.stories.append(story)
elif type == SecurityContentType.detections:
self.input_dto.director.constructDetection(self.input_dto.detection_builder, file,
self.output_dto.deployments, self.output_dto.playbooks, self.output_dto.baselines,
self.output_dto.tests, self.input_dto.attack_enrichment, self.output_dto.macros,
self.output_dto.lookups)
detection = self.input_dto.detection_builder.getObject()
self.output_dto.detections.append(detection)
elif type == SecurityContentType.unit_tests:
self.input_dto.director.constructTest(self.input_dto.basic_builder, file)
test = self.input_dto.basic_builder.getObject()
self.output_dto.tests.append(test)
@@ -0,0 +1,79 @@
import os
import uuid
from dataclasses import dataclass
from PyInquirer import prompt
from datetime import datetime
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
from bin.contentctl_project.contentctl_core.application.factory.utils.new_content_questions import NewContentQuestions
@dataclass(frozen=True)
class NewContentFactoryInputDto:
type: SecurityContentType
@dataclass(frozen=True)
class NewContentFactoryOutputDto:
obj: dict
class NewContentFactory():
def __init__(self, output_dto: NewContentFactoryOutputDto) -> None:
self.output_dto = output_dto
def execute(self, input_dto: NewContentFactoryInputDto) -> None:
if input_dto.type == SecurityContentType.detections:
questions = NewContentQuestions.get_questions_detection()
answers = prompt(questions)
self.output_dto.obj['name'] = answers['detection_name']
self.output_dto.obj['id'] = str(uuid.uuid4())
self.output_dto.obj['version'] = 1
self.output_dto.obj['date'] = datetime.today().strftime('%Y-%m-%d')
self.output_dto.obj['author'] = answers['detection_author']
self.output_dto.obj['type'] = answers['detection_type']
self.output_dto.obj['datamodel'] = answers['datamodels']
self.output_dto.obj['description'] = 'UPDATE_DESCRIPTION'
file_name = self.output_dto.obj['name'].replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower()
self.output_dto.obj['search'] = answers['detection_search'] + ' | `' + file_name + '_filter`'
self.output_dto.obj['how_to_implement'] = 'UPDATE_HOW_TO_IMPLEMENT'
self.output_dto.obj['known_false_positives'] = 'UPDATE_KNOWN_FALSE_POSITIVES'
self.output_dto.obj['references'] = ['REFERENCE']
self.output_dto.obj['tags'] = dict()
self.output_dto.obj['tags']['analytic_story'] = ['UPDATE_STORY_NAME']
self.output_dto.obj['tags']['asset_type'] = 'UPDATE asset_type'
self.output_dto.obj['tags']['cis20'] = ['CIS 3', 'CIS 5', 'CIS 16']
self.output_dto.obj['tags']['confidence'] = 'UPDATE value between 1-100'
self.output_dto.obj['tags']['context'] = ['Update context']
self.output_dto.obj['tags']['dataset'] = ['UPDATE_DATASET_URL']
self.output_dto.obj['tags']['impact'] = 'UPDATE value between 1-100'
self.output_dto.obj['tags']['kill_chain_phases'] = answers['kill_chain_phases']
self.output_dto.obj['tags']['message'] = 'UPDATE message'
self.output_dto.obj['tags']['mitre_attack_id'] = [x.strip() for x in answers['mitre_attack_ids'].split(',')]
self.output_dto.obj['tags']['nist'] = ['DE.CM']
self.output_dto.obj['tags']['observable'] = [{'name': 'UPDATE', 'type': 'UPDATE', 'role': ['UPDATE']}]
self.output_dto.obj['tags']['product'] = ['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud']
self.output_dto.obj['tags']['required_fields'] = ['UPDATE']
self.output_dto.obj['tags']['risk_score'] = 'UPDATE (impact * confidence)/100'
self.output_dto.obj['tags']['security_domain'] = answers['security_domain']
self.output_dto.obj['source'] = answers['detection_kind']
elif input_dto.type == SecurityContentType.stories:
questions = NewContentQuestions.get_questions_story()
answers = prompt(questions)
self.output_dto.obj['name'] = answers['story_name']
self.output_dto.obj['id'] = str(uuid.uuid4())
self.output_dto.obj['version'] = 1
self.output_dto.obj['date'] = datetime.today().strftime('%Y-%m-%d')
self.output_dto.obj['author'] = answers['story_author']
self.output_dto.obj['description'] = 'UPDATE_DESCRIPTION'
self.output_dto.obj['narrative'] = 'UPDATE_NARRATIVE'
self.output_dto.obj['references'] = []
self.output_dto.obj['tags']['analytic_story'] = self.output_dto.obj['name']
self.output_dto.obj['tags']['category'] = answers['category']
self.output_dto.obj['tags']['product'] = ['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud']
self.output_dto.obj['tags']['usecase'] = answers['usecase']
@@ -0,0 +1,29 @@
import os
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.application.builder.basic_builder import BasicBuilder
from bin.contentctl_project.contentctl_core.application.builder.director import Director
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType
from bin.contentctl_project.contentctl_core.application.factory.utils.utils import Utils
@dataclass(frozen=True)
class ObjectFactoryInputDto:
input_path: str
builder: BasicBuilder
director: Director
class ObjectFactory():
objects: list
def __init__(self, objects: list) -> None:
self.objects = objects
def execute(self, input_dto: ObjectFactoryInputDto) -> None:
self.input_path = input_dto.input_path
files = Utils.get_all_yml_files_from_directory(input_dto.input_path)
for file in files:
input_dto.director.constructObjects(input_dto.builder, file)
self.objects.append(input_dto.builder.getObject())
@@ -0,0 +1,266 @@
class NewContentQuestions():
@classmethod
def get_questions_detection(self) -> list:
questions = [
{
'type': 'list',
'message': 'what kind of detection is this',
'name': 'detection_kind',
'choices': [
{
'name': 'endpoint'
},
{
'name': 'cloud'
},
{
'name': 'application'
},
{
'name': 'network'
},
{
'name': 'web'
},
{
'name': 'experimental'
},
],
'default': 'endpoint'
},
{
'type': 'input',
'message': 'enter detection name',
'name': 'detection_name',
'default': 'Powershell Encoded Command',
},
{
'type': 'input',
'message': 'enter author name',
'name': 'detection_author',
},
{
'type': 'list',
'message': 'select a detection type',
'name': 'detection_type',
'choices': [
{
'name': 'TTP'
},
{
'name': 'Anomaly'
},
{
'name': 'Hunting'
},
{
'name': 'Baseline'
},
{
'name': 'Investigation'
},
{
'name': 'Correlation'
}
],
'default': 'TTP'
},
{
'type': 'checkbox',
'message': 'select the datamodels used in the detection',
'name': 'datamodels',
'choices': [
{
'name': 'Endpoint',
'checked': True
},
{
'name': 'Authentication'
},
{
'name': 'Change'
},
{
'name': 'Email'
},
{
'name': 'Network_Resolution'
},
{
'name': 'Network_Traffic'
},
{
'name': 'Network_Sessions'
},
{
'name': 'Updates'
},
{
'name': 'Vulnerabilities'
},
{
'name': 'Web'
},
{
'name': 'Risk'
},
],
},
{
'type': 'input',
'message': 'enter search (spl)',
'name': 'detection_search',
'default': '| UPDATE_SPL'
},
{
'type': 'input',
'message': 'enter MITRE ATT&CK Technique IDs related to the detection, comma delimited for multiple',
'name': 'mitre_attack_ids',
'default': 'T1003.002'
},
{
'type': 'checkbox',
'message': 'select kill chain phases related to the detection',
'name': 'kill_chain_phases',
'choices': [
{
'name': 'Reconnaissance'
},
{
'name': 'Intrusion'
},
{
'name': 'Exploitation',
'checked': True
},
{
'name': 'Privilege Escalation'
},
{
'name': 'Lateral Movement'
},
{
'name': 'Obfuscation'
},
{
'name': 'Denial of Service'
},
{
'name': 'Exfiltration'
},
],
},
{
'type': 'list',
'message': 'security_domain for detection',
'name': 'security_domain',
'choices': [
{
'name': 'access'
},
{
'name': 'endpoint'
},
{
'name': 'network'
},
{
'name': 'threat'
},
{
'name': 'identity'
},
{
'name': 'audit'
},
],
'default': 'endpoint'
},
]
return questions
@classmethod
def get_questions_story(self) -> list:
questions = [
{
'type': 'input',
'message': 'enter story name',
'name': 'story_name',
'default': 'Suspicious Powershell Behavior',
},
{
'type': 'input',
'message': 'enter author name',
'name': 'story_author',
},
{
'type': 'checkbox',
'message': 'select a category',
'name': 'category',
'choices': [
{
'name': 'Adversary Tactics',
'checked': True
},
{
'name': 'Account Compromise'
},
{
'name': 'Unauthorized Software'
},
{
'name': 'Best Practices'
},
{
'name': 'Cloud Security'
},
{
'name': 'Command and Control'
},
{
'name': 'Lateral Movement'
},
{
'name': 'Ransomware'
},
{
'name': 'Privilege Escalation'
},
],
},
{
'type': 'list',
'message': 'select a use case',
'name': 'usecase',
'choices': [
{
'name': 'Advanced Threat Detection',
'checked': True
},
{
'name': 'Security Monitoring'
},
{
'name': 'Compliance'
},
{
'name': 'Insider Threat'
},
{
'name': 'Application Security'
},
{
'name': 'Other'
},
],
},
]
return questions
@@ -0,0 +1,13 @@
import os
class Utils:
@staticmethod
def get_all_yml_files_from_directory(path: str) -> list:
listOfFiles = list()
for (dirpath, dirnames, filenames) in os.walk(path):
for file in filenames:
if file.endswith(".yml"):
listOfFiles.append(os.path.join(dirpath, file))
return sorted(listOfFiles)
@@ -0,0 +1,189 @@
import re
import uuid
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.application.factory.object_factory import ObjectFactory, ObjectFactoryInputDto
from bin.contentctl_project.contentctl_core.application.adapter.adapter import Adapter
@dataclass(frozen=True)
class ContentChangerInputDto:
adapter : Adapter
factory_input_dto : ObjectFactoryInputDto
converter_func_name : str
class ContentChanger:
def execute(self, input_dto: ContentChangerInputDto) -> None:
objects = list()
factory = ObjectFactory(objects)
factory.execute(input_dto.factory_input_dto)
converter_func = getattr(self, input_dto.converter_func_name)
converter_func(objects)
input_dto.adapter.writeObjectsInPlace(objects)
# Define Converter Functions here
def example_converter_func(self, objects : list) -> None:
for obj in objects:
obj['author'] = obj['author'].upper()
def add_default_risk_values(self, objects : list) -> None:
for obj in objects:
if not 'confidence' in obj['tags']:
obj['tags']['confidence'] = 50
if not 'impact' in obj['tags']:
obj['tags']['impact'] = 50
if not 'risk_score' in obj['tags']:
obj['tags']['risk_score'] = 25
def add_unknown_context(self, objects : list) -> None:
for obj in objects:
if not 'context' in obj['tags']:
obj['tags']['context'] = ['Unknown']
def add_default_message(self, objects : list) -> None:
for obj in objects:
if not 'message' in obj['tags']:
obj['tags']['message'] = 'tbd'
def add_default_observable(self, objects : list) -> None:
for obj in objects:
if not 'observable' in obj['tags'] or ('observable' in obj['tags'] and len(obj['tags']['observable']) == 0):
observables = []
regexp_user = re.compile(r'user')
if regexp_user.search(obj['search']):
observables.append({'name': 'user', 'type': 'User', 'role': ['Victim']})
regexp_user = re.compile(r'dest')
if regexp_user.search(obj['search']):
observables.append({'name': 'dest', 'type': 'Hostname', 'role': ['Victim']})
if len(observables) == 0:
observables.append({'name': 'dest', 'type': 'Other', 'role': ['Other']})
obj['tags']['observable'] = observables
def add_default_cis(self, objects : list) -> None:
for obj in objects:
if not 'cis20' in obj['tags']:
obj['tags']['cis20'] = ['CIS 3', 'CIS 5', 'CIS 16']
def add_default_nist(self, objects : list) -> None:
for obj in objects:
if not 'nist' in obj['tags']:
obj['tags']['nist'] = ['DE.CM']
def fix_broken_uuids(self, objects : list) -> None:
for obj in objects:
try:
uuid.UUID(str(obj['id']))
except:
obj['id'] = str(uuid.uuid4())
def fix_wrong_kill_chain_phases(self, objects : list) -> None:
valid_kill_chain_phases = [
'Reconnaissance', 'Weaponization', 'Delivery',
'Exploitation', 'Installation', 'Command and Control',
'Actions on Objectives']
for obj in objects:
if 'kill_chain_phases' in obj['tags']:
for value in obj['tags']['kill_chain_phases']:
if value not in valid_kill_chain_phases:
obj['tags']['kill_chain_phases'] = ['Exploitation']
break
def add_default_kill_chain_phases(self, objects : list) -> None:
for obj in objects:
if 'kill_chain_phases' not in obj['tags']:
obj['tags']['kill_chain_phases'] = ['Exploitation']
if obj['tags']['kill_chain_phases'] == ['Privilege Escalation']:
obj['tags']['kill_chain_phases'] = ['Exploitation']
def fix_wrong_calculated_risk_score(self, objects : list) -> None:
for obj in objects:
calculated_risk_score = (int(obj['tags']['impact']))*(int(obj['tags']['confidence']))/100
if calculated_risk_score != int(obj['tags']['risk_score']):
obj['tags']['risk_score'] = calculated_risk_score
def add_asset_type_to_endpoint_detections(self, objects : list) -> None:
for obj in objects:
if 'asset_type' not in obj['tags']:
if '/endpoint/' in obj['file_path']:
obj['tags']['asset_type'] = 'Endpoint'
def fix_observables(self, objects : list) -> None:
for obj in objects:
if 'observable' in obj['tags']:
for observable in obj['tags']['observable']:
if observable['type'] == 'Parent Process':
observable['type'] = 'Process'
if observable['type'] == 'user':
observable['type'] = 'User'
if observable['type'] == 'process name':
observable['type'] = 'Process'
def fix_context(self, objects : list) -> None:
for obj in objects:
if 'context' in obj['tags']:
new_context = []
for context in obj['tags']['context']:
if context == 'Stage:Exploitation':
context = 'Stage:Execution'
new_context.append(context)
obj['tags']['context'] = list(dict.fromkeys(new_context))
def add_default_values_deprecated(self, objects : list) -> None:
for obj in objects:
if 'context' not in obj['tags']:
obj['tags']['context'] = ['Unknown']
if 'message' not in obj['tags']:
obj['tags']['message'] = 'tbd'
if 'observable' not in obj['tags']:
obj['tags']['observable'] = [{'name': 'field', 'type': 'Unknown', 'role': ['Unknown']}]
def fix_story(self, objects : list) -> None:
for obj in objects:
if 'type' not in obj:
print(obj['name'])
if isinstance(obj['tags']['analytic_story'], list):
obj['tags']['analytic_story'] = obj['tags']['analytic_story'][0]
def remove_SAAWS(self, objects : list) -> None:
for obj in objects:
if 'Splunk Security Analytics for AWS' in obj['tags']['product']:
obj['tags']['product'].remove('Splunk Security Analytics for AWS')
def remove_testing_passed(self, objects : list) -> None:
for obj in objects:
if 'automated_detection_testing' in obj['tags']:
obj['tags'].pop('automated_detection_testing')
def change_test_file_format(self, objects : list) -> None:
for obj in objects:
obj['name'] = obj['name'] + ' Unit Test'
def fix_kill_chain(self, objects : list) -> None:
for obj in objects:
if 'kill_chain_phases' in obj['tags']:
if obj['tags']['kill_chain_phases'] == 'Exploitation':
obj['tags']['kill_chain_phases'] = ['Exploitation']
def add_default_confidence_impact_risk_score(self, objects : list) -> None:
for obj in objects:
if 'confidence' not in obj['tags']:
obj['tags']['confidence'] = 50
if 'impact' not in obj['tags']:
obj['tags']['impact'] = 50
if 'risk_score' not in obj['tags']:
calculated_risk_score = (int(obj['tags']['impact']))*(int(obj['tags']['confidence']))/100
obj['tags']['risk_score'] = calculated_risk_score
def fix_cc(self, objects : list) -> None:
for obj in objects:
if 'Command & Control' in obj['tags']['analytic_story']:
obj['tags']['analytic_story'].remove('Command & Control')
obj['tags']['analytic_story'].append('Command and Control')
@@ -0,0 +1,23 @@
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.application.factory.factory import FactoryInputDto, Factory, FactoryOutputDto
from bin.contentctl_project.contentctl_core.application.adapter.adapter import Adapter
@dataclass(frozen=True)
class DocGenInputDto:
output_path: str
factory_input_dto: FactoryInputDto
adapter : Adapter
class DocGen:
def execute(self, input_dto: DocGenInputDto) -> None:
factory_output_dto = FactoryOutputDto([],[],[],[],[],[],[],[],[])
factory = Factory(factory_output_dto)
factory.execute(input_dto.factory_input_dto)
input_dto.adapter.writeObjects([factory_output_dto.stories, factory_output_dto.detections, factory_output_dto.playbooks], input_dto.output_path)
@@ -0,0 +1,58 @@
import os
import shutil
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentProduct, SecurityContentType
from bin.contentctl_project.contentctl_core.application.adapter.adapter import Adapter
from bin.contentctl_project.contentctl_core.application.factory.factory import FactoryInputDto, Factory, FactoryOutputDto
from bin.contentctl_project.contentctl_core.application.factory.ba_factory import BAFactoryInputDto, BAFactory, BAFactoryOutputDto
@dataclass(frozen=True)
class GenerateInputDto:
output_path: str
factory_input_dto: FactoryInputDto
ba_factory_input_dto: BAFactoryInputDto
adapter : Adapter
product: SecurityContentProduct
class Generate:
def execute(self, input_dto: GenerateInputDto) -> None:
if input_dto.product == SecurityContentProduct.ESCU:
factory_output_dto = FactoryOutputDto([],[],[],[],[],[],[],[],[])
factory = Factory(factory_output_dto)
factory.execute(input_dto.factory_input_dto)
input_dto.adapter.writeHeaders(input_dto.output_path)
input_dto.adapter.writeObjects(factory_output_dto.detections, input_dto.output_path, SecurityContentType.detections)
input_dto.adapter.writeObjects(factory_output_dto.stories, input_dto.output_path, SecurityContentType.stories)
input_dto.adapter.writeObjects(factory_output_dto.baselines, input_dto.output_path, SecurityContentType.baselines)
input_dto.adapter.writeObjects(factory_output_dto.investigations, input_dto.output_path, SecurityContentType.investigations)
input_dto.adapter.writeObjects(factory_output_dto.lookups, input_dto.output_path, SecurityContentType.lookups)
input_dto.adapter.writeObjects(factory_output_dto.macros, input_dto.output_path, SecurityContentType.macros)
elif input_dto.product == SecurityContentProduct.SSA:
shutil.rmtree(input_dto.output_path + '/srs/', ignore_errors=True)
shutil.rmtree(input_dto.output_path + '/complex/', ignore_errors=True)
os.makedirs(input_dto.output_path + '/complex/')
os.makedirs(input_dto.output_path + '/srs/')
factory_output_dto = BAFactoryOutputDto([],[])
factory = BAFactory(factory_output_dto)
factory.execute(input_dto.ba_factory_input_dto)
input_dto.adapter.writeObjects(factory_output_dto.detections, input_dto.output_path)
elif input_dto.product == SecurityContentProduct.API:
factory_output_dto = FactoryOutputDto([],[],[],[],[],[],[],[],[])
factory = Factory(factory_output_dto)
factory.execute(input_dto.factory_input_dto)
input_dto.adapter.writeObjects(factory_output_dto.detections, input_dto.output_path, SecurityContentType.detections)
input_dto.adapter.writeObjects(factory_output_dto.stories, input_dto.output_path, SecurityContentType.stories)
input_dto.adapter.writeObjects(factory_output_dto.baselines, input_dto.output_path, SecurityContentType.baselines)
input_dto.adapter.writeObjects(factory_output_dto.investigations, input_dto.output_path, SecurityContentType.investigations)
input_dto.adapter.writeObjects(factory_output_dto.lookups, input_dto.output_path, SecurityContentType.lookups)
input_dto.adapter.writeObjects(factory_output_dto.macros, input_dto.output_path, SecurityContentType.macros)
input_dto.adapter.writeObjects(factory_output_dto.deployments, input_dto.output_path, SecurityContentType.deployments)
@@ -0,0 +1,22 @@
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.application.factory.new_content_factory import NewContentFactory, NewContentFactoryInputDto, NewContentFactoryOutputDto
from bin.contentctl_project.contentctl_core.application.adapter.adapter import Adapter
@dataclass(frozen=True)
class NewContentInputDto:
factory_input_dto: NewContentFactoryInputDto
adapter : Adapter
class NewContent:
def execute(self, input_dto: NewContentInputDto) -> None:
factory_output_dto = NewContentFactoryOutputDto(dict())
factory = NewContentFactory(factory_output_dto)
factory.execute(input_dto.factory_input_dto)
input_dto.adapter.writeObjectNewContent(factory_output_dto.obj, input_dto.factory_input_dto.type)
@@ -0,0 +1,24 @@
import os
from dataclasses import dataclass
from bin.contentctl_project.contentctl_core.application.factory.factory import FactoryInputDto, Factory, FactoryOutputDto
from bin.contentctl_project.contentctl_core.application.adapter.adapter import Adapter
@dataclass(frozen=True)
class ReportingInputDto:
factory_input_dto: FactoryInputDto
adapter_svg : Adapter
adapter_attack : Adapter
class Reporting:
def execute(self, input_dto: ReportingInputDto) -> None:
factory_output_dto = FactoryOutputDto([],[],[],[],[],[],[],[],[])
factory = Factory(factory_output_dto)
factory.execute(input_dto.factory_input_dto)
input_dto.adapter_svg.writeObjects(factory_output_dto.detections, os.path.join(os.path.dirname(__file__), '../../../../reporting'))
input_dto.adapter_attack.writeObjects(factory_output_dto.detections, os.path.join(os.path.dirname(__file__), '../../../../../docs/mitre-map'))
@@ -0,0 +1,47 @@
from dataclasses import dataclass
from pydantic import ValidationError
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentProduct
from bin.contentctl_project.contentctl_core.application.factory.factory import FactoryInputDto, Factory, FactoryOutputDto
from bin.contentctl_project.contentctl_core.application.factory.ba_factory import BAFactoryInputDto, BAFactory, BAFactoryOutputDto
@dataclass(frozen=True)
class ValidateInputDto:
factory_input_dto: FactoryInputDto
ba_factory_input_dto: BAFactoryInputDto
product: SecurityContentProduct
class Validate:
def execute(self, input_dto: ValidateInputDto) -> None:
if input_dto.product == SecurityContentProduct.ESCU:
factory_output_dto = FactoryOutputDto([],[],[],[],[],[],[],[],[])
factory = Factory(factory_output_dto)
factory.execute(input_dto.factory_input_dto)
elif input_dto.product == SecurityContentProduct.SSA:
factory_output_dto = BAFactoryOutputDto([],[])
factory = BAFactory(factory_output_dto)
factory.execute(input_dto.ba_factory_input_dto)
# validate detections
# validate tests
self.validate_detection_exist_for_test(factory_output_dto.tests, factory_output_dto.detections)
def validate_detection_exist_for_test(self, tests : list, detections: list):
for test in tests:
found_detection = False
for detection in detections:
if test.tests[0].file in detection.file_path:
found_detection = True
if not found_detection:
ValueError("detection doesn't exist for test file: " + test.name)
@@ -0,0 +1,98 @@
SES_CONTEXT_MAPPING = {
"Unknown": 0,
"Source:Endpoint": 10,
"Source:AD": 11,
"Source:Firewall": 12,
"Source:Application Log": 13,
"Source:IPS": 14,
"Source:Cloud Data": 15,
"Source:Correlation": 16,
"Source:Printer": 17,
"Source:Badge": 18,
"Scope:Internal": 20,
"Scope:External": 21,
"Scope:Inbound": 22,
"Scope:Outbound": 23,
"Scope:Local": 24,
"Scope:Network": 25,
"Outcome:Blocked": 30,
"Outcome:Allowed": 31,
"Stage:Recon": 40,
"Stage:Initial Access": 41,
"Stage:Execution": 42,
"Stage:Persistence": 43,
"Stage:Privilege Escalation": 44,
"Stage:Defense Evasion": 45,
"Stage:Credential Access": 46,
"Stage:Discovery": 47,
"Stage:Lateral Movement": 48,
"Stage:Collection": 49,
"Stage:Exfiltration": 50,
"Stage:Command And Control": 51,
"Consequence:Infection": 60,
"Consequence:Reduced Visibility": 61,
"Consequence:Data Destruction": 62,
"Consequence:Denial Of Service": 63,
"Consequence:Loss Of Control": 64,
"Rares:Rare User": 70,
"Rares:Rare Process": 71,
"Rares:Rare Device": 72,
"Rares:Rare Domain": 73,
"Rares:Rare Network": 74,
"Rares:Rare Location": 75,
"Other:Peer Group": 80,
"Other:Brute Force": 81,
"Other:Policy Violation": 82,
"Other:Threat Intelligence": 83,
"Other:Flight Risk": 84,
"Other:Removable Storage": 85
}
SES_KILL_CHAIN_MAPPINGS = {
"Unknown": 0,
"Reconnaissance": 1,
"Weaponization": 2,
"Delivery": 3,
"Exploitation": 4,
"Installation": 5,
"Command & Control": 6,
"Actions on Objectives": 7
}
SES_OBSERVABLE_ROLE_MAPPING = {
"Other": -1,
"Unknown": 0,
"Actor": 1,
"Target": 2,
"Attacker": 3,
"Victim": 4,
"Parent Process": 5,
"Child Process": 6,
"Known Bad": 7,
"Data Loss": 8,
"Observer": 9
}
SES_OBSERVABLE_TYPE_MAPPING = {
"Other": -1,
"Unknown": 0,
"Device": 1,
"Container": 2,
"Endpoint": 3,
"Hostname": 4,
"IP Address": 5,
"User": 6,
"Username": 7,
"Email": 8,
"Email Address": 9,
"URL": 10,
"URL Domain": 11,
"File": 12,
"File Name": 13,
"File Hash": 14,
"Process": 15,
"Process Name": 16,
"Location": 17
}
@@ -0,0 +1,100 @@
import string
import uuid
import requests
from pydantic import BaseModel, validator, ValidationError
from dataclasses import dataclass
from datetime import datetime
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import DataModel
from bin.contentctl_project.contentctl_core.domain.entities.baseline_tags import BaselineTags
from bin.contentctl_project.contentctl_core.domain.entities.deployment import Deployment
class Baseline(BaseModel, SecurityContentObject):
# baseline spec
name: str
id: str
version: int
date: str
author: str
type: str
datamodel: list
description: str
search: str
how_to_implement: str
known_false_positives: str
references: list
tags: BaselineTags
# enrichment
deployment: Deployment = None
@validator('name')
def name_max_length(cls, v):
if len(v) > 75:
raise ValueError('name is longer then 75 chars: ' + v)
return v
@validator('name')
def name_invalid_chars(cls, v):
invalidChars = set(string.punctuation.replace("-", ""))
if any(char in invalidChars for char in v):
raise ValueError('invalid chars used in name: ' + v)
return v
@validator('id')
def id_check(cls, v, values):
try:
uuid.UUID(str(v))
except:
raise ValueError('uuid is not valid: ' + values["name"])
return v
@validator('date')
def date_valid(cls, v, values):
try:
datetime.strptime(v, "%Y-%m-%d")
except:
raise ValueError('date is not in format YYYY-MM-DD: ' + values["name"])
return v
@validator('type')
def type_valid(cls, v, values):
if v != "Baseline":
raise ValueError('not valid analytics type: ' + values["name"])
return v
@validator('datamodel')
def datamodel_valid(cls, v, values):
for datamodel in v:
if datamodel not in [el.name for el in DataModel]:
raise ValueError('not valid data model: ' + values["name"])
return v
@validator('description', 'how_to_implement')
def encode_error(cls, v, values, field):
try:
v.encode('ascii')
except UnicodeEncodeError:
raise ValueError('encoding error in ' + field.name + ': ' + values["name"])
return v
@validator('references')
def references_check(cls, v, values):
for reference in v:
try:
get = requests.get(reference)
if not get.status_code == 200:
raise ValueError('Reference ' + reference + ' is not reachable: ' + values["name"])
except requests.exceptions.RequestException as e:
raise ValueError('Reference ' + reference + ' is not reachable: ' + values["name"])
return v
@validator('search')
def search_validate(cls, v, values):
# write search validator
return v
@@ -0,0 +1,25 @@
from pydantic import BaseModel, validator, ValidationError
class BaselineTags(BaseModel):
analytic_story: list
deployments: list = None
detections: list
product: list
required_fields: list
security_domain: str
@validator('product')
def tags_product(cls, v, values):
valid_products = [
"Splunk Enterprise", "Splunk Enterprise Security", "Splunk Cloud",
"Splunk Security Analytics for AWS", "Splunk Behavioral Analytics"
]
for value in v:
if value not in valid_products:
raise ValueError('product is not valid for ' + values['name'] + '. valid products are ' + str(valid_products))
return v
@@ -0,0 +1,60 @@
import uuid
import string
from pydantic import BaseModel, validator, ValidationError
from datetime import datetime
from bin.contentctl_project.contentctl_core.domain.entities.security_content_object import SecurityContentObject
from bin.contentctl_project.contentctl_core.domain.entities.deployment_scheduling import DeploymentScheduling
from bin.contentctl_project.contentctl_core.domain.entities.deployment_email import DeploymentEmail
from bin.contentctl_project.contentctl_core.domain.entities.deployment_notable import DeploymentNotable
from bin.contentctl_project.contentctl_core.domain.entities.deployment_rba import DeploymentRBA
from bin.contentctl_project.contentctl_core.domain.entities.deployment_slack import DeploymentSlack
from bin.contentctl_project.contentctl_core.domain.entities.deployment_phantom import DeploymentPhantom
class Deployment(BaseModel, SecurityContentObject):
name: str
id: str
date: str
author: str
description: str
scheduling: DeploymentScheduling = None
email: DeploymentEmail = None
notable: DeploymentNotable = None
rba: DeploymentRBA = None
slack: DeploymentSlack = None
phantom: DeploymentPhantom = None
tags: dict
@validator('name')
def name_invalid_chars(cls, v):
invalidChars = set(string.punctuation.replace("-", ""))
if any(char in invalidChars for char in v):
raise ValueError('invalid chars used in name: ' + v)
return v
@validator('id')
def id_check(cls, v, values):
try:
uuid.UUID(str(v))
except:
raise ValueError('uuid is not valid: ' + values["name"])
return v
@validator('date')
def date_valid(cls, v, values):
try:
datetime.strptime(v, "%Y-%m-%d")
except:
raise ValueError('date is not in format YYYY-MM-DD: ' + values["name"])
return v
@validator('description')
def encode_error(cls, v, values, field):
try:
v.encode('ascii')
except UnicodeEncodeError:
raise ValueError('encoding error in ' + field.name + ': ' + values["name"])
return v
@@ -0,0 +1,8 @@
from pydantic import BaseModel, validator, ValidationError
class DeploymentEmail(BaseModel):
message: str
subject: str
to: str
@@ -0,0 +1,8 @@
from pydantic import BaseModel, validator, ValidationError
class DeploymentNotable(BaseModel):
rule_description: str
rule_title: str
nes_fields: list
@@ -0,0 +1,10 @@
from pydantic import BaseModel, validator, ValidationError
class DeploymentPhantom(BaseModel):
cam_workers : str
label : str
phantom_server : str
sensitivity : str
severity : str
@@ -0,0 +1,7 @@
from pydantic import BaseModel, validator, ValidationError
class DeploymentRBA(BaseModel):
enabled: str

Some files were not shown because too many files have changed in this diff Show More