Skip to content

Commit 3f4e9d1

Browse files
committed
Add ability to export Excel without downloading from GitHub
1 parent aa9edb6 commit 3f4e9d1

File tree

3 files changed

+107
-31
lines changed

3 files changed

+107
-31
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# v1.5.4 4/23/2022
2+
3+
## Improvements
4+
5+
- Allow Excel to be exported from local STIX file without needint to download from GitHub
6+
17
# v1.5.3 4/15/2022
28

39
## Fixes

mitreattack/attackToExcel/attackToExcel.py

Lines changed: 100 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import argparse
22
import os
3+
from typing import Dict, List
34

45
import pandas as pd
56
import requests
7+
from loguru import logger
68
from stix2 import MemoryStore
79

810
# import mitreattack.attackToExcel.stixToDf as stixToDf
@@ -12,15 +14,24 @@
1214
SUB_CHARACTERS = ["\\", "/"]
1315

1416

15-
def get_stix_data(domain, version=None, remote=None):
17+
def get_stix_data(domain: str, version: str = None, remote: str = None) -> MemoryStore:
1618
"""Download the ATT&CK STIX data for the given domain and version from MITRE/CTI (or just domain if a remote workbench is specified).
1719
18-
:param domain: the domain of ATT&CK to fetch data from, e.g "enterprise-attack"
19-
:param version: the version of attack to fetch data from, e.g "v8.1". If omitted, returns the latest version
20-
(not used for invocations that use remote)
21-
:param remote: optional url to a ATT&CK workbench instance. If specified, data will be retrieved from the target
22-
Workbench instead of MITRE/CTI
23-
:returns: a MemoryStore containing the domain data
20+
Parameters
21+
----------
22+
domain : str
23+
The domain of ATT&CK to fetch data from, e.g "enterprise-attack"
24+
version : str, optional
25+
The version of attack to fetch data from, e.g "v8.1".
26+
If omitted, returns the latest version (not used for invocations that use remote), by default None
27+
remote : str, optional
28+
Optional url to a ATT&CK workbench instance.
29+
If specified, data will be retrieved from the target Workbench instead of MITRE/CTI, by default None
30+
31+
Returns
32+
-------
33+
MemoryStore
34+
A stix2.MemoryStore object containing the domain data
2435
"""
2536
if remote: # Using Workbench Instance
2637
if ":" not in remote[6:]:
@@ -29,23 +40,35 @@ def get_stix_data(domain, version=None, remote=None):
2940
remote = "http://" + remote
3041
url = f"{remote}/api/stix-bundles?domain={domain}&includeRevoked=true&includeDeprecated=true"
3142
stix_json = requests.get(url).json()
32-
return MemoryStore(stix_json)
43+
ms = MemoryStore(stix_json)
3344
else: # Using MITRE/CTI
3445
if version:
3546
url = f"https://raw.githubusercontent.com/mitre/cti/ATT%26CK-{version}/{domain}/{domain}.json"
3647
else:
3748
url = f"https://raw.githubusercontent.com/mitre/cti/master/{domain}/{domain}.json"
3849

3950
stix_json = requests.get(url).json()
40-
return MemoryStore(stix_data=stix_json["objects"])
51+
ms = MemoryStore(stix_data=stix_json["objects"])
52+
53+
return ms
4154

4255

43-
def build_dataframes(src, domain):
56+
def build_dataframes(src: MemoryStore, domain: str) -> Dict:
4457
"""Build pandas dataframes for each attack type, and return a dictionary lookup for each type to the relevant dataframe.
4558
46-
:param src: MemoryStore or other stix2 DataSource object
47-
:param domain: domain of ATT&CK src corresponds to, e.g "enterprise-attack"
48-
:returns: a dict lookup of each ATT&CK type to dataframes for the given type to be ingested by write_excel
59+
:returns:
60+
61+
Parameters
62+
----------
63+
src : MemoryStore
64+
MemoryStore or other stix2 DataSource object
65+
domain : str
66+
domain of ATT&CK src corresponds to, e.g "enterprise-attack"
67+
68+
Returns
69+
-------
70+
dict
71+
A dict lookup of each ATT&CK type to dataframes for the given type to be ingested by write_excel
4972
"""
5073
df = {
5174
"techniques": stixToDf.techniquesToDf(src, domain),
@@ -62,16 +85,26 @@ def build_dataframes(src, domain):
6285
return df
6386

6487

65-
def write_excel(dataframes, domain, version=None, outputDir="."):
88+
def write_excel(dataframes: Dict, domain: str, version: str = None, outputDir: str = ".") -> List:
6689
"""Given a set of dataframes from build_dataframes, write the ATT&CK dataset to output directory.
6790
68-
:param dataframes: pandas dataframes as built by build_dataframes
69-
:param domain: domain of ATT&CK the dataframes correspond to, e.g "enterprise-attack"
70-
:param version: optional, the version of ATT&CK the dataframes correspond to, e.g "v8.1".
71-
If omitted, the output files will not be labelled with the version number
72-
:param outputDir: optional, the directory to write the excel files to. If omitted writes to a
73-
subfolder of the current directory depending on specified domain and version
74-
:returns: a list of filepaths corresponding to the files written by the function
91+
Parameters
92+
----------
93+
dataframes : dict
94+
A dictionary of pandas dataframes as built by build_dataframes()
95+
domain : str
96+
Domain of ATT&CK the dataframes correspond to, e.g "enterprise-attack"
97+
version : str, optional
98+
The version of ATT&CK the dataframes correspond to, e.g "v8.1".
99+
If omitted, the output files will not be labelled with the version number, by default None
100+
outputDir : str, optional
101+
The directory to write the excel files to.
102+
If omitted writes to a subfolder of the current directory depending on specified domain and version, by default "."
103+
104+
Returns
105+
-------
106+
list
107+
A list of filepaths corresponding to the files written by the function
75108
"""
76109
print("writing formatted files... ", end="", flush=True)
77110
# master list of files that have been written
@@ -184,18 +217,55 @@ def write_excel(dataframes, domain, version=None, outputDir="."):
184217
return written_files
185218

186219

187-
def export(domain="enterprise-attack", version=None, outputDir=".", remote=None):
188-
"""Download ATT&CK data from MITRE/CTI and convert it to excel spreadsheets.
220+
def export(
221+
domain: str = "enterprise-attack",
222+
version: str = None,
223+
outputDir: str = ".",
224+
remote: str = None,
225+
stix_file: str = None,
226+
):
227+
"""Download ATT&CK data from MITRE/CTI and convert it to Excel spreadsheets.
189228
190-
:param domain: the domain of ATT&CK to download, e.g "enterprise-attack"
191-
:param version: optional, the version of ATT&CK to download, e.g "v8.1". If omitted will build the current version
192-
of ATT&CK
193-
:param outputDir: optional, the directory to write the excel files to. If omitted writes to a
194-
subfolder of the current directory depending on specified domain and version
195-
:param remote: optional, the URL of a remote ATT&CK Workbench instance to connect to for stix data
229+
Parameters
230+
----------
231+
domain : str, optional
232+
The domain of ATT&CK to download, e.g "enterprise-attack", by default "enterprise-attack"
233+
version : str, optional
234+
The version of ATT&CK to download, e.g "v8.1".
235+
If omitted will build the current version of ATT&CK, by default None
236+
outputDir : str, optional
237+
The directory to write the excel files to.
238+
If omitted writes to a subfolder of the current directory depending on specified domain and version, by default "."
239+
remote : str, optional
240+
The URL of a remote ATT&CK Workbench instance to connect to for stix data.
241+
Mutually exclusive with stix_file.
242+
by default None
243+
stix_file : str, optional
244+
Path to a local STIX file containing ATT&CK data for a domain, by default None
245+
246+
Raises
247+
------
248+
ValueError
249+
Raised if both `remote` and `stix_file` are passed
250+
FileNotFoundError
251+
Raised if `stix_file` not found
196252
"""
253+
if remote and stix_file:
254+
raise ValueError("remote and stix_file are mutually exclusive. Please only use one or the other")
255+
256+
mem_store = None
257+
if remote:
258+
mem_store = get_stix_data(domain, version, remote)
259+
elif stix_file:
260+
if os.path.exists(stix_file):
261+
logger.info(f"Loading STIX file from: {stix_file}")
262+
mem_store = MemoryStore()
263+
mem_store.load_from_file(stix_file)
264+
else:
265+
raise FileNotFoundError(f"{stix_file} file does not exist.")
266+
197267
# build dataframes
198-
dataframes = build_dataframes(get_stix_data(domain, version, remote), domain)
268+
dataframes = build_dataframes(src=mem_store, domain=domain)
199269
write_excel(dataframes, domain, version, outputDir)
200270

201271

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="mitreattack-python",
8-
version="1.5.3",
8+
version="1.5.4",
99
author="MITRE ATT&CK, MITRE Corporation",
1010
author_email="attack@mitre.org",
1111
description="MITRE ATT&CK python library",

0 commit comments

Comments
 (0)