Files
cti/USAGE.md
T

914 lines
41 KiB
Markdown
Raw Normal View History

2021-02-22 13:27:54 -05:00
# Introduction
2020-05-21 14:35:57 -04:00
> [!IMPORTANT]
> **Documentation Notice**
>
> We have unified our STIX 2.0 and STIX 2.1 representations into a single specification.
> For the most up-to-date information about the structure and format of our published STIX files,
2025-12-19 16:04:49 -06:00
> see the [ATT&CK Data Model Specification](https://mitre-attack.github.io/attack-data-model/schemas/).
>
> This document focuses on practical usage examples and Python recipes for working with ATT&CK data. For detailed information about object types, fields, and relationships, please refer to the specification linked above.
2021-02-22 13:27:54 -05:00
This document describes how to query and manipulate ATT&CK data from either this repository or the ATT&CK TAXII server using Python.
2021-02-22 13:31:25 -05:00
The programmatic uses of ATT&CK demonstrated in this document utilize the [stix2 python library](https://github.com/oasis-open/cti-python-stix2). Please refer to the [STIX2 Python API Documentation](https://stix2.readthedocs.io/en/latest/) for more information on how to work with STIX programmatically.
2021-02-22 13:31:25 -05:00
We also recommend reading the [ATT&CK Design and Philosophy Paper](https://attack.mitre.org/docs/ATTACK_Design_and_Philosophy_March_2020.pdf), which describes high-level overall approach, intention, and usage of ATT&CK.
If you are looking for ATT&CK data represented in STIX 2.1, please see our [attack-stix-data](https://github.com/mitre-attack/attack-stix-data) GitHub repository.
2021-02-22 13:31:25 -05:00
## Table of Contents
2022-04-23 14:36:46 -05:00
- [Introduction](#introduction)
- [Table of Contents](#table-of-contents)
- [ATT\&CK Object Types](#attck-object-types)
- [Accessing ATT\&CK data in python](#accessing-attck-data-in-python)
2022-04-23 14:36:46 -05:00
- [Requirements and imports](#requirements-and-imports)
- [stix2](#stix2)
- [taxii2client](#taxii2client)
- [Access local content](#access-local-content)
- [Access via FileSystemSource](#access-via-filesystemsource)
- [Access via bundle](#access-via-bundle)
- [Access live content](#access-live-content)
- [Access from the ATT\&CK TAXII server](#access-from-the-attck-taxii-server)
2022-04-23 14:36:46 -05:00
- [Access from Github via requests](#access-from-github-via-requests)
- [Access a specific version of ATT\&CK](#access-a-specific-version-of-attck)
2022-04-23 14:36:46 -05:00
- [Access multiple domains simultaneously](#access-multiple-domains-simultaneously)
- [Python recipes](#python-recipes)
- [Getting an object](#getting-an-object)
- [By STIX ID](#by-stix-id)
- [By ATT\&CK ID](#by-attck-id)
2022-04-23 14:36:46 -05:00
- [By name](#by-name)
- [By alias](#by-alias)
- [Getting multiple objects](#getting-multiple-objects)
- [Objects by type](#objects-by-type)
- [Getting techniques or sub-techniques](#getting-techniques-or-sub-techniques)
- [Getting software](#getting-software)
- [Objects by content](#objects-by-content)
- [Techniques by platform](#techniques-by-platform)
- [Techniques by tactic](#techniques-by-tactic)
- [Tactics by matrix](#tactics-by-matrix)
- [Objects created or modified since a given date](#objects-created-or-modified-since-a-given-date)
- [Getting related objects](#getting-related-objects)
- [Relationships microlibrary](#relationships-microlibrary)
- [Getting techniques used by a group's software](#getting-techniques-used-by-a-groups-software)
- [Working with deprecated and revoked objects](#working-with-deprecated-and-revoked-objects)
- [Removing revoked and deprecated objects](#removing-revoked-and-deprecated-objects)
- [Getting a revoking object](#getting-a-revoking-object)
## ATT&CK Object Types
2022-04-23 14:36:46 -05:00
ATT&CK uses a mix of predefined and custom STIX objects to implement ATT&CK concepts. For a complete specification of ATT&CK object types, their fields, and relationships, please refer to the [ATT&CK Data Model Specification](https://github.com/mitre-attack/attack-data-model/blob/main/docs/SPEC.md).
2020-05-21 14:35:57 -04:00
2022-04-23 14:36:46 -05:00
## Accessing ATT&CK data in python
There are several ways to acquire the ATT&CK data in Python. All of them will provide an object
implementing the DataStore API and can be used interchangeably with the recipes provided in the [Python recipes](#python-recipes) section.
2020-05-21 14:35:57 -04:00
2020-06-26 12:07:29 -04:00
This section utilizes the [stix2 python library](https://github.com/oasis-open/cti-python-stix2). Please refer to the [STIX2 Python API Documentation](https://stix2.readthedocs.io/en/latest/) for more information on how to work with STIX programmatically.
2020-05-21 14:35:57 -04:00
2022-04-23 14:36:46 -05:00
### Requirements and imports
Before installing requirements, we recommend setting up a virtual environment:
2022-04-23 14:36:46 -05:00
1. Create virtual environment:
- macOS and Linux: `python3 -m venv env`
- Windows: `py -m venv env`
2. Activate the virtual environment:
- macOS and Linux: `source env/bin/activate`
- Windows: `env/Scripts/activate.bat`
2022-04-23 14:36:46 -05:00
#### stix2
[stix2 can be installed by following the instructions on their repository](https://github.com/oasis-open/cti-python-stix2#installation). Imports for the recipes in this repository can be done from the base package, for example:
```python
from stix2 import Filter
```
However, if you are aiming to extend the ATT&CK dataset with new objects or implement complex workflows, you may need to use the `v20` specifier for some imports. This ensures that the objects use the STIX 2.0 API instead of the STIX 2.1 API. For example:
```python
from stix2.v20 import AttackPattern
```
You can see a full list of the classes which have versioned imports [here](https://stix2.readthedocs.io/en/latest/api/stix2.v20.html).
2022-04-23 14:36:46 -05:00
#### taxii2client
[taxii2-client can be installed by following the instructions on their repository](https://github.com/oasis-open/cti-taxii-client#installation). The ATT&CK TAXII server implements the 2.0 version of the TAXII specification, but the default import of `taxii2client` (version 2.0.0 and above) uses the 2.1 version of the TAXII specification, which can lead to 406 responses when connecting to our TAXII server if not accounted for.
If the TAXII Client is getting a 406 Response, make sure you are running the latest version (`pip install --upgrade stix2` or `pip install --upgrade taxii2-client`). In addition, make sure you are running the 2.0 version of the client (using the `v20` import) as shown below in order to communicate with the ATT&CK TAXII 2.0 Server.
2022-04-23 14:36:46 -05:00
```python
from taxii2client.v20 import Collection
```
2022-04-23 14:36:46 -05:00
### Access local content
2020-05-21 14:35:57 -04:00
Many users may opt to access the ATT&CK content via a local copy of the STIX data on this repo. This can be advantageous for several reasons:
2022-04-23 14:36:46 -05:00
2020-05-21 14:35:57 -04:00
- Doesn't require internet access after the initial download
- User can modify the ATT&CK content if desired
- Downloaded copy is static, so updates to the ATT&CK catalog won't cause bugs in automated workflows. User can still manually update by cloning a fresh version of the data
2022-04-23 14:36:46 -05:00
#### Access via FileSystemSource
Each domain in this repo is formatted according to the [STIX2 FileSystem spec](https://stix2.readthedocs.io/en/latest/guide/filesystem.html).
2020-05-21 14:35:57 -04:00
Therefore you can use a `FileSystemSource` to load a domain, for example to load the enterprise-attack domain:
2018-01-29 16:11:05 -05:00
```python
from stix2 import FileSystemSource
2018-01-29 16:11:05 -05:00
src = FileSystemSource('./cti/enterprise-attack')
2020-05-21 14:35:57 -04:00
```
2018-01-29 16:11:05 -05:00
2022-04-23 14:36:46 -05:00
#### Access via bundle
2020-05-21 14:35:57 -04:00
If you instead prefer to download just the domain bundle, e.g [enterprise-attack.json](/enterprise-attack/enterprise-attack.json), you can still load this using a MemoryStore:
2022-04-23 14:36:46 -05:00
2018-01-29 16:11:05 -05:00
```python
from stix2 import MemoryStore
2020-05-21 14:35:57 -04:00
src = MemoryStore()
2020-06-12 09:39:14 -04:00
src.load_from_file("enterprise-attack.json")
2018-01-29 16:11:05 -05:00
```
2022-04-23 14:36:46 -05:00
### Access live content
Some users may instead prefer to access "live" ATT&CK content over the internet. This is advantageous for several reasons:
2022-04-23 14:36:46 -05:00
2020-05-21 14:35:57 -04:00
- Always stays up to date with the evolving ATT&CK catalog
- Doesn't require an initial download of the ATT&CK content, generally requires less setup
2022-04-23 14:36:46 -05:00
#### Access from the ATT&CK TAXII server
2020-05-21 14:35:57 -04:00
Users can access the ATT&CK data from the official ATT&CK TAXII server. In TAXII, the ATT&CK domains are represented as collections with static IDs:
2018-01-29 16:11:05 -05:00
| domain | collection ID |
|:--------------------|:---------------------------------------|
2020-05-21 14:35:57 -04:00
| `enterprise-attack` | `95ecc380-afe9-11e4-9b6c-751b66dd541e` |
| `mobile-attack` | `2f669986-b40b-4423-b720-4396ca6a462b` |
| `ics-attack` | `02c3ef24-9cd4-48f3-a99f-b74ce24f1d34` |
You can also get a list of available collection from the server directly:
```python
from taxii2client.v20 import Server # only specify v20 if your installed version is >= 2.0.0
server = Server("https://cti-taxii.mitre.org/taxii/")
api_root = server.api_roots[0]
# Print name and ID of all ATT&CK domains available as collections
for collection in api_root.collections:
print(collection.title.ljust(20) + collection.id)
```
2018-01-29 16:11:05 -05:00
2020-05-21 14:35:57 -04:00
The following recipe demonstrates how to access the enterprise-attack data from the TAXII server.
2018-01-29 16:11:05 -05:00
```python
2020-05-21 14:35:57 -04:00
from stix2 import TAXIICollectionSource
from taxii2client.v20 import Collection # only specify v20 if your installed version is >= 2.0.0
2018-01-29 16:11:05 -05:00
2020-05-21 14:35:57 -04:00
collections = {
"enterprise_attack": "95ecc380-afe9-11e4-9b6c-751b66dd541e",
"mobile_attack": "2f669986-b40b-4423-b720-4396ca6a462b",
"ics-attack": "02c3ef24-9cd4-48f3-a99f-b74ce24f1d34"
2020-05-21 14:35:57 -04:00
}
collection = Collection(f"https://cti-taxii.mitre.org/stix/collections/{collections['enterprise_attack']}/")
src = TAXIICollectionSource(collection)
2018-01-29 16:11:05 -05:00
```
2020-06-26 12:28:47 -04:00
For more about TAXII, please see oasis-open's [Introduction to TAXII](https://oasis-open.github.io/cti-documentation/taxii/intro).
2020-06-26 12:07:29 -04:00
2022-04-23 14:36:46 -05:00
#### Access from Github via requests
Users can alternatively access the data from MITRE/CTI using HTTP requests, and load the resulting content into a MemoryStore.
While typically the TAXII method is more desirable for "live" access, this method can be useful if you want to
2020-05-21 14:35:57 -04:00
access data on a branch of the MITRE/CTI repo (the TAXII server only holds the master branch) or in the case of a TAXII server outage.
2018-01-29 16:11:05 -05:00
```python
2020-05-21 14:35:57 -04:00
import requests
from stix2 import MemoryStore
2018-01-29 16:11:05 -05:00
2020-06-12 09:26:17 -04:00
def get_data_from_branch(domain, branch="master"):
"""get the ATT&CK STIX data from MITRE/CTI. Domain should be 'enterprise-attack', 'mobile-attack' or 'ics-attack'. Branch should typically be master."""
2020-05-21 14:35:57 -04:00
stix_json = requests.get(f"https://raw.githubusercontent.com/mitre/cti/{branch}/{domain}/{domain}.json").json()
return MemoryStore(stix_data=stix_json["objects"])
2020-05-21 14:35:57 -04:00
2020-06-12 09:26:17 -04:00
src = get_data_from_branch("enterprise-attack")
```
2022-04-23 14:36:46 -05:00
### Access a specific version of ATT&CK
2020-06-12 09:26:17 -04:00
2022-04-23 14:36:46 -05:00
ATT&CK versions are tracked on the MITRE/CTI repo using [tags](https://github.com/mitre/cti/tags). Tags prefixed with `ATT&CK-v` correspond to ATT&CK versions and tags prefixed with `CAPEC-v` correspond to CAPEC versions. You can find more information about ATT&CK versions on the [versions of ATT&CK page](https://attack.mitre.org/resources/versions/) on the ATT&CK website.
2020-06-12 09:26:17 -04:00
In addition to checking out the repo under the tag for a given version or downloading the STIX from github using your browser, you can also use a variation on the [requests method](#access-from-github-via-requests) to access a particular version of ATT&CK:
2020-06-12 09:26:17 -04:00
```python
import requests
from stix2 import MemoryStore
2020-06-12 09:26:17 -04:00
def get_data_from_version(domain, version):
"""get the ATT&CK STIX data for the given version from MITRE/CTI. Domain should be 'enterprise-attack', 'mobile-attack' or 'ics-attack'."""
2020-06-12 09:26:17 -04:00
stix_json = requests.get(f"https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v{version}/{domain}/{domain}.json").json()
return MemoryStore(stix_data=stix_json["objects"])
2020-06-12 09:26:17 -04:00
src = get_data_from_version("enterprise-attack", "5.2")
2018-01-29 16:11:05 -05:00
```
You can get a list of ATT&CK versions programmatically using the github API:
```python
import requests
import re
refToTag = re.compile(r"ATT&CK-v(.*)")
tags = requests.get("https://api.github.com/repos/mitre/cti/git/refs/tags").json()
versions = list(map(lambda tag: refToTag.search(tag["ref"]).groups()[0] , filter(lambda tag: "ATT&CK-v" in tag["ref"], tags)))
# versions = ["1.0", "2.0", ...]
```
2022-04-23 14:36:46 -05:00
### Access multiple domains simultaneously
Because ATT&CK is stored in multiple domains (as of this writing, enterprise-attack, mobile-attack and ics-attack), the above methodologies will only allow you to work
2020-05-21 14:35:57 -04:00
with a single domain at a time. While oftentimes the hard separation of domains is advantageous, occasionally it is useful to combine
domains into a single DataStore. Use any of the methods above to acquire the individual datastores, and then use the following approach to combine them into
a single CompositeDataSource:
2019-02-21 14:05:31 -05:00
```python
from stix2 import CompositeDataSource
src = CompositeDataSource()
src.add_data_sources([enterprise_attack_src, mobile_attack_src, ics_attack_src])
2019-02-21 14:05:31 -05:00
```
2020-05-21 14:35:57 -04:00
You can then use this CompositeDataSource just as you would the DataSource for an individual domain.
2022-04-23 14:36:46 -05:00
## Python recipes
Below are example python recipes which can be used to work with ATT&CK data. They assume the existence of an object implementing the DataStore API. Any of the methods outlined in the [Accessing ATT&CK data in python](#accessing-attck-data-in-python) section should provide an object implementing this API.
2020-06-26 12:07:29 -04:00
This section utilizes the [stix2 python library](https://github.com/oasis-open/cti-python-stix2). Please refer to the [STIX2 Python API Documentation](https://stix2.readthedocs.io/en/latest/) for more information on how to work with STIX programmatically. See also the section on [Requirements and imports](#requirements-and-imports).
2020-05-21 14:35:57 -04:00
2022-04-23 14:36:46 -05:00
### Getting an object
2020-05-21 14:35:57 -04:00
The recipes in this section address how to query the dataset for a single object.
2022-04-23 14:36:46 -05:00
#### By STIX ID
2020-05-21 14:35:57 -04:00
The following recipe can be used to retrieve an object according to its STIX ID. This is typically the preferred way to retrieve objects when working with ATT&CK data because STIX IDs are guaranteed to be unique.
2018-01-29 16:11:05 -05:00
```python
2020-05-21 14:35:57 -04:00
g0075 = src.get("intrusion-set--f40eb8ce-2a74-4e56-89a1-227021410142")
```
2022-04-23 14:36:46 -05:00
#### By ATT&CK ID
2020-05-21 14:35:57 -04:00
The following recipe can be used to retrieve an object according to its ATT&CK ID:
2018-01-29 16:11:05 -05:00
2020-05-21 14:35:57 -04:00
```python
from stix2 import Filter
g0075 = src.query([ Filter("external_references.external_id", "=", "G0075") ])[0]
2018-01-29 16:11:05 -05:00
```
2023-07-17 09:31:53 -05:00
Note: in prior versions of ATT&CK, mitigations had 1:1 relationships with techniques and shared their technique's ID. Therefore the above method does not work properly for techniques because technique ATT&CK IDs are not truly unique. By specifying the STIX type you're looking for as `attack-pattern` you can avoid this issue.
2018-01-29 16:11:05 -05:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
t1134 = src.query([
Filter("external_references.external_id", "=", "T1134"),
Filter("type", "=", "attack-pattern")
2020-05-21 14:35:57 -04:00
])[0]
2018-01-29 16:11:05 -05:00
```
The old 1:1 mitigations causing this issue are deprecated, so you can also filter them out that way — see [Removing revoked and deprecated objects](#removing-revoked-and-deprecated-objects).
2020-05-21 14:35:57 -04:00
2022-04-23 14:36:46 -05:00
#### By name
2020-05-21 14:35:57 -04:00
The following recipe retrieves an object according to its name:
2018-01-29 16:11:05 -05:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def get_technique_by_name(thesrc, name):
filt = [
2018-01-29 16:11:05 -05:00
Filter('type', '=', 'attack-pattern'),
2020-05-21 14:35:57 -04:00
Filter('name', '=', name)
]
return thesrc.query(filt)
2020-06-12 09:39:14 -04:00
# get the technique titled "System Information Discovery"
2020-05-21 14:35:57 -04:00
get_technique_by_name(src, 'System Information Discovery')
2018-01-29 16:11:05 -05:00
```
2022-04-23 14:36:46 -05:00
#### By alias
2020-05-21 14:35:57 -04:00
The following methodology can be used to find the group corresponding to a given alias:
2018-01-29 16:11:05 -05:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def get_group_by_alias(thesrc, alias):
return thesrc.query([
Filter('type', '=', 'intrusion-set'),
Filter('aliases', '=', alias)
])[0]
get_group_by_alias(src, 'Cozy Bear')
```
2018-01-29 16:11:05 -05:00
2022-04-23 14:36:46 -05:00
### Getting multiple objects
2018-01-29 16:11:05 -05:00
2020-05-21 14:35:57 -04:00
The recipes in this section address how to query the dataset for multiple objects.
2018-01-29 16:11:05 -05:00
⚠ When working with queries to return objects based on a set of characteristics, it is likely that you'll end up with a few objects which are no longer maintained by ATT&CK. These are objects marked as deprecated or revoked. We keep these outdated objects around so that workflows depending on them don't break, but we recommend you avoid using them when possible. Please see the section [Working with deprecated and revoked objects](#working-with-deprecated-and-revoked-objects) for more information.
2018-01-29 16:11:05 -05:00
2022-04-23 14:36:46 -05:00
#### Objects by type
See [The ATT&CK data model](#The-ATTCK-Data-Model) for mappings of ATT&CK type to STIX type.
2018-01-29 16:11:05 -05:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
# use the appropriate STIX type in the query according to the desired ATT&CK type
groups = src.query([ Filter("type", "=", "intrusion-set") ])
2020-08-03 10:13:17 -04:00
```
2022-04-23 14:36:46 -05:00
##### Getting techniques or sub-techniques
2020-08-03 10:13:17 -04:00
ATT&CK Techniques and sub-techniques are both represented as `attack-pattern` objects. Therefore further parsing is necessary to get specifically techniques or sub-techniques.
```python
from stix2 import Filter
def get_techniques_or_subtechniques(thesrc, include="both"):
2020-08-03 10:13:17 -04:00
"""Filter Techniques or Sub-Techniques from ATT&CK Enterprise Domain.
include argument has three options: "techniques", "subtechniques", or "both"
depending on the intended behavior."""
if include == "techniques":
query_results = thesrc.query([
Filter('type', '=', 'attack-pattern'),
Filter('x_mitre_is_subtechnique', '=', False)
2020-08-03 10:13:17 -04:00
])
elif include == "subtechniques":
query_results = thesrc.query([
Filter('type', '=', 'attack-pattern'),
Filter('x_mitre_is_subtechnique', '=', True)
2020-08-03 10:13:17 -04:00
])
elif include == "both":
query_results = thesrc.query([
Filter('type', '=', 'attack-pattern')
2020-08-03 10:13:17 -04:00
])
else:
raise RuntimeError("Unknown option %s!" % include)
return query_results
subtechniques = get_techniques_or_subtechniques(src, "subtechniques")
2020-08-03 13:02:43 -04:00
subtechniques = remove_revoked_deprecated(subtechniques) # see https://github.com/mitre/cti/blob/master/USAGE.md#removing-revoked-and-deprecated-objects
2020-05-21 14:35:57 -04:00
```
2018-01-29 16:11:05 -05:00
2022-04-23 14:36:46 -05:00
##### Getting software
2020-10-14 11:04:32 -04:00
Because software are the union of two STIX types (`tool` and `malware`), the process for accessing software is slightly more complicated.
```python
from itertools import chain
from stix2 import Filter
2020-10-14 11:08:50 -04:00
2020-10-14 11:04:32 -04:00
def get_software(thesrc):
return list(chain.from_iterable(
thesrc.query(f) for f in [
Filter("type", "=", "tool"),
Filter("type", "=", "malware")
]
))
get_software(src)
```
2022-04-23 14:36:46 -05:00
#### Objects by content
2020-05-21 14:35:57 -04:00
Sometimes it may be useful to query objects by the content of their description:
2018-01-29 16:11:05 -05:00
2020-05-21 14:35:57 -04:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def get_techniques_by_content(thesrc, content):
techniques = src.query([ Filter('type', '=', 'attack-pattern') ])
return list(filter(lambda t: content.lower() in t.description.lower(), techniques))
# Get all techniques where the string LSASS appears in the description
get_techniques_by_content(src, 'LSASS')
2018-01-29 16:11:05 -05:00
```
2022-04-23 14:36:46 -05:00
#### Techniques by platform
2020-05-21 14:35:57 -04:00
Techniques are associated with one or more platforms. You can query the techniques
under a specific platform with the following code:
2018-01-29 16:11:05 -05:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def get_techniques_by_platform(thesrc, platform):
return thesrc.query([
2018-01-29 16:11:05 -05:00
Filter('type', '=', 'attack-pattern'),
Filter('x_mitre_platforms', '=', platform)
])
2020-05-21 14:35:57 -04:00
# get techniques in the windows platform
get_techniques_by_platform(src, 'Windows')
2018-01-29 16:11:05 -05:00
```
2022-04-23 14:36:46 -05:00
#### Techniques by tactic
Techniques are related to tactics by their kill_chain_phases property.
2020-05-21 14:35:57 -04:00
The `phase_name` of each kill chain phase corresponds to the `x_mitre_shortname` of a tactic.
2018-01-29 16:11:05 -05:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def get_tactic_techniques(thesrc, tactic):
2018-01-29 16:11:05 -05:00
# double checking the kill chain is MITRE ATT&CK
2020-06-12 09:39:14 -04:00
# note: kill_chain_name is different for other domains:
# - enterprise: "mitre-attack"
# - mobile: "mitre-mobile-attack"
2020-10-22 13:22:44 -04:00
# - ics: "mitre-ics-attack"
return thesrc.query([
Filter('type', '=', 'attack-pattern'),
Filter('kill_chain_phases.phase_name', '=', tactic),
Filter('kill_chain_phases.kill_chain_name', '=', 'mitre-attack'),
])
2018-01-29 16:11:05 -05:00
2020-05-21 14:35:57 -04:00
# use the x_mitre_shortname as argument
get_tactic_techniques(src, 'defense-evasion')
2018-02-28 09:33:39 -05:00
```
2018-11-02 11:43:39 -04:00
2022-04-23 14:36:46 -05:00
#### Tactics by matrix
The tactics are individual objects (`x-mitre-tactic`), and their order in a matrix (`x-mitre-matrix`) is
found within the `tactic_refs` property in a matrix. The order of the tactics in that list matches
2020-05-21 14:35:57 -04:00
the ordering of the tactics in that matrix. The following recipe returns a structured list of tactics within each matrix of the input DataStore.
2019-02-12 09:20:50 -05:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def getTacticsByMatrix(thesrc):
2019-02-12 09:20:50 -05:00
tactics = {}
2020-05-21 14:35:57 -04:00
matrix = thesrc.query([
Filter('type', '=', 'x-mitre-matrix'),
2019-02-12 09:20:50 -05:00
])
2020-05-21 14:35:57 -04:00
2019-02-12 09:20:50 -05:00
for i in range(len(matrix)):
tactics[matrix[i]['name']] = []
for tactic_id in matrix[i]['tactic_refs']:
2020-05-21 14:35:57 -04:00
tactics[matrix[i]['name']].append(thesrc.get(tactic_id))
2019-02-12 09:20:50 -05:00
return tactics
2020-05-21 14:35:57 -04:00
# get tactic layout
getTacticsByMatrix(src)
2019-02-12 09:20:50 -05:00
```
2022-04-23 14:36:46 -05:00
#### Objects created or modified since a given date
Sometimes you may want to get a list of objects which have been created or modified after a certain time.
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def get_created_after(thesrc, timestamp):
filt = [
Filter('created', '>', timestamp)
]
return thesrc.query(filt)
get_created_after(src, "2018-10-01T00:14:20.652Z")
def get_modified_after(thesrc, timestamp):
filt = [
Filter('modified', '>', timestamp)
]
return thesrc.query(filt)
get_modified_after(src, "2018-10-01T00:14:20.652Z")
```
2021-02-22 13:27:54 -05:00
We don't recommend you use this method to detect a change to the contents of the knowledge base. For detecting an update to the overall knowledge base we recommend using requests to [check the list of released versions of ATT&CK](https://github.com/mitre/cti/blob/master/USAGE.md#access-a-specific-version-of-attck).
2022-04-23 14:36:46 -05:00
### Getting related objects
2020-05-21 14:35:57 -04:00
A large part of working with ATT&CK revolves around parsing relationships between objects. It is useful
to track not only the related object but the relationship itself because a description is often
present to contextualize the nature of the relationship. The following recipes demonstrate
some common uses of relationships.
2022-04-23 14:36:46 -05:00
#### Relationships microlibrary
2022-10-25 10:33:19 -04:00
NOTE: The following code is intended to be used with the ATT&CK v12 release which includes Campaign Objects.
The examples are backwards-compatible for previous versions af ATT&CK that omit those objects.
2022-10-25 10:33:19 -04:00
This microlibrary can be used to build a lookup table of stixID to related objects and relationships.
2020-05-21 14:35:57 -04:00
The argument to each accessor function is a STIX2 MemoryStore to build the relationship mappings from.
```python
from pprint import pprint
2020-05-21 14:35:57 -04:00
from stix2 import MemoryStore, Filter
# See section below on "Removing revoked and deprecated objects"
def remove_revoked_deprecated(stix_objects):
"""Remove any revoked or deprecated objects from queries made to the data source"""
# Note we use .get() because the property may not be present in the JSON data. The default is False
# if the property is not set.
return list(
filter(
lambda x: x.get("x_mitre_deprecated", False) is False and x.get("revoked", False) is False,
stix_objects
)
)
2020-05-21 14:35:57 -04:00
def get_related(thesrc, src_type, rel_type, target_type, reverse=False):
2020-05-21 14:35:57 -04:00
"""build relationship mappings
params:
thesrc: MemoryStore to build relationship lookups for
2020-05-21 14:35:57 -04:00
src_type: source type for the relationships, e.g "attack-pattern"
rel_type: relationship type for the relationships, e.g "uses"
target_type: target type for the relationship, e.g "intrusion-set"
reverse: build reverse mapping of target to source
"""
relationships = thesrc.query([
2020-05-21 14:35:57 -04:00
Filter('type', '=', 'relationship'),
Filter('relationship_type', '=', rel_type),
Filter('revoked', '=', False),
])
# See section below on "Removing revoked and deprecated objects"
relationships = remove_revoked_deprecated(relationships)
2020-05-21 14:35:57 -04:00
# stix_id => [ { relationship, related_object_id } for each related object ]
id_to_related = {}
2020-05-21 14:35:57 -04:00
# build the dict
for relationship in relationships:
if src_type in relationship.source_ref and target_type in relationship.target_ref:
2020-05-21 14:35:57 -04:00
if (relationship.source_ref in id_to_related and not reverse) or (relationship.target_ref in id_to_related and reverse):
# append to existing entry
if not reverse:
2020-05-21 14:35:57 -04:00
id_to_related[relationship.source_ref].append({
"relationship": relationship,
"id": relationship.target_ref
})
else:
2020-05-21 14:35:57 -04:00
id_to_related[relationship.target_ref].append({
"relationship": relationship,
2020-05-21 14:35:57 -04:00
"id": relationship.source_ref
})
else:
2020-05-21 14:35:57 -04:00
# create a new entry
if not reverse:
2020-05-21 14:35:57 -04:00
id_to_related[relationship.source_ref] = [{
"relationship": relationship,
2020-05-21 14:35:57 -04:00
"id": relationship.target_ref
}]
else:
id_to_related[relationship.target_ref] = [{
"relationship": relationship,
2020-05-21 14:35:57 -04:00
"id": relationship.source_ref
}]
# all objects of relevant type
if not reverse:
targets = thesrc.query([
2020-05-21 14:35:57 -04:00
Filter('type', '=', target_type),
Filter('revoked', '=', False)
2020-05-21 14:35:57 -04:00
])
else:
targets = thesrc.query([
2020-05-21 14:35:57 -04:00
Filter('type', '=', src_type),
Filter('revoked', '=', False)
2020-05-21 14:35:57 -04:00
])
# build lookup of stixID to stix object
id_to_target = {}
for target in targets:
id_to_target[target.id] = target
# build final output mappings
output = {}
for stix_id in id_to_related:
value = []
for related in id_to_related[stix_id]:
if not related["id"] in id_to_target:
continue # targeting a revoked object
2020-05-21 14:35:57 -04:00
value.append({
"object": id_to_target[related["id"]],
"relationship": related["relationship"]
})
output[stix_id] = value
return output
# software:group
def software_used_by_groups(thesrc):
2022-10-04 14:41:48 -04:00
"""returns group_id => {software, relationship} for each software used by the group and each software used by campaigns attributed to the group."""
2022-10-04 10:37:39 -04:00
# get all software used by groups
tools_used_by_group = get_related(thesrc, "intrusion-set", "uses", "tool")
malware_used_by_group = get_related(thesrc, "intrusion-set", "uses", "malware")
2022-10-04 14:41:48 -04:00
software_used_by_group = {**tools_used_by_group, **malware_used_by_group} # group_id -> [{software, relationship}]
2022-10-04 10:37:39 -04:00
# get groups attributing to campaigns and all software used by campaigns
2022-10-04 14:41:48 -04:00
software_used_by_campaign = get_related(thesrc, "campaign", "uses", "tool")
malware_used_by_campaign = get_related(thesrc, "campaign", "uses", "malware")
2022-10-04 14:41:48 -04:00
for id in malware_used_by_campaign:
if id in software_used_by_campaign:
software_used_by_campaign[id].extend(malware_used_by_campaign[id])
else:
software_used_by_campaign[id] = malware_used_by_campaign[id]
campaigns_attributed_to_group = {
2022-10-04 14:41:48 -04:00
"campaigns": get_related(thesrc, "campaign", "attributed-to", "intrusion-set", reverse=True), # group_id => {campaign, relationship}
"software": software_used_by_campaign # campaign_id => {software, relationship}
}
for group_id in campaigns_attributed_to_group["campaigns"]:
software_used_by_campaigns = []
2022-10-04 10:37:39 -04:00
# check if attributed campaign is using software
for campaign in campaigns_attributed_to_group["campaigns"][group_id]:
campaign_id = campaign["object"]["id"]
if campaign_id in campaigns_attributed_to_group["software"]:
2022-10-04 14:41:48 -04:00
software_used_by_campaigns.extend(campaigns_attributed_to_group["software"][campaign_id])
2022-10-04 10:37:39 -04:00
# update software used by group to include software used by a groups attributed campaign
if group_id in software_used_by_group:
software_used_by_group[group_id].extend(software_used_by_campaigns)
else:
software_used_by_group[group_id] = software_used_by_campaigns
return software_used_by_group
2020-05-21 14:35:57 -04:00
def groups_using_software(thesrc):
2022-10-04 14:41:48 -04:00
"""returns software_id => {group, relationship} for each group using the software and each software used by attributed campaigns."""
2022-10-04 10:37:39 -04:00
# get all groups using software
groups_using_tool = get_related(thesrc, "intrusion-set", "uses", "tool", reverse=True)
groups_using_malware = get_related(thesrc, "intrusion-set", "uses", "malware", reverse=True)
groups_using_software = {**groups_using_tool, **groups_using_malware} # software_id => {group, relationship}
2022-10-04 10:37:39 -04:00
# get campaigns attributed to groups and all campaigns using software
2022-10-04 14:41:48 -04:00
campaigns_using_software = get_related(thesrc, "campaign", "uses", "tool", reverse=True)
campaigns_using_malware = get_related(thesrc, "campaign", "uses", "malware", reverse=True)
2022-10-04 14:41:48 -04:00
for id in campaigns_using_malware:
if id in campaigns_using_software:
campaigns_using_software[id].extend(campaigns_using_malware[id])
else:
campaigns_using_software[id] = campaigns_using_malware[id]
groups_attributing_to_campaigns = {
2022-10-04 14:41:48 -04:00
"campaigns": campaigns_using_software,# software_id => {campaign, relationship}
"groups": get_related(thesrc, "campaign", "attributed-to", "intrusion-set") # campaign_id => {group, relationship}
}
for software_id in groups_attributing_to_campaigns["campaigns"]:
groups_attributed_to_campaigns = []
2022-10-04 10:37:39 -04:00
# check if campaign is attributed to group
for campaign in groups_attributing_to_campaigns["campaigns"][software_id]:
campaign_id = campaign["object"]["id"]
if campaign_id in groups_attributing_to_campaigns["groups"]:
2022-10-04 14:41:48 -04:00
groups_attributed_to_campaigns.extend(groups_attributing_to_campaigns["groups"][campaign_id])
2022-10-04 10:37:39 -04:00
# update groups using software to include software used by a groups attributed campaign
if software_id in groups_using_software:
groups_using_software[software_id].extend(groups_attributed_to_campaigns)
else:
groups_using_software[software_id] = groups_attributed_to_campaigns
return groups_using_software
2020-05-21 14:35:57 -04:00
# software:campaign
def software_used_by_campaigns(thesrc):
"""returns campaign_id => {software, relationship} for each software used by the campaign."""
tools_used_by_campaign = get_related(thesrc, "campaign", "uses", "tool")
malware_used_by_campaign = get_related(thesrc, "campaign", "uses", "malware")
return {**tools_used_by_campaign, **malware_used_by_campaign}
def campaigns_using_software(thesrc):
2022-10-03 16:24:36 -04:00
"""returns software_id => {campaign, relationship} for each campaign using the software."""
campaigns_using_tool = get_related(thesrc, "campaign", "uses", "tool", reverse=True)
campaigns_using_malware = get_related(thesrc, "campaign", "uses", "malware", reverse=True)
return {**campaigns_using_tool, **campaigns_using_malware}
2022-10-03 16:24:36 -04:00
# campaign:group
def groups_attributing_to_campaign(thesrc):
"""returns campaign_id => {group, relationship} for each group attributing to the campaign."""
2022-10-04 14:41:48 -04:00
return get_related(thesrc, "campaign", "attributed-to", "intrusion-set")
def campaigns_attributed_to_group(thesrc):
"""returns group_id => {campaign, relationship} for each campaign attributed to the group."""
2022-10-03 16:24:36 -04:00
return get_related(thesrc, "campaign", "attributed-to", "intrusion-set", reverse=True)
2020-05-21 14:35:57 -04:00
# technique:group
def techniques_used_by_groups(thesrc):
"""returns group_id => {technique, relationship} for each technique used by the group and each
technique used by campaigns attributed to the group."""
2022-10-04 10:37:39 -04:00
# get all techniques used by groups
techniques_used_by_groups = get_related(thesrc, "intrusion-set", "uses", "attack-pattern") # group_id => {technique, relationship}
2022-10-04 10:37:39 -04:00
# get groups attributing to campaigns and all techniques used by campaigns
campaigns_attributed_to_group = {
2022-10-04 14:41:48 -04:00
"campaigns": get_related(thesrc, "campaign", "attributed-to", "intrusion-set", reverse=True), # group_id => {campaign, relationship}
"techniques": get_related(thesrc, "campaign", "uses", "attack-pattern") # campaign_id => {technique, relationship}
}
2022-10-04 10:37:39 -04:00
for group_id in campaigns_attributed_to_group["campaigns"]:
techniques_used_by_campaigns = []
2022-10-04 10:37:39 -04:00
# check if attributed campaign is using technique
for campaign in campaigns_attributed_to_group["campaigns"][group_id]:
campaign_id = campaign["object"]["id"]
if campaign_id in campaigns_attributed_to_group["techniques"]:
2022-10-04 14:41:48 -04:00
techniques_used_by_campaigns.extend(campaigns_attributed_to_group["techniques"][campaign_id])
2022-10-04 10:37:39 -04:00
# update techniques used by groups to include techniques used by a groups attributed campaign
if group_id in techniques_used_by_groups:
techniques_used_by_groups[group_id].extend(techniques_used_by_campaigns)
else:
techniques_used_by_groups[group_id] = techniques_used_by_campaigns
return techniques_used_by_groups
2020-05-21 14:35:57 -04:00
def groups_using_technique(thesrc):
2022-10-04 14:41:48 -04:00
"""returns technique_id => {group, relationship} for each group using the technique and each campaign attributed to groups using the technique."""
2022-10-04 10:37:39 -04:00
# get all groups using techniques
groups_using_techniques = get_related(thesrc, "intrusion-set", "uses", "attack-pattern", reverse=True) # technique_id => {group, relationship}
2022-10-04 10:37:39 -04:00
# get campaigns attributed to groups and all campaigns using techniques
groups_attributing_to_campaigns = {
"campaigns": get_related(thesrc, "campaign", "uses", "attack-pattern", reverse=True), # technique_id => {campaign, relationship}
2022-10-04 14:41:48 -04:00
"groups": get_related(thesrc, "campaign", "attributed-to", "intrusion-set") # campaign_id => {group, relationship}
}
2022-10-04 10:37:39 -04:00
for technique_id in groups_attributing_to_campaigns["campaigns"]:
campaigns_attributed_to_group = []
2022-10-04 10:37:39 -04:00
# check if campaign is attributed to group
2022-10-04 14:41:48 -04:00
for campaign in groups_attributing_to_campaigns["campaigns"][technique_id]:
campaign_id = campaign["object"]["id"]
if campaign_id in groups_attributing_to_campaigns["groups"]:
2022-10-04 14:41:48 -04:00
campaigns_attributed_to_group.extend(groups_attributing_to_campaigns["groups"][campaign_id])
2022-10-04 10:37:39 -04:00
# update groups using techniques to include techniques used by a groups attributed campaign
if technique_id in groups_using_techniques:
groups_using_techniques[technique_id].extend(campaigns_attributed_to_group)
else:
groups_using_techniques[technique_id] = campaigns_attributed_to_group
return groups_using_techniques
2020-05-21 14:35:57 -04:00
# technique:campaign
def techniques_used_by_campaigns(thesrc):
"""returns campaign_id => {technique, relationship} for each technique used by the campaign."""
return get_related(thesrc, "campaign", "uses", "attack-pattern")
def campaigns_using_technique(thesrc):
"""returns technique_id => {campaign, relationship} for each campaign using the technique."""
return get_related(thesrc, "campaign", "uses", "attack-pattern", reverse=True)
2020-05-21 14:35:57 -04:00
# technique:software
def techniques_used_by_software(thesrc):
2020-05-21 14:35:57 -04:00
"""return software_id => {technique, relationship} for each technique used by the software."""
techniques_by_tool = get_related(thesrc, "tool", "uses", "attack-pattern")
techniques_by_malware = get_related(thesrc, "malware", "uses", "attack-pattern")
return {**techniques_by_tool, **techniques_by_malware}
2020-05-21 14:35:57 -04:00
def software_using_technique(thesrc):
2020-05-21 14:35:57 -04:00
"""return technique_id => {software, relationship} for each software using the technique."""
tools_by_technique_id = get_related(thesrc, "tool", "uses", "attack-pattern", reverse=True)
malware_by_technique_id = get_related(thesrc, "malware", "uses", "attack-pattern", reverse=True)
return {**tools_by_technique_id, **malware_by_technique_id}
2020-05-21 14:35:57 -04:00
# technique:mitigation
def mitigation_mitigates_techniques(thesrc):
2020-05-21 14:35:57 -04:00
"""return mitigation_id => {technique, relationship} for each technique mitigated by the mitigation."""
return get_related(thesrc, "course-of-action", "mitigates", "attack-pattern", reverse=False)
2020-05-21 14:35:57 -04:00
def technique_mitigated_by_mitigations(thesrc):
2020-05-21 14:35:57 -04:00
"""return technique_id => {mitigation, relationship} for each mitigation of the technique."""
return get_related(thesrc, "course-of-action", "mitigates", "attack-pattern", reverse=True)
2020-05-21 14:35:57 -04:00
2021-10-06 10:43:54 -04:00
# technique:sub-technique
def subtechniques_of(thesrc):
2020-05-21 14:35:57 -04:00
"""return technique_id => {subtechnique, relationship} for each subtechnique of the technique."""
return get_related(thesrc, "attack-pattern", "subtechnique-of", "attack-pattern", reverse=True)
2020-05-21 14:35:57 -04:00
def parent_technique_of(thesrc):
2020-05-21 14:35:57 -04:00
"""return subtechnique_id => {technique, relationship} describing the parent technique of the subtechnique"""
return get_related(thesrc, "attack-pattern", "subtechnique-of", "attack-pattern")[0]
2021-10-06 10:43:54 -04:00
# technique:data-component
def datacomponent_detects_techniques(thesrc):
"""return datacomponent_id => {technique, relationship} describing the detections of each data component"""
return get_related(thesrc, "x-mitre-data-component", "detects", "attack-pattern")
2021-10-06 10:43:54 -04:00
def technique_detected_by_datacomponents(thesrc):
"""return technique_id => {datacomponent, relationship} describing the data components that can detect the technique"""
return get_related(thesrc, "x-mitre-data-component", "detects", "attack-pattern", reverse=True)
2020-05-21 14:35:57 -04:00
# Example usage:
src = MemoryStore()
src.load_from_file("path/to/enterprise-attack.json")
2020-05-21 14:35:57 -04:00
group_id_to_software = software_used_by_groups(src)
pprint(group_id_to_software["intrusion-set--2a158b0a-7ef8-43cb-9985-bf34d1e12050"]) # G0019
2020-05-21 14:35:57 -04:00
# [
# {
# "object": Malware, # S0061
# "relationship": Relationship # relationship between G0019 and S0061
# },
# {
2020-05-21 14:35:57 -04:00
# ...
# }
# ]
```
2018-11-02 11:43:39 -04:00
2022-04-23 14:36:46 -05:00
#### Getting techniques used by a group's software
2020-05-21 14:35:57 -04:00
Because a group uses software, and software uses techniques, groups can be considered indirect users of techniques used by their software.
These techniques are oftentimes distinct from the techniques used directly by a group, although there are occasionally intersections in these two sets of techniques.
2018-11-02 11:43:39 -04:00
2020-05-21 14:35:57 -04:00
The following recipe can be used to retrieve the techniques used by a group's software:
2018-11-02 11:43:39 -04:00
```python
2020-05-21 14:35:57 -04:00
from stix2.utils import get_type_from_id
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def get_techniques_by_group_software(thesrc, group_stix_id):
2020-05-21 14:35:57 -04:00
# get the malware, tools that the group uses
group_uses = [
r for r in thesrc.relationships(group_stix_id, 'uses', source_only=True)
2020-05-21 14:35:57 -04:00
if get_type_from_id(r.target_ref) in ['malware', 'tool']
]
# get the technique stix ids that the malware, tools use
software_uses = thesrc.query([
2020-05-21 14:35:57 -04:00
Filter('type', '=', 'relationship'),
Filter('relationship_type', '=', 'uses'),
Filter('source_ref', 'in', [r.source_ref for r in group_uses])
])
2018-11-02 11:43:39 -04:00
2020-05-21 14:35:57 -04:00
#get the techniques themselves
return thesrc.query([
2020-05-21 14:35:57 -04:00
Filter('type', '=', 'attack-pattern'),
Filter('id', 'in', [r.target_ref for r in software_uses])
])
2018-11-02 11:43:39 -04:00
get_techniques_by_group_software(src, "intrusion-set--f047ee18-7985-4946-8bfb-4ed754d3a0dd")
2020-05-21 14:35:57 -04:00
```
2022-04-23 14:36:46 -05:00
### Working with deprecated and revoked objects
Objects that are deemed no longer beneficial to track as part of the knowledge base are marked as deprecated, and objects which are replaced by a different object are revoked. In both cases, the old object is marked with a field (either `x_mitre_deprecated` or `revoked`) noting their status. In the case of revoked objects, a relationship of type `revoked-by` is also created targeting the replacing object.
Unlike other objects in the dataset, relationships cannot be revoked or deprecated. Relationships are considered deprecated/revoked if one of the objects it is attached to is revoked or deprecated.
2020-05-21 14:35:57 -04:00
2022-04-23 14:36:46 -05:00
#### Removing revoked and deprecated objects
2020-12-11 11:53:27 -05:00
2022-04-23 14:36:46 -05:00
Revoked and deprecated objects are kept in the knowledge base so that workflows relying on those objects are not
broken. We recommend you filter out revoked and deprecated objects from your views whenever possible since they are no
2020-05-21 14:35:57 -04:00
longer maintained by ATT&CK.
2018-11-02 11:43:39 -04:00
2020-12-11 12:09:32 -05:00
We recommend _not_ using built-in STIX filters for removing revoked objects (e.g `Filter('revoked', '=', False)`). This is because the behavior of this specific filter is inconsistent depending on the method of access (using local data or accessing via the TAXII server). We recommend using the following code example to filter revoked objects instead. See [issue #127](https://github.com/mitre/cti/issues/127) for more details.
2020-05-21 14:35:57 -04:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def remove_revoked_deprecated(stix_objects):
"""Remove any revoked or deprecated objects from queries made to the data source"""
# Note we use .get() because the property may not be present in the JSON data. The default is False
# if the property is not set.
return list(
filter(
lambda x: x.get("x_mitre_deprecated", False) is False and x.get("revoked", False) is False,
stix_objects
)
)
mitigations = src.query([ Filter("type", "=", "course-of-action") ])
2020-06-12 09:26:17 -04:00
mitigations = remove_revoked_deprecated(mitigations)
2020-05-21 14:35:57 -04:00
```
2022-04-23 14:36:46 -05:00
#### Getting a revoking object
2020-05-21 14:35:57 -04:00
2020-06-26 12:07:29 -04:00
When an object is replaced by another object, it is marked with the field `revoked` and a relationship of type `revoked-by` is created where the `source_ref` is the revoked object and the `target_ref` is the revoking object. This relationship can be followed to find the replacing object:
2020-05-21 14:35:57 -04:00
```python
from stix2 import Filter
2020-05-21 14:35:57 -04:00
def getRevokedBy(stix_id, thesrc):
relations = thesrc.relationships(stix_id, 'revoked-by', source_only=True)
revoked_by = thesrc.query([
Filter('id', 'in', [r.target_ref for r in relations]),
Filter('revoked', '=', False)
2020-05-21 14:35:57 -04:00
])
if revoked_by is not None:
revoked_by = revoked_by[0]
return revoked_by
2018-11-02 11:43:39 -04:00
2020-05-21 14:35:57 -04:00
getRevokedBy("attack-pattern--c16e5409-ee53-4d79-afdc-4099dc9292df", src)
2018-11-02 11:43:39 -04:00
```