Compare commits
2 Commits
aa4d9e2bec
...
pre-commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bb6dd34c3 | |||
| 3cb6966dee |
@@ -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: ''
|
||||
|
||||
@@ -7,7 +7,7 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Why the change?
|
||||
### Why the change?
|
||||
|
||||
|
||||
### A summary of the change
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user