diff --git a/attackcti/attack_api.py b/attackcti/attack_api.py index 1d9445b..284c7b8 100644 --- a/attackcti/attack_api.py +++ b/attackcti/attack_api.py @@ -9,7 +9,7 @@ # https://github.com/oasis-open/cti-python-stix2/issues/183 # https://stackoverflow.com/a/4406521 -from stix2 import TAXIICollectionSource, Filter, CompositeDataSource, FileSystemSource +from stix2 import TAXIICollectionSource, Filter, CompositeDataSource from stix2.datastore.filters import apply_common_filters from stix2.utils import get_type_from_id from stix2.v20.sdo import ( @@ -25,32 +25,22 @@ import json import os -from .models import * -from pydantic import TypeAdapter +from pydantic import TypeAdapter, ValidationError from typing import List, Type, Dict, Any, Union +from attackcti.models import * +from attackcti.utils.storage import STIXStore # os.environ['http_proxy'] = "http://xxxxxxx" # os.environ['https_proxy'] = "https://xxxxxxx" ATTACK_STIX_COLLECTIONS = "https://cti-taxii.mitre.org/stix/collections/" ENTERPRISE_ATTACK = "95ecc380-afe9-11e4-9b6c-751b66dd541e" -PRE_ATTACK = "062767bd-02d2-4b72-84ba-56caef0f8658" MOBILE_ATTACK = "2f669986-b40b-4423-b720-4396ca6a462b" ICS_ATTACK = "02c3ef24-9cd4-48f3-a99f-b74ce24f1d34" -ENTERPRISE_ATTACK_LOCAL_DIR = "enterprise-attack" -PRE_ATTACK_LOCAL_DIR = "pre-attack" -MOBILE_ATTACK_LOCAL_DIR = "mobile-attack" -ICS_ATTACK_LOCAL_DIR = "ics-attack" - -class attack_client(object): - """A Python Module for ATT&CK""" - TC_ENTERPRISE_SOURCE = None - TC_PRE_SOURCE = None - TC_MOBILE_SOURCE = None - TC_ICS_SOURCE = None - COMPOSITE_DS = None - +class attack_client: + """A Python Module for accessing ATT&CK data locally or remotely.""" + pydantic_model_mapping = { "techniques": Technique, "data-component": DataComponent, @@ -74,37 +64,79 @@ class attack_client(object): "x-mitre-data-component": DataComponent } - def __init__(self, local_path=None, include_pre_attack=False, proxies=None, verify=True): + def __init__(self, local_paths=None, proxies=None, verify=True): """ + Initializes the ATT&CK client, setting up local or remote data sources. + Args: - proxies - See https://requests.readthedocs.io/en/latest/user/advanced/#proxies - verify - See https://requests.readthedocs.io/en/latest/user/advanced/#ssl-cert-verification + local_paths (dict, optional): Dictionary with paths to local directories or JSON files for each domain. + Keys should be 'enterprise', 'mobile', and 'ics'. + proxies (dict, optional): Dictionary mapping protocol or protocol and hostname to the URL of the proxy. + verify (bool, optional): Whether to verify SSL certificates. Defaults to True. """ + self.COMPOSITE_DS = CompositeDataSource() - if local_path is not None and os.path.isdir(os.path.join(local_path, ENTERPRISE_ATTACK_LOCAL_DIR)) \ - and os.path.isdir(os.path.join(local_path, PRE_ATTACK_LOCAL_DIR)) \ - and os.path.isdir(os.path.join(local_path, MOBILE_ATTACK_LOCAL_DIR)) \ - and os.path.isdir(os.path.join(local_path, ICS_ATTACK_LOCAL_DIR)): - self.TC_ENTERPRISE_SOURCE = FileSystemSource(os.path.join(local_path, ENTERPRISE_ATTACK_LOCAL_DIR)) - self.TC_PRE_SOURCE = FileSystemSource(os.path.join(local_path, PRE_ATTACK_LOCAL_DIR)) - self.TC_MOBILE_SOURCE = FileSystemSource(os.path.join(local_path, MOBILE_ATTACK_LOCAL_DIR)) - self.TC_ICS_SOURCE = FileSystemSource(os.path.join(local_path, ICS_ATTACK_LOCAL_DIR)) - else: - ENTERPRISE_COLLECTION = Collection(ATTACK_STIX_COLLECTIONS + ENTERPRISE_ATTACK + "/", verify=verify, proxies=proxies) - PRE_COLLECTION = Collection(ATTACK_STIX_COLLECTIONS + PRE_ATTACK + "/", verify=verify, proxies=proxies) - MOBILE_COLLECTION = Collection(ATTACK_STIX_COLLECTIONS + MOBILE_ATTACK + "/", verify=verify, proxies=proxies) - ICS_COLLECTION = Collection(ATTACK_STIX_COLLECTIONS + ICS_ATTACK + "/", verify=verify, proxies=proxies) + # Validate local_paths with Pydantic + if local_paths: + try: + self.local_paths = STIXLocalPaths(**local_paths) + except ValidationError as e: + raise ValueError(f"Invalid local_paths: {e}") - self.TC_ENTERPRISE_SOURCE = TAXIICollectionSource(ENTERPRISE_COLLECTION) - self.TC_PRE_SOURCE = TAXIICollectionSource(PRE_COLLECTION) - self.TC_MOBILE_SOURCE = TAXIICollectionSource(MOBILE_COLLECTION) - self.TC_ICS_SOURCE = TAXIICollectionSource(ICS_COLLECTION) + # Initialize data sources + self.init_data_sources(self.local_paths if local_paths else None, proxies, verify) + + def init_data_sources(self, local_paths, proxies, verify): + """ + Initializes data sources, either local or remote. + + Args: + local_paths (LocalPathsModel, optional): Validated dictionary with paths to local directories or JSON files for each domain. + proxies (dict, optional): Dictionary mapping protocol or protocol and hostname to the URL of the proxy. + verify (bool, optional): Whether to verify SSL certificates. Defaults to True. + """ + if local_paths: + self.TC_ENTERPRISE_SOURCE = self.load_stix_store(local_paths.enterprise) + self.TC_MOBILE_SOURCE = self.load_stix_store(local_paths.mobile) + self.TC_ICS_SOURCE = self.load_stix_store(local_paths.ics) + + if not (self.TC_ENTERPRISE_SOURCE and self.TC_MOBILE_SOURCE and self.TC_ICS_SOURCE): + self.initialize_taxii_sources(proxies, verify) + else: + self.initialize_taxii_sources(proxies, verify) - self.COMPOSITE_DS = CompositeDataSource() self.COMPOSITE_DS.add_data_sources([self.TC_ENTERPRISE_SOURCE, self.TC_MOBILE_SOURCE, self.TC_ICS_SOURCE]) - if include_pre_attack: - self.COMPOSITE_DS.add_data_sources([self.TC_PRE_SOURCE]) + def load_stix_store(self, path): + """ + Loads a STIXStore from the given path. + + Args: + path (str): Path to the source directory or JSON file. + + Returns: + The loaded STIXStore or None if the path is invalid. + """ + if path and os.path.exists(path): + store = STIXStore(path) + return store.get_store() + return None + + def initialize_taxii_sources(self, proxies, verify): + """ + Initializes data sources from the ATT&CK TAXII server. + + Args: + proxies (dict, optional): Dictionary mapping protocol or protocol and hostname to the URL of the proxy. + verify (bool, optional): Whether to verify SSL certificates. Defaults to True. + """ + ENTERPRISE_COLLECTION = Collection(ATTACK_STIX_COLLECTIONS + ENTERPRISE_ATTACK + "/", verify=verify, proxies=proxies) + MOBILE_COLLECTION = Collection(ATTACK_STIX_COLLECTIONS + MOBILE_ATTACK + "/", verify=verify, proxies=proxies) + ICS_COLLECTION = Collection(ATTACK_STIX_COLLECTIONS + ICS_ATTACK + "/", verify=verify, proxies=proxies) + + self.TC_ENTERPRISE_SOURCE = TAXIICollectionSource(ENTERPRISE_COLLECTION) + self.TC_MOBILE_SOURCE = TAXIICollectionSource(MOBILE_COLLECTION) + self.TC_ICS_SOURCE = TAXIICollectionSource(ICS_COLLECTION) def get_stix_objects( self, diff --git a/attackcti/models.py b/attackcti/models.py index c411206..028bdc2 100644 --- a/attackcti/models.py +++ b/attackcti/models.py @@ -191,4 +191,9 @@ def extract_phase_name(cls, values: Dict[str, Any]): kill_chain_phases = values['tactic'] phase_names = [phase['phase_name'] for phase in kill_chain_phases if 'phase_name' in phase] values['tactic'] = phase_names - return values \ No newline at end of file + return values + +class STIXLocalPaths(BaseModel): + enterprise: Optional[str] = Field(None, description="Path to the local enterprise-attack directory or JSON file.") + mobile: Optional[str] = Field(None, description="Path to the local mobile-attack directory or JSON file.") + ics: Optional[str] = Field(None, description="Path to the local ics-attack directory or JSON file.") diff --git a/attackcti/utils/__init__.py b/attackcti/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/attackcti/utils/downloader.py b/attackcti/utils/downloader.py new file mode 100644 index 0000000..9e2b03c --- /dev/null +++ b/attackcti/utils/downloader.py @@ -0,0 +1,179 @@ +import requests +from pathlib import Path +from typing import Optional, List, Dict +import re +import json + +class STIXDownloader: + def __init__(self, download_dir: str, domain: Optional[str] = None, stix_version: Optional[str] = None, use_session: bool = False): + """ + Initializes the STIXDownloader with optional default settings. + + Args: + download_dir (str): Directory to download the STIX files to. + domain (Optional[str]): Default ATT&CK domain from the following list ["enterprise", "mobile", "ics"]. + stix_version (Optional[str]): Default version of STIX to download. Options are "2.0" or "2.1". + use_session (bool): Whether to use a persistent session for HTTP requests. Defaults to False. + """ + self.download_dir = download_dir + self.domain = domain + self.stix_version = stix_version + self.use_session = use_session + self.cti_base_url = "https://raw.githubusercontent.com/mitre/cti/" + self.stix_data_base_url = "https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/" + self.session = requests.Session() if use_session else None # Use a session if specified + self.downloaded_file_paths: Dict[str, str] = {} # Attribute to store the full paths of the downloaded files + + @staticmethod + def fetch_attack_stix2_0_versions() -> List[str]: + """ + Fetches available ATT&CK versions in STIX 2.0 format from the cti GitHub repository. + + Returns: + List[str]: A list of available ATT&CK versions in STIX 2.0 format. + """ + ref_to_tag = re.compile(r"ATT&CK-v(.*)") + tags = requests.get("https://api.github.com/repos/mitre/cti/git/refs/tags").json() + versions = [ref_to_tag.search(tag["ref"]).groups()[0] for tag in tags if "ATT&CK-v" in tag["ref"]] + return versions + + @staticmethod + def fetch_attack_stix2_1_versions() -> List[str]: + """ + Fetches available ATT&CK versions in STIX 2.1 format from the attack-stix-data repository. + + Returns: + List[str]: A list of available ATT&CK versions in STIX 2.1 format. + """ + index_url = "https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/index.json" + index_data = requests.get(index_url).json() + versions = [v["version"] for v in index_data["collections"][0]["versions"]] + return versions + + def download_file(self, url: str, dest_path: str) -> None: + """ + Downloads a file from the given URL to the specified destination path. + + Args: + url (str): URL of the file to download. + dest_path (str): Destination file path to save the downloaded file. + + Raises: + requests.HTTPError: If the download request fails. + """ + if self.session: + response = self.session.get(url, stream=True) # Use session if available + else: + response = requests.get(url, stream=True) # Otherwise, use a regular request + + response.raise_for_status() + with open(dest_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + def is_pretty_printed(self, file_path: str) -> bool: + """ + Checks if the JSON file is already pretty-printed. + + Args: + file_path (str): Path to the JSON file to check. + + Returns: + bool: True if the file is pretty-printed, False otherwise. + """ + with open(file_path, 'r', encoding='utf-8') as f: + for i, line in enumerate(f): + if i > 10: # Check only the first few lines for efficiency + break + if len(line.strip()) == 0: + continue + if line.strip().startswith('{') or line.strip().startswith('['): + continue + return True + return False + + def pretty_print_json(self, file_path: str) -> None: + """ + Converts a compact JSON file to a pretty-printed format. + + Args: + file_path (str): Path to the JSON file to be pretty-printed. + """ + with open(file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=4, ensure_ascii=False) + + def download_attack_data(self, stix_version: Optional[str] = None, domain: Optional[str] = None, release: Optional[str] = None, pretty_print: Optional[bool] = None): + """ + Downloads the ATT&CK STIX release file. If release is not specified, downloads the latest release. + + Args: + stix_version (Optional[str]): Version of STIX to download. Options are "2.0" or "2.1". If not specified, uses the default. + domain (Optional[str]): An ATT&CK domain from the following list ["enterprise", "mobile", "ics"]. If not specified, uses the default. + release (Optional[str]): ATT&CK release to download. If not specified, downloads the latest release. + pretty_print (Optional[bool]): Whether to pretty-print the JSON file after downloading. If None, do not pretty-print. + + Raises: + ValueError: If the STIX version is invalid or the release version does not exist. + """ + stix_version = stix_version or self.stix_version + domain = domain or self.domain + + if stix_version not in ["2.0", "2.1"]: + raise ValueError("Invalid STIX version. Choose '2.0' or '2.1'.") + + if stix_version == "2.0": + versions = self.fetch_attack_stix2_0_versions() + base_url = self.cti_base_url + if release is None: + release_dir = "master" + elif release not in versions: + raise ValueError(f"Release {release} not found in cti repository.") + else: + release_dir = f"ATT%26CK-v{release}" + url_path = f"{release_dir}/{domain}-attack/{domain}-attack.json" + else: + versions = self.fetch_attack_stix2_1_versions() + base_url = self.stix_data_base_url + if release is None: + release_dir = "master" + elif release not in versions: + raise ValueError(f"Release {release} not found in attack-stix-data repository.") + else: + url_path = f"{domain}-attack/{domain}-attack-{release}.json" + + download_url = f"{base_url}{url_path}" + + release_folder = "latest" if release is None else f"v{release}" + release_download_dir = Path(self.download_dir) / release_folder + release_download_dir.mkdir(parents=True, exist_ok=True) + + dest_path = release_download_dir / f"{domain}-attack.json" + self.download_file(download_url, dest_path) + + self.downloaded_file_path = str(dest_path) # Store the full path of the downloaded file + self.downloaded_file_paths[domain] = str(dest_path) # Store the path for the specific domain + + if pretty_print: + if self.is_pretty_printed(self.downloaded_file_path): + print("Warning: The file appears to be already pretty-printed.") + self.pretty_print_json(self.downloaded_file_path) + + print(f"Downloaded {domain}-attack.json to {release_download_dir}") + + def download_all_domains(self, stix_version: Optional[str] = None, release: Optional[str] = None, pretty_print: Optional[bool] = None): + """ + Downloads the ATT&CK STIX release files for all domains (enterprise, mobile, ics). + + Args: + stix_version (Optional[str]): Version of STIX to download. Options are "2.0" or "2.1". If not specified, uses the default. + release (Optional[str]): ATT&CK release to download. If not specified, downloads the latest release. + pretty_print (Optional[bool]): Whether to pretty-print the JSON file after downloading. If None, do not pretty-print. + """ + domains = ["enterprise", "mobile", "ics"] + for domain in domains: + self.download_attack_data(stix_version=stix_version, domain=domain, release=release, pretty_print=pretty_print) + + return self.downloaded_file_paths \ No newline at end of file diff --git a/attackcti/utils/storage.py b/attackcti/utils/storage.py new file mode 100644 index 0000000..2e32630 --- /dev/null +++ b/attackcti/utils/storage.py @@ -0,0 +1,43 @@ +from stix2 import FileSystemSource, MemorySource +from pathlib import Path + +class STIXStore: + def __init__(self, path: str, auto_load: bool = True): + """ + Initializes the STIXStore. + + Args: + path (str): Path to the source directory or JSON file. + auto_load (bool): Flag indicating whether to automatically load data during initialization. Defaults to True. + """ + self.path = Path(path) + self.source = None + + if auto_load: + self.load_data() + + def load_data(self): + """ + Loads data from the specified path, determining if it's a directory or a file. + + Raises: + ValueError: If the path is invalid or not specified correctly. + """ + if self.path.is_dir(): + self.source = FileSystemSource(str(self.path)) + elif self.path.is_file() and self.path.suffix == '.json': + self.source = MemorySource() + self.source.load_from_file(str(self.path)) + else: + raise ValueError(f"The specified path {self.path} is not a valid directory or JSON file.") + + def get_store(self): + """ + Returns the loaded data store. + + Returns: + The loaded data store (FileSystemSource or MemoryStore). + """ + if self.source is None: + raise ValueError("Data has not been loaded yet. Call load_data() first.") + return self.source \ No newline at end of file diff --git a/docs/playground/0-Download-ATTACK-STIX-Data.ipynb b/docs/playground/0-Download-ATTACK-STIX-Data.ipynb new file mode 100644 index 0000000..0be82de --- /dev/null +++ b/docs/playground/0-Download-ATTACK-STIX-Data.ipynb @@ -0,0 +1,273 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Download ATT&CK STIX Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import STIX Downloader" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from attackcti.utils.downloader import STIXDownloader" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize STIX 2.0 Downloader" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "stix20_downloader = STIXDownloader(download_dir=\"./downloads\", stix_version=\"2.0\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download ATT&CK Enterprise v15.1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloaded enterprise-attack.json to downloads/v15.1\n" + ] + } + ], + "source": [ + "stix20_downloader.download_attack_data(domain=\"enterprise\", release=\"15.1\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize STX 2.1 Downloader " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "stix21_downloader = STIXDownloader(download_dir=\"./downloads\", stix_version=\"2.1\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download ATT&CK Mobile v15.1" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloaded mobile-attack.json to downloads/v15.1\n" + ] + } + ], + "source": [ + "stix21_downloader.download_attack_data(domain=\"mobile\", release=\"15.1\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'downloads/v15.1/mobile-attack.json'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "stix21_downloader.downloaded_file_path" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'mobile': 'downloads/v15.1/mobile-attack.json'}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "stix21_downloader.downloaded_file_paths" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize STIX Storage" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from attackcti.utils.storage import STIXStore" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "store = STIXStore(stix21_downloader.downloaded_file_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import Filter\n", + "\n", + "filters = [Filter(\"type\", \"=\", \"attack-pattern\")]\n", + "\n", + "techniques = store.source.query(filters)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "187" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(techniques)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download All Domains at Once" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "stix20_downloader = STIXDownloader(download_dir=\"./downloads\", stix_version=\"2.0\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloaded enterprise-attack.json to downloads/v15.1\n", + "Downloaded mobile-attack.json to downloads/v15.1\n", + "Downloaded ics-attack.json to downloads/v15.1\n" + ] + }, + { + "data": { + "text/plain": [ + "{'enterprise': 'downloads/v15.1/enterprise-attack.json',\n", + " 'mobile': 'downloads/v15.1/mobile-attack.json',\n", + " 'ics': 'downloads/v15.1/ics-attack.json'}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "stix20_downloader.download_all_domains(release=\"15.1\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/playground/11-Initialize_Client_Local_STIX_data.ipynb b/docs/playground/11-Initialize_Client_Local_STIX_data.ipynb new file mode 100644 index 0000000..e24aef1 --- /dev/null +++ b/docs/playground/11-Initialize_Client_Local_STIX_data.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Initialize ATTACK Client with Local STIX Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Local JSON Files" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "local_paths = {\n", + " 'enterprise': 'downloads/v15.1/enterprise-attack.json',\n", + " 'mobile': 'downloads/v15.1/mobile-attack.json',\n", + " 'ics': 'downloads/v15.1/ics-attack.json'\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialize ATTACK Client" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from attackcti import attack_client" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "lift = attack_client(local_paths=local_paths)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "enterprise_techniques = lift.get_enterprise_techniques()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "637" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(enterprise_techniques)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AttackPattern(type='attack-pattern', id='attack-pattern--005a06c6-14bf-4118-afa0-ebcd8aebb0c9', created_by_ref='identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', created='2019-11-27T14:58:00.429Z', modified='2023-11-15T14:33:53.354Z', name='Scheduled Task', description='Adversaries may abuse the Windows Task Scheduler to perform task scheduling for initial or recurring execution of malicious code. There are multiple ways to access the Task Scheduler in Windows. The [schtasks](https://attack.mitre.org/software/S0111) utility can be run directly on the command line, or the Task Scheduler can be opened through the GUI within the Administrator Tools section of the Control Panel. In some cases, adversaries have used a .NET wrapper for the Windows Task Scheduler, and alternatively, adversaries have used the Windows netapi32 library to create a scheduled task.\\n\\nThe deprecated [at](https://attack.mitre.org/software/S0110) utility could also be abused by adversaries (ex: [At](https://attack.mitre.org/techniques/T1053/002)), though at.exe can not access tasks created with schtasks or the Control Panel.\\n\\nAn adversary may use Windows Task Scheduler to execute programs at system startup or on a scheduled basis for persistence. The Windows Task Scheduler can also be abused to conduct remote Execution as part of Lateral Movement and/or to run a process under the context of a specified account (such as SYSTEM). Similar to [System Binary Proxy Execution](https://attack.mitre.org/techniques/T1218), adversaries have also abused the Windows Task Scheduler to potentially mask one-time execution under signed/trusted system processes.(Citation: ProofPoint Serpent)\\n\\nAdversaries may also create \"hidden\" scheduled tasks (i.e. [Hide Artifacts](https://attack.mitre.org/techniques/T1564)) that may not be visible to defender tools and manual queries used to enumerate tasks. Specifically, an adversary may hide a task from `schtasks /query` and the Task Scheduler by deleting the associated Security Descriptor (SD) registry value (where deletion of this value must be completed using SYSTEM permissions).(Citation: SigmaHQ)(Citation: Tarrask scheduled task) Adversaries may also employ alternate methods to hide tasks, such as altering the metadata (e.g., `Index` value) within associated registry keys.(Citation: Defending Against Scheduled Task Attacks in Windows Environments) ', kill_chain_phases=[KillChainPhase(kill_chain_name='mitre-attack', phase_name='execution'), KillChainPhase(kill_chain_name='mitre-attack', phase_name='persistence'), KillChainPhase(kill_chain_name='mitre-attack', phase_name='privilege-escalation')], revoked=False, external_references=[ExternalReference(source_name='mitre-attack', url='https://attack.mitre.org/techniques/T1053/005', external_id='T1053.005'), ExternalReference(source_name='ProofPoint Serpent', description='Campbell, B. et al. (2022, March 21). Serpent, No Swiping! New Backdoor Targets French Entities with Unique Attack Chain. Retrieved April 11, 2022.', url='https://www.proofpoint.com/us/blog/threat-insight/serpent-no-swiping-new-backdoor-targets-french-entities-unique-attack-chain'), ExternalReference(source_name='Defending Against Scheduled Task Attacks in Windows Environments', description='Harshal Tupsamudre. (2022, June 20). Defending Against Scheduled Tasks. Retrieved July 5, 2022.', url='https://blog.qualys.com/vulnerabilities-threat-research/2022/06/20/defending-against-scheduled-task-attacks-in-windows-environments'), ExternalReference(source_name='Twitter Leoloobeek Scheduled Task', description='Loobeek, L. (2017, December 8). leoloobeek Status. Retrieved December 12, 2017.', url='https://twitter.com/leoloobeek/status/939248813465853953'), ExternalReference(source_name='Tarrask scheduled task', description='Microsoft Threat Intelligence Team & Detection and Response Team . (2022, April 12). Tarrask malware uses scheduled tasks for defense evasion. Retrieved June 1, 2022.', url='https://www.microsoft.com/security/blog/2022/04/12/tarrask-malware-uses-scheduled-tasks-for-defense-evasion/'), ExternalReference(source_name='Microsoft Scheduled Task Events Win10', description='Microsoft. (2017, May 28). Audit Other Object Access Events. Retrieved June 27, 2019.', url='https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/audit-other-object-access-events'), ExternalReference(source_name='TechNet Scheduled Task Events', description='Microsoft. (n.d.). General Task Registration. Retrieved December 12, 2017.', url='https://technet.microsoft.com/library/dd315590.aspx'), ExternalReference(source_name='TechNet Autoruns', description='Russinovich, M. (2016, January 4). Autoruns for Windows v13.51. Retrieved June 6, 2016.', url='https://technet.microsoft.com/en-us/sysinternals/bb963902'), ExternalReference(source_name='TechNet Forum Scheduled Task Operational Setting', description='Satyajit321. (2015, November 3). Scheduled Tasks History Retention settings. Retrieved December 12, 2017.', url='https://social.technet.microsoft.com/Forums/en-US/e5bca729-52e7-4fcb-ba12-3225c564674c/scheduled-tasks-history-retention-settings?forum=winserver8gen'), ExternalReference(source_name='SigmaHQ', description='Sittikorn S. (2022, April 15). Removal Of SD Value to Hide Schedule Task - Registry. Retrieved June 1, 2022.', url='https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_delete/registry_delete_schtasks_hide_task_via_sd_value_removal.yml')], object_marking_refs=['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], x_mitre_attack_spec_version='3.2.0', x_mitre_contributors=['Andrew Northern, @ex_raritas', 'Bryan Campbell, @bry_campbell', 'Zachary Abzug, @ZackDoesML', 'Selena Larson, @selenalarson', 'Sittikorn Sangrattanapitak'], x_mitre_data_sources=['Windows Registry: Windows Registry Key Creation', 'File: File Modification', 'File: File Creation', 'Process: Process Creation', 'Command: Command Execution', 'Network Traffic: Network Traffic Flow', 'Scheduled Job: Scheduled Job Creation'], x_mitre_deprecated=False, x_mitre_detection='Monitor process execution from the svchost.exe in Windows 10 and the Windows Task Scheduler taskeng.exe for older versions of Windows. (Citation: Twitter Leoloobeek Scheduled Task) If scheduled tasks are not used for persistence, then the adversary is likely to remove the task when the action is complete. Monitor Windows Task Scheduler stores in %systemroot%\\\\System32\\\\Tasks for change entries related to scheduled tasks that do not correlate with known software, patch cycles, etc.\\n\\nConfigure event logging for scheduled task creation and changes by enabling the \"Microsoft-Windows-TaskScheduler/Operational\" setting within the event logging service. (Citation: TechNet Forum Scheduled Task Operational Setting) Several events will then be logged on scheduled task activity, including: (Citation: TechNet Scheduled Task Events)(Citation: Microsoft Scheduled Task Events Win10)\\n\\n* Event ID 106 on Windows 7, Server 2008 R2 - Scheduled task registered\\n* Event ID 140 on Windows 7, Server 2008 R2 / 4702 on Windows 10, Server 2016 - Scheduled task updated\\n* Event ID 141 on Windows 7, Server 2008 R2 / 4699 on Windows 10, Server 2016 - Scheduled task deleted\\n* Event ID 4698 on Windows 10, Server 2016 - Scheduled task created\\n* Event ID 4700 on Windows 10, Server 2016 - Scheduled task enabled\\n* Event ID 4701 on Windows 10, Server 2016 - Scheduled task disabled\\n\\nTools such as Sysinternals Autoruns may also be used to detect system changes that could be attempts at persistence, including listing current scheduled tasks. (Citation: TechNet Autoruns)\\n\\nRemote access tools with built-in features may interact directly with the Windows API to perform these functions outside of typical system utilities. Tasks may also be created through Windows system management tools such as Windows Management Instrumentation and PowerShell, so additional logging may need to be configured to gather the appropriate data.', x_mitre_domains=['enterprise-attack'], x_mitre_is_subtechnique=True, x_mitre_modified_by_ref='identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_permissions_required=['Administrator'], x_mitre_platforms=['Windows'], x_mitre_remote_support=True, x_mitre_version='1.5')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "enterprise_techniques[1]" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/setup.py b/setup.py index c4fb0fe..485bf27 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name="attackcti", - version="0.4.2", + version="0.4.4", author="Roberto Rodriguez", description="MITRE ATTACK CTI Python Libary", long_description=long_description,