From 254b4eb23f4d2b127c34422c6aa31bd004c3976f Mon Sep 17 00:00:00 2001 From: Justin Ibarra Date: Fri, 4 Mar 2022 08:20:44 -0900 Subject: [PATCH] Generate ATT&CK navigator layer files and links (#1787) * Generate attack layer files and build with package * add update-navigator-gists command * add workflow to update navigator gists on pushes to main * Add coverage readme * fix keys for links * update navigator layer names * purge gist files prior to update; add badge * Update how the navigator links are displayed * moved navigator code to dedicated and refactored to dataclasses * convert gist links to permalink versions * alphabetize; catch 404 for gist update --- .github/workflows/pythonpackage.yml | 5 + .github/workflows/release-fleet.yml | 2 +- README.md | 1 + detection_rules/__init__.py | 2 + detection_rules/attack.py | 6 + detection_rules/devtools.py | 73 ++++++- detection_rules/docs.py | 3 +- detection_rules/ghwrap.py | 35 ++++ detection_rules/misc.py | 4 +- detection_rules/navigator.py | 287 ++++++++++++++++++++++++++++ detection_rules/packaging.py | 17 +- docs/ATT&CK-coverage.md | 82 ++++++++ 12 files changed, 509 insertions(+), 8 deletions(-) create mode 100644 detection_rules/navigator.py create mode 100644 docs/ATT&CK-coverage.md diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 21f6b0aa9..eaeb3d5f0 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -49,3 +49,8 @@ jobs: run: | python -m detection_rules test + - name: Update navigator gist files + env: + GITHUB_TOKEN: "${{ secrets.NAVIGATOR_GIST_TOKEN }}" + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + run: python -m detection_rules dev update-navigator-gists diff --git a/.github/workflows/release-fleet.yml b/.github/workflows/release-fleet.yml index db8612e0b..cabb1e869 100644 --- a/.github/workflows/release-fleet.yml +++ b/.github/workflows/release-fleet.yml @@ -23,7 +23,7 @@ jobs: uses: actions/github-script@v3 with: script: | - if ('refs/heads/main' === '${{github.event.ref}}') { + if ('refs/heads/main' === '${{github.ref}}') { core.setFailed('Forbidden branch') } diff --git a/README.md b/README.md index b9509348f..a5a8e1068 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Supported Python versions](https://img.shields.io/badge/python-3.8+-yellow.svg)](https://www.python.org/downloads/) [![Unit Tests](https://github.com/elastic/detection-rules/workflows/Unit%20Tests/badge.svg)](https://github.com/elastic/detection-rules/actions) [![Chat](https://img.shields.io/badge/chat-%23security--detection--rules-blueviolet)](https://ela.st/slack) +[![ATT&CK navigator coverage](https://img.shields.io/badge/ATT&CK-Navigator-red.svg)](https://ela.st/detection-rules-navigator) # Detection Rules diff --git a/detection_rules/__init__.py b/detection_rules/__init__.py index 84042de12..505b579a4 100644 --- a/detection_rules/__init__.py +++ b/detection_rules/__init__.py @@ -18,6 +18,7 @@ from . import ( # noqa: E402 mappings, ml, misc, + navigator, rule_formatter, rule_loader, schemas, @@ -34,6 +35,7 @@ __all__ = ( "main", 'misc', 'ml', + 'navigator', 'rule_formatter', 'rule_loader', 'schemas', diff --git a/detection_rules/attack.py b/detection_rules/attack.py index 68e235002..cdac1fc07 100644 --- a/detection_rules/attack.py +++ b/detection_rules/attack.py @@ -33,7 +33,13 @@ def get_attack_file_path() -> str: return attack_file[0] +_, _attack_path_base = get_attack_file_path().split('-v') +_ext_length = len('.json.gz') +CURRENT_ATTACK_VERSION = _attack_path_base[:-_ext_length] + + def load_attack_gz() -> dict: + return json.loads(read_gzip(get_attack_file_path())) diff --git a/detection_rules/devtools.py b/detection_rules/devtools.py index cf2da197a..340fda3c3 100644 --- a/detection_rules/devtools.py +++ b/detection_rules/devtools.py @@ -14,10 +14,12 @@ import subprocess import textwrap import time import typing +import urllib.parse from pathlib import Path from typing import Dict, Optional, Tuple, List import click +import requests.exceptions import yaml from elasticsearch import Elasticsearch @@ -26,10 +28,10 @@ from . import rule_loader, utils from .cli_utils import single_collection from .docs import IntegrationSecurityDocs from .eswrap import CollectEvents, add_range_to_dsl -from .ghwrap import GithubClient +from .ghwrap import GithubClient, update_gist from .main import root from .misc import PYTHON_LICENSE, add_client, client_error -from .packaging import PACKAGE_FILE, Package, RELEASE_DIR, current_stack_version +from .packaging import PACKAGE_FILE, RELEASE_DIR, CURRENT_RELEASE_PATH, Package, current_stack_version from .version_lock import default_version_lock from .rule import AnyRuleData, BaseRuleData, QueryRuleData, TOMLRule from .rule_loader import RuleCollection, production_filter @@ -39,6 +41,11 @@ from .utils import dict_hash, get_path, load_dump RULES_DIR = get_path('rules') GH_CONFIG = Path.home() / ".config" / "gh" / "hosts.yml" +NAVIGATOR_GIST_ID = '1a3f65224822a30a8228a8ed20289a89' +NAVIGATOR_URL = 'https://ela.st/detection-rules-navigator' +NAVIGATOR_BADGE = ( + f'[![ATT&CK navigator coverage](https://img.shields.io/badge/ATT&CK-Navigator-red.svg)]({NAVIGATOR_URL})' +) def get_github_token() -> Optional[str]: @@ -739,6 +746,68 @@ def update_schemas(): cls.save_schema() +@dev_group.command('update-navigator-gists') +@click.option('--directory', type=Path, default=CURRENT_RELEASE_PATH.joinpath('extras', 'navigator_layers'), + help='Directory containing only navigator files.') +@click.option('--token', required=True, prompt=get_github_token() is None, default=get_github_token(), + help='GitHub token to push to gist', hide_input=True) +@click.option('--gist-id', default=NAVIGATOR_GIST_ID, help='Gist ID to be updated (must exist).') +@click.option('--print-markdown', is_flag=True, help='Print the generated urls') +def update_navigator_gists(directory: Path, token: str, gist_id: str, print_markdown: bool) -> list: + """Update the gists with new navigator files.""" + assert directory.exists(), f'{directory} does not exist' + + def raw_permalink(raw_link): + # Gist file URLs change with each revision, but can be permalinked to the latest by removing the hash after raw + prefix, _, suffix = raw_link.rsplit('/', 2) + return '/'.join([prefix, suffix]) + + file_map = {f: f.read_text() for f in directory.glob('*.json')} + try: + response = update_gist(token, + file_map, + description='ATT&CK Navigator layer files.', + gist_id=gist_id, + pre_purge=True) + except requests.exceptions.HTTPError as exc: + if exc.response.status_code == requests.status_codes.codes.not_found: + raise client_error('Gist not found: verify the gist_id exists and the token has access to it', exc=exc) + else: + raise + + response_data = response.json() + raw_urls = {name: raw_permalink(data['raw_url']) for name, data in response_data['files'].items()} + + base_url = 'https://mitre-attack.github.io/attack-navigator/#layerURL={}&leave_site_dialog=false&tabs=false' + + # pull out full and platform coverage to print on top of markdown table + all_url = base_url.format(urllib.parse.quote_plus(raw_urls.pop('Elastic-detection-rules-all.json'))) + platforms_url = base_url.format(urllib.parse.quote_plus(raw_urls.pop('Elastic-detection-rules-platforms.json'))) + + generated_urls = [all_url, platforms_url] + markdown_links = [] + for name, gist_url in raw_urls.items(): + query = urllib.parse.quote_plus(gist_url) + url = f'https://mitre-attack.github.io/attack-navigator/#layerURL={query}&leave_site_dialog=false&tabs=false' + generated_urls.append(url) + link_name = name.split('.')[0] + markdown_links.append(f'|[{link_name}]({url})|') + + if print_markdown: + markdown = [ + f'**Full coverage**: {NAVIGATOR_BADGE}', + '\n', + f'**Coverage by platform**: [navigator]({platforms_url})', + '\n', + '| other navigator links by rule attributes |', + '|------------------------------------------|', + ] + markdown_links + click.echo('\n'.join(markdown) + '\n') + + click.echo(f'Gist update status on {len(generated_urls)} files: {response.status_code} {response.reason}') + return generated_urls + + @dev_group.group('test') def test_group(): """Commands for testing against stack resources.""" diff --git a/detection_rules/docs.py b/detection_rules/docs.py index e391277fb..0ad2b4456 100644 --- a/detection_rules/docs.py +++ b/detection_rules/docs.py @@ -16,7 +16,7 @@ from typing import Dict, Iterable, Optional, Union import json import xlsxwriter -from .attack import technique_lookup, matrix, attack_tm, tactics +from .attack import attack_tm, matrix, tactics, technique_lookup from .packaging import Package from .rule_loader import DeprecatedCollection, RuleCollection from .rule import ThreatMapping, TOMLRule @@ -220,6 +220,7 @@ class PackageDocument(xlsxwriter.Workbook): worksheet.autofilter(0, 0, max([len(v) for k, v in matrix.items()]) + 1, len(tactics) - 1) +# product rule docs # Documentation generation of product docs https://www.elastic.co/guide/en/security/7.15/detection-engine-overview.html diff --git a/detection_rules/ghwrap.py b/detection_rules/ghwrap.py index e6c3fd3ff..f6dd79a44 100644 --- a/detection_rules/ghwrap.py +++ b/detection_rules/ghwrap.py @@ -19,6 +19,7 @@ from zipfile import ZipFile import click import requests +from requests import Response from .schemas import definitions @@ -98,6 +99,40 @@ def download_gh_asset(url: str, path: str, overwrite=False): z.close() +def update_gist(token: str, + file_map: Dict[Path, str], + description: str, + gist_id: str, + public=False, + pre_purge=False) -> Response: + """Update existing gist.""" + url = f'https://api.github.com/gists/{gist_id}' + headers = { + 'accept': 'application/vnd.github.v3+json', + 'Authorization': f'token {token}' + } + body = { + 'description': description, + 'files': {}, # {path.name: {'content': contents} for path, contents in file_map.items()}, + 'public': public + } + + if pre_purge: + # retrieve all existing file names which are not in the file_map and overwrite them to empty to delete files + response = requests.get(url) + response.raise_for_status() + data = response.json() + files = list(data['files']) + body['files'] = {file: {} for file in files if file not in file_map} + response = requests.patch(url, headers=headers, json=body) + response.raise_for_status() + + body['files'] = {path.name: {'content': contents} for path, contents in file_map.items()} + response = requests.patch(url, headers=headers, json=body) + response.raise_for_status() + return response + + class GithubClient: """GitHub client wrapper.""" diff --git a/detection_rules/misc.py b/detection_rules/misc.py index f9a522d3e..e48154428 100644 --- a/detection_rules/misc.py +++ b/detection_rules/misc.py @@ -59,8 +59,8 @@ class ClientError(click.ClickException): def show(self, file=None, err=True): """Print the error to the console.""" - err = f' {self.original_error_type}' if self.original_error else '' - msg = f'{click.style(f"CLI Error {self.original_error_type}", fg="red", bold=True)}: {self.format_message()}' + # err_msg = f' {self.original_error_type}' if self.original_error else '' + msg = f'{click.style(f"CLI Error ({self.original_error_type})", fg="red", bold=True)}: {self.format_message()}' click.echo(msg, err=err, file=file) diff --git a/detection_rules/navigator.py b/detection_rules/navigator.py new file mode 100644 index 000000000..70d00396c --- /dev/null +++ b/detection_rules/navigator.py @@ -0,0 +1,287 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +"""Create summary documents for a rule package.""" + +from collections import defaultdict +from dataclasses import dataclass, field, fields +from pathlib import Path +from typing import Dict, List, Optional +from marshmallow import pre_load + +import json + +from . import utils +from .attack import CURRENT_ATTACK_VERSION +from .mixins import MarshmallowDataclassMixin +from .rule import TOMLRule + + +_DEFAULT_PLATFORMS = [ + "Azure AD", + "Containers", + "Google Workspace", + "IaaS", + "Linux", + "macOS", + "Network", + "Office 365", + "PRE", + "SaaS", + "Windows" +] +_DEFAULT_NAVIGATOR_LINKS = { + "label": "repo", + "url": "https://github.com/elastic/detection-rules" +} + + +@dataclass +class NavigatorMetadata(MarshmallowDataclassMixin): + """Metadata for ATT&CK navigator objects.""" + name: str + value: str + + +@dataclass +class NavigatorLinks(MarshmallowDataclassMixin): + """Metadata for ATT&CK navigator objects.""" + label: str + url: str + + +@dataclass +class Techniques(MarshmallowDataclassMixin): + """ATT&CK navigator techniques array class.""" + techniqueID: str + tactic: str + score: int + metadata: List[NavigatorMetadata] + links: List[NavigatorLinks] + + color: str = '' + comment: str = '' + enabled: bool = True + showSubtechniques: bool = False + + @pre_load + def set_score(self, data: dict, **kwargs): + data['score'] = len(data['metadata']) + return data + + +@dataclass +class Navigator(MarshmallowDataclassMixin): + """ATT&CK navigator class.""" + @dataclass + class Versions: + attack: str + layer: str = '4.3' + navigator: str = '4.5.5' + + @dataclass + class Filters: + platforms: list = field(default_factory=_DEFAULT_PLATFORMS.copy) + + @dataclass + class Layout: + layout: str = 'side' + aggregateFunction: str = 'average' + showID: bool = True + showName: bool = True + showAggregateScores: bool = False + countUnscored: bool = False + + @dataclass + class Gradient: + colors: list = field(default_factory=['#d3e0fa', '#0861fb'].copy) + minValue: int = 0 + maxValue: int = 10 + + # not all defaults set + name: str + versions: Versions + techniques: List[Techniques] + + # all defaults set + filters: Filters = fields(Filters) + layout: Layout = fields(Layout) + gradient: Gradient = fields(Gradient) + + domain: str = 'enterprise-attack' + description: str = 'Elastic detection-rules coverage' + hideDisabled: bool = False + legendItems: list = field(default_factory=list) + links: List[NavigatorLinks] = field(default_factory=[_DEFAULT_NAVIGATOR_LINKS].copy) + metadata: Optional[List[NavigatorLinks]] = field(default_factory=list) + showTacticRowBackground: bool = False + selectTechniquesAcrossTactics: bool = False + selectSubtechniquesWithParent: bool = False + sorting: int = 0 + tacticRowBackground: str = '#dddddd' + + +def technique_dict() -> dict: + return {'metadata': [], 'links': []} + + +class NavigatorBuilder: + """Rule navigator mappings and management.""" + + def __init__(self, detection_rules: List[TOMLRule]): + self.detection_rules = detection_rules + + self.layers = { + 'all': defaultdict(lambda: defaultdict(technique_dict)), + 'platforms': defaultdict(lambda: defaultdict(technique_dict)), + + # these will build multiple layers + 'indexes': defaultdict(lambda: defaultdict(lambda: defaultdict(technique_dict))), + 'tags': defaultdict(lambda: defaultdict(lambda: defaultdict(technique_dict))) + } + self.process_rules() + + @staticmethod + def meta_dict(name: str, value: any) -> dict: + meta = { + 'name': name, + 'value': value + } + return meta + + @staticmethod + def links_dict(label: str, url: any) -> dict: + links = { + 'label': label, + 'url': url + } + return links + + def rule_links_dict(self, rule: TOMLRule) -> dict: + base_url = 'https://github.com/elastic/detection-rules/blob/main/rules/' + local_rules_path = utils.get_path('rules') + base_path = rule.path.relative_to(local_rules_path) + url = f'{base_url}{base_path}' + return self.links_dict(rule.name, url) + + def get_layer(self, layer_name: str, layer_key: Optional[str] = None) -> dict: + """Safely retrieve a layer with optional sub-keys.""" + return self.layers[layer_name][layer_key] if layer_key else self.layers[layer_name] + + def _update_all(self, rule: TOMLRule, tactic: str, technique_id: str): + value = f'{rule.contents.data.type}/{rule.contents.data.get("language")}' + self.add_rule_to_technique(rule, 'all', tactic, technique_id, value) + + def _update_platforms(self, rule: TOMLRule, tactic: str, technique_id: str): + value = rule.path.parent.name + self.add_rule_to_technique(rule, 'platforms', tactic, technique_id, value) + + def _update_indexes(self, rule: TOMLRule, tactic: str, technique_id: str): + for index in rule.contents.data.get('index', []): + value = rule.id + self.add_rule_to_technique(rule, 'indexes', tactic, technique_id, value, layer_key=index.lower()) + + def _update_tags(self, rule: TOMLRule, tactic: str, technique_id: str): + for tag in rule.contents.data.get('tags', []): + value = rule.id + layer_key = tag.replace(' ', '-').lower() + self.add_rule_to_technique(rule, 'tags', tactic, technique_id, value, layer_key=layer_key) + + def add_rule_to_technique(self, + rule: TOMLRule, + layer_name: str, + tactic: str, + technique_id: str, + value: str, + layer_key: Optional[str] = None): + """Add a rule to a technique metadata and links.""" + layer = self.get_layer(layer_name, layer_key) + layer[tactic][technique_id]['metadata'].append(self.meta_dict(rule.name, value)) + layer[tactic][technique_id]['links'].append(self.rule_links_dict(rule)) + + def process_rule(self, rule: TOMLRule, tactic: str, technique_id: str): + self._update_all(rule, tactic, technique_id) + self._update_platforms(rule, tactic, technique_id) + self._update_indexes(rule, tactic, technique_id) + self._update_tags(rule, tactic, technique_id) + + def process_rules(self): + """Adds rule to each applicable layer, including multi-layers.""" + for rule in self.detection_rules: + threat = rule.contents.data.threat + if threat: + for entry in threat: + tactic = entry.tactic.name.lower() + if entry.technique: + for technique_entry in entry.technique: + technique_id = technique_entry.id + self.process_rule(rule, tactic, technique_id) + + if technique_entry.subtechnique: + for sub in technique_entry.subtechnique: + self.process_rule(rule, tactic, sub.id) + + def build_navigator(self, layer_name: str, layer_key: Optional[str] = None) -> Navigator: + populated_techniques = [] + layer = self.get_layer(layer_name, layer_key) + base_name = f'{layer_name}-{layer_key}' if layer_key else layer_name + name = f'Elastic-detection-rules-{base_name}' + + for tactic, techniques in layer.items(): + tactic_normalized = '-'.join(tactic.lower().split()) + for technique_id, rules_data in techniques.items(): + rules_data.update(tactic=tactic_normalized, techniqueID=technique_id) + techniques = Techniques.from_dict(rules_data) + + populated_techniques.append(techniques.to_dict()) + + base_nav_obj = { + 'name': name, + 'techniques': populated_techniques, + 'versions': {'attack': CURRENT_ATTACK_VERSION} + } + navigator = Navigator.from_dict(base_nav_obj) + return navigator + + def build_all(self) -> List[Navigator]: + built = [] + + for layer_name, data in self.layers.items(): + # this is a single layer + if 'defense evasion' in data: + built.append(self.build_navigator(layer_name)) + else: + # multi layers + for layer_key, sub_data in data.items(): + built.append(self.build_navigator(layer_name, layer_key)) + + return built + + @staticmethod + def _save(built: Navigator, directory: Path, verbose=True) -> Path: + path = directory.joinpath(built.name).with_suffix('.json') + path.write_text(json.dumps(built.to_dict(), indent=2)) + + if verbose: + print(f'saved: {path}') + return path + + def save_layer(self, + layer_name: str, + directory: Path, + layer_key: Optional[str] = None, + verbose=True + ) -> (Path, dict): + built = self.build_navigator(layer_name, layer_key) + return self._save(built, directory, verbose), built + + def save_all(self, directory: Path, verbose=True) -> Dict[Path, Navigator]: + paths = {} + + for built in self.build_all(): + path = self._save(built, directory, verbose) + paths[path] = built + + return paths diff --git a/detection_rules/packaging.py b/detection_rules/packaging.py index 479e12258..0a9eaad13 100644 --- a/detection_rules/packaging.py +++ b/detection_rules/packaging.py @@ -13,12 +13,13 @@ import shutil import textwrap from collections import defaultdict from pathlib import Path -from typing import Optional, Tuple +from typing import Dict, Optional, Tuple import click import yaml from .misc import JS_LICENSE, cached +from .navigator import NavigatorBuilder, Navigator from .rule import TOMLRule, QueryRuleData, ThreatMapping from .rule_loader import DeprecatedCollection, RuleCollection, DEFAULT_RULES_DIR from .schemas import definitions @@ -73,6 +74,9 @@ def load_current_package_version() -> str: return load_etc_dump('packages.yml')['package']['name'] +CURRENT_RELEASE_PATH = Path(RELEASE_DIR) / load_current_package_version() + + class Package(object): """Packaging object for siem rules and releases.""" @@ -138,7 +142,7 @@ class Package(object): with open(os.path.join(save_dir, 'index.ts'), 'wt') as f: f.write('\n'.join(index_ts)) - def save_release_files(self, directory, changed_rules, new_rules, removed_rules): + def save_release_files(self, directory: str, changed_rules: list, new_rules: list, removed_rules: list): """Release a package.""" summary, changelog = self.generate_summary_and_changelog(changed_rules, new_rules, removed_rules) with open(os.path.join(directory, f'{self.name}-summary.txt'), 'w') as f: @@ -146,6 +150,8 @@ class Package(object): with open(os.path.join(directory, f'{self.name}-changelog-entry.md'), 'w') as f: f.write(changelog) + self.generate_attack_navigator(Path(directory)) + consolidated = json.loads(self.get_consolidated()) with open(os.path.join(directory, f'{self.name}-consolidated-rules.json'), 'w') as f: json.dump(consolidated, f, sort_keys=True, indent=2) @@ -356,6 +362,13 @@ class Package(object): return summary_str, changelog_str + def generate_attack_navigator(self, path: Path) -> Dict[Path, Navigator]: + """Generate ATT&CK navigator layer files.""" + save_dir = path / 'navigator_layers' + save_dir.mkdir() + lb = NavigatorBuilder(self.rules.rules) + return lb.save_all(save_dir, verbose=False) + def generate_xslx(self, path): """Generate a detailed breakdown of a package in an excel file.""" from .docs import PackageDocument diff --git a/docs/ATT&CK-coverage.md b/docs/ATT&CK-coverage.md new file mode 100644 index 000000000..f8f46c332 --- /dev/null +++ b/docs/ATT&CK-coverage.md @@ -0,0 +1,82 @@ +# Rule coverage + +ATT&CK navigator layer files are generated when a package is built with `make release` or `python -m detection-rules`. +This also means they can be downloaded from all successful builds. + +These files can be used to pass to a custom navigator +session. For convenience, the links are generated below. You can also include multiple across tabs in a single session, +though it is not advisable to upload _all_ of them as it will likely overload your browsers resources. + +## Current rule coverage + +The source files for these links are regenerated with every successful merge to main. These represent coverage from the +state of rules in the `main` branch. + + +**Full coverage**: [![ATT&CK navigator coverage](https://img.shields.io/badge/ATT&CK-Navigator-red.svg)](https://ela.st/detection-rules-navigator) + + +**Coverage by platform**: [navigator](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-platforms.json&leave_site_dialog=false&tabs=false) + + +| other navigator links by rule attributes | +|------------------------------------------| +|[Elastic-detection-rules-indexes-auditbeat-*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-auditbeat-%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-filebeat-*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-filebeat-%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-aws*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-aws%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-azure*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-azure%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-cyberarkpas](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-cyberarkpas.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-endpoint](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-endpoint.events.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-gcp*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-gcp%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-google_workspace*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-google_workspace%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-o365*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-o365%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-okta*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-okta%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-system](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-system.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-logs-windows](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-logs-windows.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-metrics-*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-metrics-%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-packetbeat-*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-packetbeat-%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-traces-*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-traces-%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-indexes-winlogbeat-*](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-indexes-winlogbeat-%2A.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-active-directory](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-active-directory.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-application](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-application.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-asset-visibility](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-asset-visibility.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-aws](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-aws.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-azure](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-azure.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-cloud](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-cloud.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-collection](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-collection.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-command-and-control](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-command-and-control.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-communication](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-communication.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-configuration-audit](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-configuration-audit.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-continuous-monitoring](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-continuous-monitoring.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-credential-access](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-credential-access.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-cyberarkpas](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-cyberarkpas.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-data-protection](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-data-protection.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-defense-evasion](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-defense-evasion.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-discovery](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-discovery.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-elastic](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-elastic.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-execution](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-execution.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-gcp](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-gcp.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-google-workspace](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-google-workspace.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-host](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-host.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-identity-and-access](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-identity-and-access.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-identity](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-identity.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-impact](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-impact.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-initial-access](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-initial-access.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-lateral-movement](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-lateral-movement.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-linux](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-linux.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-log-auditing](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-log-auditing.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-macos](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-macos.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-microsoft-365](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-microsoft-365.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-ml](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-ml.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-monitoring](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-monitoring.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-network-security](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-network-security.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-network](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-network.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-okta](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-okta.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-persistence](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-persistence.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-post-execution](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-post-execution.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-privilege-escalation](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-privilege-escalation.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-secops](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-secops.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-threat-detection](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-threat-detection.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-windows](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-windows.json&leave_site_dialog=false&tabs=false)| +|[Elastic-detection-rules-tags-zoom](https://mitre-attack.github.io/attack-navigator/#layerURL=https%3A%2F%2Fgist.githubusercontent.com%2Fbrokensound77%2F1a3f65224822a30a8228a8ed20289a89%2Fraw%2FElastic-detection-rules-tags-zoom.json&leave_site_dialog=false&tabs=false)|