From 0bb6dd34c39334af6805dd6cb662c84ee64d5967 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Fri, 28 Nov 2025 22:36:47 -0500 Subject: [PATCH] update --- .github/ISSUE_TEMPLATE/idea.md | 2 +- .github/ISSUE_TEMPLATE/website_change.md | 2 +- .github/dependabot.yml | 18 +-- .github/workflows/assign-labels.yml | 95 +++++++------- .github/workflows/generate-docs.yml | 109 ++++++++-------- .github/workflows/run-python-tests.yml | 32 ++--- .github/workflows/stale.yml | 22 ++-- .github/workflows/validate-atomics.yml | 116 +++++++++--------- .github/workflows/validate-terraform.yml | 16 +-- .pre-commit-config.yaml | 38 +++--- .yamlfmt | 2 +- CODE_OF_CONDUCT.md | 12 +- atomic_red_team/atomic_red_team.rb | 38 +++--- atomic_red_team/attack_api.rb | 22 ++-- .../elevation_required_but_not_provided.yaml | 2 +- .../empty_dependency_executor_name.yaml | 2 +- .../invalid_dependency_executor_name.yaml | 2 +- bin/generate-atomic-docs.rb | 16 +-- 18 files changed, 274 insertions(+), 272 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/idea.md b/.github/ISSUE_TEMPLATE/idea.md index 025d8590..9ec8847a 100644 --- a/.github/ISSUE_TEMPLATE/idea.md +++ b/.github/ISSUE_TEMPLATE/idea.md @@ -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: '' diff --git a/.github/ISSUE_TEMPLATE/website_change.md b/.github/ISSUE_TEMPLATE/website_change.md index 945b944a..a88a858b 100644 --- a/.github/ISSUE_TEMPLATE/website_change.md +++ b/.github/ISSUE_TEMPLATE/website_change.md @@ -7,7 +7,7 @@ assignees: '' --- -### Why the change? +### Why the change? ### A summary of the change diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0c714298..e3a19883 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -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" diff --git a/.github/workflows/assign-labels.yml b/.github/workflows/assign-labels.yml index 3d25c28d..94bc47c7 100644 --- a/.github/workflows/assign-labels.yml +++ b/.github/workflows/assign-labels.yml @@ -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 + }); + } diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index 77605f13..ed6b0006 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -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 diff --git a/.github/workflows/run-python-tests.yml b/.github/workflows/run-python-tests.yml index e3655668..4607758c 100644 --- a/.github/workflows/run-python-tests.yml +++ b/.github/workflows/run-python-tests.yml @@ -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 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7d23c830..afd72d76 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -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 diff --git a/.github/workflows/validate-atomics.yml b/.github/workflows/validate-atomics.yml index 6db84fc1..15d74741 100644 --- a/.github/workflows/validate-atomics.yml +++ b/.github/workflows/validate-atomics.yml @@ -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/ diff --git a/.github/workflows/validate-terraform.yml b/.github/workflows/validate-terraform.yml index 7f2fe0fd..7007d944 100644 --- a/.github/workflows/validate-terraform.yml +++ b/.github/workflows/validate-terraform.yml @@ -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 \ No newline at end of file + - name: Terraform fmt + id: fmt + run: terraform fmt -recursive -check + continue-on-error: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f2c988df..cfc637e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +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/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" +- repo: https://github.com/google/yamlfmt + rev: v0.20.0 + hooks: + - id: yamlfmt + args: + - "--verbose" diff --git a/.yamlfmt b/.yamlfmt index dfdb3644..ebdeb63c 100644 --- a/.yamlfmt +++ b/.yamlfmt @@ -9,6 +9,6 @@ formatter: retain_line_breaks_single: true indentless_arrays: true verbose: true - + exclude: - ".git/**" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c40df2dd..8e116464 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -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 diff --git a/atomic_red_team/atomic_red_team.rb b/atomic_red_team/atomic_red_team.rb index 2e158a58..7ea6c82e 100755 --- a/atomic_red_team/atomic_red_team.rb +++ b/atomic_red_team/atomic_red_team.rb @@ -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 diff --git a/atomic_red_team/attack_api.rb b/atomic_red_team/attack_api.rb index 1e92b763..c0f6df25 100755 --- a/atomic_red_team/attack_api.rb +++ b/atomic_red_team/attack_api.rb @@ -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 diff --git a/atomic_red_team/test_data/elevation_required_but_not_provided.yaml b/atomic_red_team/test_data/elevation_required_but_not_provided.yaml index 12a84b4d..d344540e 100644 --- a/atomic_red_team/test_data/elevation_required_but_not_provided.yaml +++ b/atomic_red_team/test_data/elevation_required_but_not_provided.yaml @@ -10,4 +10,4 @@ atomic_tests: executor: command: | sudo -i - name: bash \ No newline at end of file + name: bash diff --git a/atomic_red_team/test_data/empty_dependency_executor_name.yaml b/atomic_red_team/test_data/empty_dependency_executor_name.yaml index 289081f5..949d0718 100644 --- a/atomic_red_team/test_data/empty_dependency_executor_name.yaml +++ b/atomic_red_team/test_data/empty_dependency_executor_name.yaml @@ -17,4 +17,4 @@ atomic_tests: "#{gsecdump_exe}" -a name: command_prompt elevation_required: true - dependency_executor_name: \ No newline at end of file + dependency_executor_name: diff --git a/atomic_red_team/test_data/invalid_dependency_executor_name.yaml b/atomic_red_team/test_data/invalid_dependency_executor_name.yaml index 3f5fdec0..69168e17 100644 --- a/atomic_red_team/test_data/invalid_dependency_executor_name.yaml +++ b/atomic_red_team/test_data/invalid_dependency_executor_name.yaml @@ -17,4 +17,4 @@ atomic_tests: "#{gsecdump_exe}" -a name: command_prompt elevation_required: true - dependency_executor_name: "bash" \ No newline at end of file + dependency_executor_name: "bash" diff --git a/bin/generate-atomic-docs.rb b/bin/generate-atomic-docs.rb index ecdd1f56..f383ef30 100755 --- a/bin/generate-atomic-docs.rb +++ b/bin/generate-atomic-docs.rb @@ -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)"