Skip to content

Commit e5bb2b3

Browse files
authored
feat: add functionality to download specific version of ATT&CK
1 parent e03c53a commit e5bb2b3

1 file changed

Lines changed: 101 additions & 5 deletions

File tree

mitreattack/download_stix.py

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ def download_stix(stix_version: str, domain: str, download_dir: str, release: st
4040
pooch.retrieve(download_url, known_hash=known_hash, fname=fname, path=str(release_download_dir))
4141

4242

43-
def download_domains(domains: List[str], download_dir: str, all_versions: bool, stix_version: str):
43+
def download_domains(
44+
domains: List[str], download_dir: str, all_versions: bool, stix_version: str, specific_versions: List[str] = None
45+
):
4446
"""Download ATT&CK domains specified.
4547
4648
Parameters
@@ -53,6 +55,8 @@ def download_domains(domains: List[str], download_dir: str, all_versions: bool,
5355
Whether or not to download all versions of the domains.
5456
stix_version : str
5557
Version of STIX to download. Options are "2.0" or "2.1"
58+
specific_versions : List[str], optional
59+
List of specific versions to download. If provided, overrides all_versions behavior.
5660
"""
5761
for domain in domains:
5862
if domain == "pre" and stix_version == "2.1":
@@ -75,7 +79,22 @@ def download_domains(domains: List[str], download_dir: str, all_versions: bool,
7579
if stix_version == "2.0":
7680
releases = stix_hash_data["pre"]
7781

78-
if all_versions:
82+
if specific_versions:
83+
# Download specific versions
84+
logger.info(f"Downloading STIX {stix_version} specific versions for the {domain} domain to {download_dir}")
85+
for version in specific_versions:
86+
if version in releases:
87+
known_hash = releases[version]
88+
download_stix(
89+
stix_version=stix_version,
90+
domain=domain,
91+
download_dir=download_dir,
92+
release=version,
93+
known_hash=known_hash,
94+
)
95+
else:
96+
logger.warning(f"Version {version} not available for {domain} domain in STIX {stix_version}")
97+
elif all_versions:
7998
logger.info(f"Downloading STIX {stix_version} bundles for the {domain} domain to {download_dir}")
8099
for release, known_hash in releases.items():
81100
download_stix(
@@ -99,31 +118,108 @@ def download_domains(domains: List[str], download_dir: str, all_versions: bool,
99118
)
100119

101120

121+
def _validate_versions(versions: List[str], stix20: bool, stix21: bool):
122+
"""Validate that specified versions exist in the available data.
123+
124+
Parameters
125+
----------
126+
versions : List[str]
127+
List of versions to validate
128+
stix20 : bool
129+
Whether STIX 2.0 is being downloaded
130+
stix21 : bool
131+
Whether STIX 2.1 is being downloaded
132+
"""
133+
domains = ["enterprise", "mobile", "ics", "pre"]
134+
invalid_versions = []
135+
136+
for version in versions:
137+
version_exists = False
138+
139+
# Check STIX 2.0 if enabled
140+
if stix20:
141+
for domain in domains:
142+
if domain == "pre":
143+
domain_releases = release_info.STIX20.get("pre", {})
144+
else:
145+
domain_releases = release_info.STIX20.get(domain, {})
146+
147+
if version in domain_releases:
148+
version_exists = True
149+
break
150+
151+
# Check STIX 2.1 if enabled
152+
if stix21 and not version_exists:
153+
for domain in domains:
154+
if domain == "pre":
155+
# PRE domain not available in STIX 2.1
156+
continue
157+
158+
domain_releases = release_info.STIX21.get(domain, {})
159+
if version in domain_releases:
160+
version_exists = True
161+
break
162+
163+
if not version_exists:
164+
invalid_versions.append(version)
165+
166+
if invalid_versions:
167+
logger.error(f"Invalid version(s): {', '.join(invalid_versions)}")
168+
logger.info(f"Latest available version: {release_info.LATEST_VERSION}")
169+
raise typer.Exit(code=1)
170+
171+
102172
@app.command()
103173
def download_attack_stix(
104174
download_dir: str = typer.Option(
105175
"attack-releases", "--download-dir", "-d", help="Folder to save downloaded STIX data."
106176
),
107-
all_versions: bool = typer.Option(False, "--all", "-a", help="Download all ATT&CK releases."),
177+
all_versions: bool = typer.Option(
178+
False, "--all", "-a", help="Download all ATT&CK releases. Mutually exclusive with --version."
179+
),
180+
versions: List[str] = typer.Option(
181+
None,
182+
"--version",
183+
"-v",
184+
help="Download specific ATT&CK version(s). Can be specified multiple times. Mutually exclusive with --all.",
185+
),
108186
stix20: bool = typer.Option(True, help="Download STIX 2.0 data."),
109187
stix21: bool = typer.Option(False, help="Download STIX 2.1 data."),
110188
):
111189
"""Download the ATT&CK STIX data from GitHub in JSON format.
112190
113191
By default, only the latest ATT&CK release will be downloaded in STIX 2.0 format.
192+
Use --version to specify particular versions, or --all to download all versions.
114193
"""
194+
# Validate mutually exclusive options
195+
if all_versions and versions:
196+
logger.error("Cannot specify both --all and --version. Use one or the other.")
197+
raise typer.Exit(code=1)
198+
199+
# Validate specified versions exist
200+
if versions:
201+
_validate_versions(versions, stix20, stix21)
202+
115203
domains = ["enterprise", "mobile", "ics", "pre"]
116204

117205
if stix20:
118206
stix20_download_dir = f"{download_dir}/stix-2.0"
119207
pathlib.Path(stix20_download_dir).mkdir(parents=True, exist_ok=True)
120208
download_domains(
121-
domains=domains, download_dir=stix20_download_dir, all_versions=all_versions, stix_version="2.0"
209+
domains=domains,
210+
download_dir=stix20_download_dir,
211+
all_versions=all_versions,
212+
stix_version="2.0",
213+
specific_versions=versions,
122214
)
123215

124216
if stix21:
125217
stix21_download_dir = f"{download_dir}/stix-2.1"
126218
pathlib.Path(stix21_download_dir).mkdir(parents=True, exist_ok=True)
127219
download_domains(
128-
domains=domains, download_dir=stix21_download_dir, all_versions=all_versions, stix_version="2.1"
220+
domains=domains,
221+
download_dir=stix21_download_dir,
222+
all_versions=all_versions,
223+
stix_version="2.1",
224+
specific_versions=versions,
129225
)

0 commit comments

Comments
 (0)