Compare commits

...

2 Commits

Author SHA1 Message Date
Hare Sudhan 0bb6dd34c3 update 2025-11-28 22:36:47 -05:00
Hare Sudhan 3cb6966dee pre-commit checks 2025-11-26 00:13:06 -05:00
20 changed files with 290 additions and 254 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: Idea
about: An idea for a feature or improvement to Atomic Red Team.
about: An idea for a feature or improvement to Atomic Red Team.
title: 'Idea: '
labels: 'idea'
assignees: ''
+1 -1
View File
@@ -7,7 +7,7 @@ assignees: ''
---
### Why the change?
### Why the change?
### A summary of the change
+9 -9
View File
@@ -5,12 +5,12 @@
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+48 -47
View File
@@ -2,58 +2,59 @@ name: assign-labels
on:
workflow_run:
workflows: ["validate-atomics"]
workflows:
- "validate-atomics"
types:
- completed
- completed
jobs:
assign-labels:
runs-on: ubuntu-latest
steps:
- name: download-artifact
uses: actions/github-script@v8
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "labels.json"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/labels.zip`, Buffer.from(download.data));
- name: download-artifact
uses: actions/github-script@v8
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "labels.json"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/labels.zip`, Buffer.from(download.data));
- name: unzip-artifact
run: unzip labels.zip
- name: unzip-artifact
run: unzip labels.zip
- name: assign-labels-and-reviewers
uses: actions/github-script@v8
with:
script: |
let fs = require('fs');
const obj = JSON.parse(fs.readFileSync('./labels.json'));
console.log(obj)
if(obj.labels.length > 0){
await github.rest.issues.addLabels({
issue_number: obj.pr,
owner: context.repo.owner,
repo: context.repo.repo,
labels: obj.labels
})
}
if(obj.maintainers.length > 0){
await github.rest.issues.addAssignees({
issue_number: obj.pr,
owner: context.repo.owner,
repo: context.repo.repo,
assignees: obj.maintainers
});
}
- name: assign-labels-and-reviewers
uses: actions/github-script@v8
with:
script: |-
let fs = require('fs');
const obj = JSON.parse(fs.readFileSync('./labels.json'));
console.log(obj)
if(obj.labels.length > 0){
await github.rest.issues.addLabels({
issue_number: obj.pr,
owner: context.repo.owner,
repo: context.repo.repo,
labels: obj.labels
})
}
if(obj.maintainers.length > 0){
await github.rest.issues.addAssignees({
issue_number: obj.pr,
owner: context.repo.owner,
repo: context.repo.repo,
assignees: obj.maintainers
});
}
+55 -54
View File
@@ -1,69 +1,70 @@
name: generate-docs
on:
push:
branches: [ "master" ]
branches:
- "master"
jobs:
generate-docs:
runs-on: ubuntu-latest
steps:
- name: checkout repo
uses: actions/checkout@v6
with:
token: ${{ secrets.PROTECTED_BRANCH_PUSH_TOKEN }}
- name: checkout repo
uses: actions/checkout@v6
with:
token: ${{ secrets.PROTECTED_BRANCH_PUSH_TOKEN }}
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v6
with:
python-version: "3.11.2"
cache: "poetry"
- name: Install dependencies
run: poetry install --no-interaction
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v6
with:
python-version: "3.11.2"
cache: "poetry"
- name: Install dependencies
run: poetry install --no-interaction
- name: Generate shields.io URL
run: poetry run python runner.py generate-counter
id: counter
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
- name: Generate shields.io URL
run: poetry run python runner.py generate-counter
id: counter
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
- name: Update README
run: |
echo ${{ steps.counter.outputs.result }}
sed -i "s|https://img.shields.io/badge/Atomics-.*-flat.svg|${{ steps.counter.outputs.result }}|" README.md
shell: bash
- name: Update README
run: |
echo ${{ steps.counter.outputs.result }}
sed -i "s|https://img.shields.io/badge/Atomics-.*-flat.svg|${{ steps.counter.outputs.result }}|" README.md
shell: bash
- name: Generate and commit unique GUIDs for each atomic test
run: poetry run python runner.py generate-guids
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
- name: Generate and commit unique GUIDs for each atomic test
run: poetry run python runner.py generate-guids
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
- name: setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0
bundler-cache: true
- name: setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0
bundler-cache: true
- name: generate markdown docs for atomics
run: |
bin/generate-atomic-docs.rb
echo ""
echo ""
git status
echo ""
echo ""
git diff-index HEAD --
- name: generate markdown docs for atomics
run: |-
bin/generate-atomic-docs.rb
echo ""
echo ""
git status
echo ""
echo ""
git diff-index HEAD --
if git diff-index --quiet HEAD -- ; then
echo "Not committing documentation because there are no changes"
else
git config credential.helper 'cache --timeout=120'
git config user.email "opensource@redcanary.com"
git config user.name "Atomic Red Team doc generator"
git add README.md
git add atomics
git commit -am "Generated docs from job=$GITHUB_JOB branch=$GITHUB_REF_NAME [ci skip]"
git push origin $GITHUB_REF_NAME -f
fi
if git diff-index --quiet HEAD -- ; then
echo "Not committing documentation because there are no changes"
else
git config credential.helper 'cache --timeout=120'
git config user.email "opensource@redcanary.com"
git config user.name "Atomic Red Team doc generator"
git add README.md
git add atomics
git commit -am "Generated docs from job=$GITHUB_JOB branch=$GITHUB_REF_NAME [ci skip]"
git push origin $GITHUB_REF_NAME -f
fi
+16 -16
View File
@@ -3,27 +3,27 @@ name: validate-python-file-changes
on:
pull_request:
branches:
- master
- master
paths:
- "atomic_red_team/**/*.py"
- "atomic_red_team/**/*.py"
jobs:
validate-python-file-changes:
runs-on: macos-latest
steps:
- name: checkout repo
uses: actions/checkout@v6
- name: Install poetry
run: pipx install poetry
- name: setup python3.11
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.12.4"
cache: "poetry"
- name: checkout repo
uses: actions/checkout@v6
- name: Install poetry
run: pipx install poetry
- name: setup python3.11
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.12.4"
cache: "poetry"
- name: Install dependencies
run: poetry install --no-interaction
- name: Install dependencies
run: poetry install --no-interaction
- name: Run pytest
run: poetry run pytest atomic_red_team/tests
- name: Run pytest
run: poetry run pytest atomic_red_team/tests
+11 -11
View File
@@ -1,19 +1,19 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
days-before-issue-stale: 30
days-before-pr-stale: 45
days-before-issue-close: 10
days-before-pr-close: 10
- uses: actions/stale@v10
with:
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
days-before-issue-stale: 30
days-before-pr-stale: 45
days-before-issue-close: 10
days-before-pr-close: 10
+58 -58
View File
@@ -3,73 +3,73 @@ name: validate-atomics
on:
pull_request:
branches:
- master
- master
jobs:
validate-atomics:
runs-on: ubuntu-latest
steps:
- name: checkout repo
uses: actions/checkout@v6
- name: Install poetry
run: pipx install poetry
- name: setup python3.11
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.11.2"
cache: "poetry"
- name: checkout repo
uses: actions/checkout@v6
- name: Install poetry
run: pipx install poetry
- name: setup python3.11
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.11.2"
cache: "poetry"
- name: Install dependencies
run: poetry install --no-interaction
- name: Install dependencies
run: poetry install --no-interaction
- name: validate the format of atomics tests against the spec
run: poetry run python runner.py validate
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
- name: validate the format of atomics tests against the spec
run: poetry run python runner.py validate
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
upload:
runs-on: ubuntu-latest
steps:
- name: checkout repo
uses: actions/checkout@v6
- name: Install poetry
run: pipx install poetry
- name: setup python3.11
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.11.2"
cache: "poetry"
- uses: actions/github-script@v8
id: get_pr_number
with:
script: |
if (context.issue.number) {
// Return issue number if present
return context.issue.number;
} else {
// Otherwise return issue number from commit
return (
await github.rest.repos.listPullRequestsAssociatedWithCommit({
commit_sha: context.sha,
owner: context.repo.owner,
repo: context.repo.repo,
})
).data[0].number;
}
result-encoding: string
- name: Install dependencies
run: poetry install --no-interaction --no-root
- name: save labels and reviewers into a file.
run: |
poetry run python runner.py generate-labels --pr '${{steps.get_pr_number.outputs.result}}' --token ${{ secrets.GITHUB_TOKEN }}
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
- name: checkout repo
uses: actions/checkout@v6
- name: Install poetry
run: pipx install poetry
- name: setup python3.11
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.11.2"
cache: "poetry"
- uses: actions/github-script@v8
id: get_pr_number
with:
script: |
if (context.issue.number) {
// Return issue number if present
return context.issue.number;
} else {
// Otherwise return issue number from commit
return (
await github.rest.repos.listPullRequestsAssociatedWithCommit({
commit_sha: context.sha,
owner: context.repo.owner,
repo: context.repo.repo,
})
).data[0].number;
}
result-encoding: string
- name: Install dependencies
run: poetry install --no-interaction --no-root
- name: save labels and reviewers into a file.
run: |
poetry run python runner.py generate-labels --pr '${{steps.get_pr_number.outputs.result}}' --token ${{ secrets.GITHUB_TOKEN }}
working-directory: atomic_red_team
env:
PYTHONPATH: ${{ github.workspace }}
- uses: actions/upload-artifact@v5
with:
name: labels.json
path: atomic_red_team/pr/
- uses: actions/upload-artifact@v5
with:
name: labels.json
path: atomic_red_team/pr/
+8 -8
View File
@@ -3,18 +3,18 @@ name: validate-terraform
on:
pull_request:
branches:
- master
- master
paths:
- "**/*.tf"
- "**/*.tf"
jobs:
validate-terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: hashicorp/setup-terraform@v3
- uses: actions/checkout@v6
- uses: hashicorp/setup-terraform@v3
- name: Terraform fmt
id: fmt
run: terraform fmt -recursive -check
continue-on-error: false
- name: Terraform fmt
id: fmt
run: terraform fmt -recursive -check
continue-on-error: false
+21
View File
@@ -0,0 +1,21 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- id: check-added-large-files
args:
- "--maxkb=10000"
- id: check-merge-conflict
- id: check-json
- id: mixed-line-ending
- id: fix-byte-order-marker
- id: debug-statements
- repo: https://github.com/google/yamlfmt
rev: v0.20.0
hooks:
- id: yamlfmt
args:
- "--verbose"
+14
View File
@@ -0,0 +1,14 @@
# yamlfmt configuration file
# See https://github.com/google/yamlfmt for more options
formatter:
indent: 2
trim_trailing_whitespace: true
eof_newline: true
force_array_style: block
retain_line_breaks_single: true
indentless_arrays: true
verbose: true
exclude:
- ".git/**"
+6 -6
View File
@@ -1,6 +1,6 @@
# Contributor Code of Conduct
Welcome to the [Atomic Red Team online community](https://atomicredteam.io/). Our goal is to foster an open, safe, and welcoming environment. As a collective, we—as contributors, maintainers, and the Open Source Projects team of Red Canary—pledge to encourage our project and community to be a harassment-free space. We invite you to collaborate, exchange thoughts or information, and engage with one another. Atomic Red Team is meant for everyone, regardless of age, personal appearance, body size, disability, nationality, race, ethnicity, gender identity and expression, level of experience or academics, religion, or sexual identity and orientation.
Welcome to the [Atomic Red Team online community](https://atomicredteam.io/). Our goal is to foster an open, safe, and welcoming environment. As a collective, we—as contributors, maintainers, and the Open Source Projects team of Red Canary—pledge to encourage our project and community to be a harassment-free space. We invite you to collaborate, exchange thoughts or information, and engage with one another. Atomic Red Team is meant for everyone, regardless of age, personal appearance, body size, disability, nationality, race, ethnicity, gender identity and expression, level of experience or academics, religion, or sexual identity and orientation.
## Our Guidelines
@@ -27,7 +27,7 @@ If you see anything that you believe breaks our community guidelines, no matter
## Enforcement & Consequences
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Community Manager. Unacceptable behavior will not be tolerated by community members, maintainers, and Red Canary team members. The Atomic Red Team Community Manager and maintainers will review and investigate all complaints.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Community Manager. Unacceptable behavior will not be tolerated by community members, maintainers, and Red Canary team members. The Atomic Red Team Community Manager and maintainers will review and investigate all complaints.
Anyone asked to stop unacceptable behavior is expected to comply immediately. If an Atomic Red Team community member (anyone contributing to our [GitHub Repo](https://github.com/redcanaryco/atomic-red-team) or [Community Slack](https://slack.atomicredteam.io/)) engages in unacceptable behavior, the Community Manager may take any temporary or permanent action they deem appropriate, up to and including immediate expulsion from the Atomic Red Team community without warning.
@@ -49,10 +49,10 @@ This Code of Conduct applies to all of the Atomic Red Team, and “Atomic Family
* [Atomic Red Team GitHub](https://github.com/redcanaryco/atomic-red-team)
* **Atomic Family**
* [Invoke-AtomicRedTeam](https://github.com/redcanaryco/invoke-atomicredteam)
* [AtomicTestHarnesses](https://github.com/redcanaryco/atomictestharnesses)
* [Chain Reactor](https://github.com/redcanaryco/chain-reactor)
* **Atomic Family**
* [Invoke-AtomicRedTeam](https://github.com/redcanaryco/invoke-atomicredteam)
* [AtomicTestHarnesses](https://github.com/redcanaryco/atomictestharnesses)
* [Chain Reactor](https://github.com/redcanaryco/chain-reactor)
## Attribution
+19 -19
View File
@@ -10,7 +10,7 @@ class AtomicRedTeam
# TODO- should these all be relative URLs?
ROOT_GITHUB_URL = "https://github.com/redcanaryco/atomic-red-team"
#
# Returns a list of paths that contain Atomic Tests
#
@@ -19,10 +19,10 @@ class AtomicRedTeam
end
#
# Returns a list of Atomic Tests in Atomic Red Team (as Hashes from source YAML)
# Returns a list of Atomic Tests in Atomic Red Team (as Hashes from source YAML)
#
def atomic_tests
@atomic_tests ||= atomic_test_paths.collect do |path|
@atomic_tests ||= atomic_test_paths.collect do |path|
atomic_yaml = YAML.load(File.read path)
atomic_yaml['atomic_yaml_path'] = path
atomic_yaml
@@ -40,7 +40,7 @@ class AtomicRedTeam
end
test_list = Array.new
atomic_tests.find do |atomic_yaml|
atomic_tests.find do |atomic_yaml|
if atomic_yaml.fetch('attack_technique').upcase == technique_identifier.upcase
atomic_yaml['atomic_tests'].each do |a_test|
if a_test["supported_platforms"].include?(platform[:platform])
@@ -62,13 +62,13 @@ class AtomicRedTeam
technique_or_technique_identifier
end
atomic_tests.find do |atomic_yaml|
atomic_tests.find do |atomic_yaml|
atomic_yaml.fetch('attack_technique').upcase == technique_identifier.upcase
end.to_h.fetch('atomic_tests', [])
end
#
# Returns a Markdown formatted Github link to a technique. This will be to the edit page for
# Returns a Markdown formatted Github link to a technique. This will be to the edit page for
# techniques that already have one or more Atomic Red Team tests, or the create page for
# techniques that have no existing tests for the given OS.
#
@@ -103,17 +103,17 @@ class AtomicRedTeam
def validate_atomic_yaml!(yaml, used_guids_file, unique_guid_array)
raise("YAML file has no elements") if yaml.nil?
raise('`attack_technique` element is required') unless yaml.has_key?('attack_technique')
raise('`attack_technique` element must be a string') unless yaml['attack_technique'].is_a?(String)
raise('`display_name` element is required') unless yaml.has_key?('display_name')
raise('`display_name` element must be an array') unless yaml['display_name'].is_a?(String)
raise('`atomic_tests` element is required') unless yaml.has_key?('atomic_tests')
raise('`atomic_tests` element must be an array') unless yaml['atomic_tests'].is_a?(Array)
raise('`atomic_tests` element is empty - you have no tests') unless yaml['atomic_tests'].count > 0
yaml['atomic_tests'].each_with_index do |atomic, i|
raise("`atomic_tests[#{i}].name` element is required") unless atomic.has_key?('name')
raise("`atomic_tests[#{i}].name` element must be a string") unless atomic['name'].is_a?(String)
@@ -127,10 +127,10 @@ class AtomicRedTeam
raise("`atomic_tests[#{i}].description` element is required") unless atomic.has_key?('description')
raise("`atomic_tests[#{i}].description` element must be a string") unless atomic['description'].is_a?(String)
raise("`atomic_tests[#{i}].supported_platforms` element is required") unless atomic.has_key?('supported_platforms')
raise("`atomic_tests[#{i}].supported_platforms` element must be an Array (was a #{atomic['supported_platforms'].class.name})") unless atomic['supported_platforms'].is_a?(Array)
valid_supported_platforms = ['windows', 'macos', 'linux', 'office-365', 'azure-ad', 'google-workspace', 'saas', 'iaas', 'containers', 'iaas:aws', 'iaas:azure', 'iaas:gcp']
atomic['supported_platforms'].each do |platform|
if !valid_supported_platforms.include?(platform)
@@ -149,22 +149,22 @@ class AtomicRedTeam
arg_name, arg = arg_kvp
raise("`atomic_tests[#{i}].input_arguments[#{iai}].description` element is required") unless arg.has_key?('description')
raise("`atomic_tests[#{i}].input_arguments[#{iai}].description` element must be a string") unless arg['description'].is_a?(String)
raise("`atomic_tests[#{i}].input_arguments[#{iai}].type` element is required") unless arg.has_key?('type')
raise("`atomic_tests[#{i}].input_arguments[#{iai}].type` element must be a string") unless arg['type'].is_a?(String)
raise("`atomic_tests[#{i}].input_arguments[#{iai}].type` element must be lowercased and underscored (was #{arg['type']})") unless arg['type'] =~ /[a-z_]+/
# TODO: determine if we think default values are required for EVERY input argument
# raise("`atomic_tests[#{i}].input_arguments[#{iai}].default` element is required") unless arg.has_key?('default')
# raise("`atomic_tests[#{i}].input_arguments[#{iai}].default` element must be a string (was a #{arg['default'].class.name})") unless arg['default'].is_a?(String)
end
raise("`atomic_tests[#{i}].executor` element is required") unless atomic.has_key?('executor')
executor = atomic['executor']
raise("`atomic_tests[#{i}].executor.name` element is required") unless executor.has_key?('name')
raise("`atomic_tests[#{i}].executor.name` element must be a string") unless executor['name'].is_a?(String)
raise("`atomic_tests[#{i}].executor.name` element must be lowercased and underscored (was #{executor['name']})") unless executor['name'] =~ /[a-z_]+/
valid_executor_types = ['command_prompt', 'sh', 'bash', 'powershell', 'manual', 'aws', 'az', 'gcloud', 'kubectl']
case executor['name']
when 'manual'
@@ -192,7 +192,7 @@ class AtomicRedTeam
def record_used_guids!(yaml, used_guids_file)
return unless !yaml.nil?
yaml['atomic_tests'].each_with_index do |atomic, i|
next unless atomic.has_key?('auto_generated_guid')
guid = atomic["auto_generated_guid"].to_s
@@ -201,7 +201,7 @@ class AtomicRedTeam
end
def generate_guids_for_yaml!(path, used_guids_file)
text = File.read(path)
text = File.read(path)
# add the "auto_generated_guid:" element after the "- name:" element if it isn't already there
text.gsub!(/(?i)(^([ \t]*-[ \t]*)name:.*$(?!\s*auto_generated_guid))/) { |m| "#{$1}\n#{$2.gsub(/-/," ")}auto_generated_guid:"}
# fill the "auto_generated_guid:" element in if it doesn't contain a guid
@@ -218,7 +218,7 @@ class AtomicRedTeam
break unless !is_unique_guid(new_guid, used_guids_file)
end
# add this new unique guid to the used guids file
add_guid_to_used_guid_file(new_guid, used_guids_file)
add_guid_to_used_guid_file(new_guid, used_guids_file)
return new_guid
end
+11 -11
View File
@@ -2,14 +2,14 @@ require 'open-uri'
require 'json'
#
# Attack is an API class that loads information about ATT&CK techniques from MITRE'S ATT&CK
# Attack is an API class that loads information about ATT&CK techniques from MITRE'S ATT&CK
# STIX representation. It makes it very simple to do common things with ATT&CK.
#
class Attack
#
#
# Tactics as presented in the order that the ATT&CK matrics uses
#
def ordered_tactics
def ordered_tactics
[
'initial-access',
'execution',
@@ -26,27 +26,27 @@ class Attack
]
end
#
#
# Returns the technique identifier (T1234) for a Technique object
#
def technique_identifier_for_technique(technique)
technique.fetch('external_references', []).find do |refs|
technique.fetch('external_references', []).find do |refs|
refs['source_name'] == 'mitre-attack'
end['external_id'].upcase
end
#
#
# Returns a Technique object given a technique identifier (T1234)
#
def technique_info(technique_id)
techniques.find do |item|
techniques.find do |item|
item.fetch('external_references', []).find do |references|
references['external_id'] == technique_id.upcase
end
end
end
#
#
# Returns the ATT&CK Matrix as a 2D array, in order by `ordered_tactics`
#
def ordered_tactic_to_technique_matrix(only_platform: /.*/)
@@ -68,7 +68,7 @@ class Attack
all_techniques_in_tactic_order.transpose
end
#
#
# Returns a map of all [ ATT&CK Tactic name ] => [ List of ATT&CK techniques associated with that tactic]
#
def techniques_by_tactic(only_platform: /.*/)
@@ -84,7 +84,7 @@ class Attack
end
end
techniques_by_tactic
end
end
#
# Returns a list of all ATT&CK techniques
@@ -10,4 +10,4 @@ atomic_tests:
executor:
command: |
sudo -i
name: bash
name: bash
@@ -17,4 +17,4 @@ atomic_tests:
"#{gsecdump_exe}" -a
name: command_prompt
elevation_required: true
dependency_executor_name:
dependency_executor_name:
@@ -17,4 +17,4 @@ atomic_tests:
"#{gsecdump_exe}" -a
name: command_prompt
elevation_required: true
dependency_executor_name: "bash"
dependency_executor_name: "bash"
+1 -1
View File
@@ -3,7 +3,7 @@ display_name: 'Brute Force: Password Spraying'
atomic_tests:
- name: Password Spray all Domain Users
auto_generated_guid: 90bc2e54-6c84-47a5-9439-0a2a92b4b175
description:
description: |
CAUTION! Be very careful to not exceed the password lockout threshold for users in the domain by running this test too frequently.
This atomic attempts to map the IPC$ share on one of the Domain Controllers using a password of Spring2020 for each user in the %temp%\users.txt list.
-1
View File
@@ -34,7 +34,6 @@ atomic_tests:
- name: DDEAUTO
auto_generated_guid: cf91174c-4e74-414e-bec0-8d60a104d181
description: |
TrustedSec - Unicorn - https://github.com/trustedsec/unicorn
SensePost DDEAUTO - https://sensepost.com/blog/2017/macro-less-code-exec-in-msword/
+8 -8
View File
@@ -244,7 +244,7 @@ class AtomicRedTeamDocs
"versions" => { "attack": "16", "navigator": "5.1.0", "layer": "4.5" },
"description" => layer_name + " MITRE ATT&CK Navigator Layer",
"domain" => "enterprise-attack",
"filters"=> filters,
"filters"=> filters,
"gradient" => {
"colors" => ["#ffffff",
"#ce232e"
@@ -265,11 +265,11 @@ class AtomicRedTeamDocs
"techniques" => techniques
}
end
#
# Process the current technique and update the list
#
#
def update_techniquesList(current_technique, current_techniqueParent, techniques_list, atomic_yaml, comments)
if not atomic_yaml['attack_technique'].include?(".") then
tech_parent = techniques_list.find { |h| h["techniqueID"] == atomic_yaml['attack_technique'].split('.')[0] }
@@ -298,7 +298,7 @@ class AtomicRedTeamDocs
techniques_list.push(current_technique)
end
end
#
# Generates a MITRE ATT&CK Navigator Layer based on contributed techniques
#
@@ -385,7 +385,7 @@ class AtomicRedTeamDocs
win_technique['score'] += 1
win_technique['comment'] += "- " + atomic['name'] + "\n"
end
if atomic['supported_platforms'].any? {|platform| platform.downcase =~ /macos/} then
if atomic['supported_platforms'].any? {|platform| platform.downcase =~ /macos/} then
has_macos_tests = true
macos_technique['score'] += 1
macos_technique['comment'] += "- " + atomic['name'] + "\n"
@@ -441,7 +441,7 @@ class AtomicRedTeamDocs
esxi_technique['comment'] += "- " + atomic['name'] + "\n"
end
end
# Update full Atomic Layer
update_techniquesList(technique, techniqueParent, techniques, atomic_yaml, false)
# Update all other Atomic Layers
@@ -483,9 +483,9 @@ class AtomicRedTeamDocs
end
end
end
puts techniques_iaas_gcp
layer = get_layer techniques, "Atomic Red Team"
layer_win = get_layer techniques_win, "Atomic Red Team (Windows)"
layer_mac = get_layer techniques_mac, "Atomic Red Team (macOS)"