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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://github.com/elastic/detection-rules/actions)
|
||||
[](https://ela.st/slack)
|
||||
[](https://ela.st/detection-rules-navigator)
|
||||
|
||||
# Detection Rules
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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()))
|
||||
|
||||
|
||||
|
||||
@@ -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'[]({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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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**: [](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)|
|
||||
Reference in New Issue
Block a user