Skip to content

Commit 0a9a2d9

Browse files
authored
Merge pull request #311 from mitre-attack/feature/#310-verify-domains-tactics-techniques-mitigations
Feature/#310 and #314
2 parents 07c3254 + f937422 commit 0a9a2d9

File tree

9 files changed

+89
-37
lines changed

9 files changed

+89
-37
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
### Improvements
3030
- Added support for deprecated relationships, software and groups. Deprecated relationships, software and groups will not appear on the website UI but can be added to STIX bundles. See issue [#302](https://github.com/mitre-attack/attack-website/issues/302) and [#305](https://github.com/mitre-attack/attack-website/issues/305).
3131
- Added support for input data with more than one object with the same STIX or ATT&CK ID which can occur if there are multiple versions of the object present in the data. Website will display the most recently modified object depending on the deprecation status. See issue [#304](https://github.com/mitre-attack/attack-website/issues/304).
32+
- Added domain checks for tactics, software, and mitigations objects. See issue [#310](https://github.com/mitre-attack/attack-website/issues/310).
33+
- Sorted sub-techniques by ATT&CK ID on Techniques Used tables. See issue [#314](https://github.com/mitre-attack/attack-website/issues/314).
3234

3335
# 20 May 2021
3436
## ATT&CK website version 3.2.2

modules/attack_redirections/attack_redirections.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def generate_markdown_files(domain):
3030

3131
ms = util.relationshipgetters.get_ms()
3232

33-
for key in attack_redirections_config.general_redirects_dict:
34-
objs = util.stixhelpers.get_all_of_type(ms[domain], key)
33+
for types in attack_redirections_config.general_redirects_types_dict:
34+
objs = util.stixhelpers.get_all_of_type(ms[domain], types)
3535
for obj in objs:
3636
new_attack_id, old_attack_id = get_new_and_old_ids(obj)
3737

@@ -44,29 +44,29 @@ def generate_markdown_files(domain):
4444
revoked_attack_id = util.buildhelpers.get_attack_id(revoked_by_obj)
4545

4646
if revoked_attack_id:
47-
generate_obj_redirect(attack_redirections_config.general_redirects_dict[key], revoked_attack_id, old_attack_id, domain)
47+
generate_obj_redirect(attack_redirections_config.general_redirects_dict[types[0]], revoked_attack_id, old_attack_id, domain)
4848

4949
if old_attack_id != new_attack_id:
50-
generate_obj_redirect(attack_redirections_config.general_redirects_dict[key], revoked_attack_id, new_attack_id, domain)
50+
generate_obj_redirect(attack_redirections_config.general_redirects_dict[types[0]], revoked_attack_id, new_attack_id, domain)
5151
else:
52-
generate_obj_redirect(attack_redirections_config.general_redirects_dict[key], new_attack_id, old_attack_id, domain)
52+
generate_obj_redirect(attack_redirections_config.general_redirects_dict[types[0]], new_attack_id, old_attack_id, domain)
5353

5454
if domain == "mobile-attack":
55-
for key in attack_redirections_config.mobile_redirect_dict:
56-
objs = util.stixhelpers.get_all_of_type(ms[domain], key)
55+
for types in attack_redirections_config.mobile_redirect_types_dict:
56+
objs = util.stixhelpers.get_all_of_type(ms[domain], types)
5757
for obj in objs:
5858
new_attack_id, old_attack_id = get_new_and_old_ids(obj)
5959

6060
if new_attack_id:
61-
generate_obj_redirect(attack_redirections_config.mobile_redirect_dict[key], new_attack_id, old_attack_id, domain)
61+
generate_obj_redirect(attack_redirections_config.mobile_redirect_dict[types[0]], new_attack_id, old_attack_id, domain)
6262

6363
generate_tactic_redirects(ms, domain)
6464

6565

6666
def generate_tactic_redirects(ms, domain):
6767
"""Responsible for generating tactic redirects markdown"""
6868

69-
tactics = util.stixhelpers.get_all_of_type(ms[domain], 'x-mitre-tactic')
69+
tactics = util.stixhelpers.get_all_of_type(ms[domain], ['x-mitre-tactic'])
7070

7171
data = {}
7272
for tactic in tactics:

modules/attack_redirections/attack_redirections_config.py

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
"intrusion-set": {"old": "Group", "new": "groups"}
1313
}
1414

15+
general_redirects_types_dict = [
16+
["attack-pattern"],
17+
["malware", "tool"],
18+
["intrusion-set"]
19+
]
20+
1521
# Mobile redirects
1622
mobile_redirect_dict = {
1723
"course-of-action": {
@@ -20,6 +26,10 @@
2026
}
2127
}
2228

29+
mobile_redirect_types_dict = [
30+
["course-of-action"]
31+
]
32+
2333
# File paths dictionary
2434
redirects_paths = {
2535
'enterprise-attack': "wiki/",

modules/matrices/matrices.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def generate_deprecated_matrix(matrix, side_menu_data=None):
9292
# memorystore for the current domain
9393
domain_ms = ms[matrix['matrix']]
9494

95-
sub_matrices = util.stixhelpers.get_matrices(domain_ms)
95+
sub_matrices = util.stixhelpers.get_matrices(domain_ms, matrix['matrix'])
9696
data['descr'] = ""
9797
for sub in sub_matrices:
9898
if sub.get('description'):
@@ -121,13 +121,13 @@ def get_sub_matrices(matrix):
121121
# memorystore for the current domain
122122
domain_ms = ms[matrix['matrix']]
123123
# get relevant techniques
124-
techniques = util.stixhelpers.get_techniques(domain_ms)
124+
techniques = util.stixhelpers.get_techniques(domain_ms, matrix['matrix'])
125125
platform_techniques = util.buildhelpers.filter_techniques_by_platform(techniques, matrix['platforms'])
126126
platform_techniques = util.buildhelpers.filter_out_subtechniques(platform_techniques)
127127
# remove revoked
128128
platform_techniques = util.buildhelpers.filter_deprecated_revoked(platform_techniques)
129129
# get relevant tactics
130-
all_tactics = util.stixhelpers.get_all_of_type(domain_ms, "x-mitre-tactic")
130+
all_tactics = util.stixhelpers.get_all_of_type(domain_ms, ["x-mitre-tactic"])
131131
tactic_id_to_shortname = { tactic["id"]: tactic["x_mitre_shortname"] for tactic in all_tactics }
132132

133133
has_subtechniques = False #track whether the current matrix has subtechniques
@@ -217,7 +217,7 @@ def transform_tactic(tactic_id):
217217
return obj
218218

219219
data = []
220-
sub_matrices = util.stixhelpers.get_matrices(domain_ms)
220+
sub_matrices = util.stixhelpers.get_matrices(domain_ms, matrix['matrix'])
221221
for sub_matrix in sub_matrices:
222222
# find last modified date
223223
matrix_dates = util.buildhelpers.get_created_and_modified_dates(sub_matrix)

modules/tactics/tactics.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def generate_tactics():
3939

4040
for domain in site_config.domains:
4141
#Reads the STIX and creates a list of the ATT&CK Techniques
42-
techniques_no_sub[domain['name']] = util.buildhelpers.filter_out_subtechniques(util.stixhelpers.get_techniques(ms[domain['name']]))
43-
tactics[domain['name']] = util.stixhelpers.get_tactic_list(ms[domain['name']])
42+
techniques_no_sub[domain['name']] = util.buildhelpers.filter_out_subtechniques(util.stixhelpers.get_techniques(ms[domain['name']], domain['name']))
43+
tactics[domain['name']] = util.stixhelpers.get_tactic_list(ms[domain['name']], domain['name'])
4444

4545
side_nav_data = util.buildhelpers.get_side_nav_domains_data("tactics", tactics)
4646

modules/techniques/techniques.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ def generate_techniques():
4343

4444
for domain in site_config.domains:
4545
#Reads the STIX and creates a list of the ATT&CK Techniques
46-
techniques_no_sub[domain['name']] = util.buildhelpers.filter_out_subtechniques(util.stixhelpers.get_techniques(ms[domain['name']]))
47-
tactics[domain['name']] = util.stixhelpers.get_tactic_list(ms[domain['name']])
46+
techniques_no_sub[domain['name']] = util.buildhelpers.filter_out_subtechniques(util.stixhelpers.get_techniques(ms[domain['name']], domain['name']))
47+
tactics[domain['name']] = util.stixhelpers.get_tactic_list(ms[domain['name']], domain['name'])
4848

4949
side_nav_data = get_technique_side_nav_data(techniques_no_sub, tactics)
5050

@@ -488,6 +488,10 @@ def get_technique_side_nav_data(techniques, tactics):
488488
child['children'] = []
489489
technique_row['children'].append(child)
490490

491+
# Sort subtechniques by ATT&CK ID
492+
if technique_row['children']:
493+
technique_row['children'] = sorted(technique_row['children'], key=lambda k: k['id'])
494+
491495
# Add technique data to tactic
492496
tactic_row['children'].append(technique_row)
493497
# Add tactic to domain

modules/tour/tour.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def get_tour_steps(matrix):
7272
return {}
7373

7474
# Reads the STIX and creates a list of the techniques
75-
techniques = util.stixhelpers.get_techniques(ms)
75+
techniques = util.stixhelpers.get_techniques(ms, matrix['matrix'])
7676

7777
techs_no_subtechs = util.buildhelpers.filter_out_subtechniques(techniques)
7878
techs_with_subtechs = util.buildhelpers.filter_out_techniques_without_subtechniques(techniques)
@@ -89,6 +89,10 @@ def get_tour_steps(matrix):
8989
# Find technique with sub-techniques
9090
technique = get_technique_with_subtechniques(techs_no_subtechs)
9191

92+
if not technique:
93+
# Did not find technique with sub-technique
94+
return steps
95+
9296
# Get technique ID and store that as the step
9397
steps['technique'] = "techniques/{}".format(util.buildhelpers.get_attack_id(technique))
9498
subtechnique_attack_id = get_subtech_n_of_technique(technique)
@@ -178,6 +182,7 @@ def get_technique_with_subtechniques(techs_no_subtechs):
178182
Return technique with the most sub-techniques if not the case
179183
"""
180184

185+
subtech_count_min = 4
181186
counter = 0
182187
chosen_tech = {}
183188

@@ -191,7 +196,7 @@ def get_technique_with_subtechniques(techs_no_subtechs):
191196
# Check if sub-technique count is bigger than counter
192197
if counter < subtech_count:
193198
# Quick return if found
194-
if subtech_count > 3: return tech
199+
if subtech_count >= subtech_count_min: return tech
195200
# Set counter and new techique
196201
counter = subtech_count
197202
chosen_tech = tech

modules/util/buildhelpers.py

+2
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ def technique_used_helper(technique_list, technique, reference_list):
509509
technique_list[parent_id] = parent_technique_used_helper(parent_id)
510510

511511
technique_list[parent_id]['subtechniques'].append(get_technique_data_helper(attack_id, technique, reference_list))
512+
# Sort subtechniques by name
513+
technique_list[parent_id]['subtechniques'] = sorted(technique_list[parent_id]['subtechniques'], key=lambda k: k['id'])
512514

513515
# Attack id is regular technique
514516
else:

modules/util/stixhelpers.py

+47-18
Original file line numberDiff line numberDiff line change
@@ -20,49 +20,72 @@ def get_mitigation_list(src):
2020

2121
return sorted(mitigations, key=lambda k: k['name'].lower())
2222

23-
def get_matrices(src):
23+
def get_matrices(src, domain):
2424
"""Reads the STIX and returns a list of all matrices in the STIX"""
2525

2626
matrices = src.query([
2727
stix2.Filter('type', '=', 'x-mitre-matrix'),
2828
])
2929

30+
# Filter out by domain
31+
matrices = [x for x in matrices if not hasattr(x, 'x_mitre_domains') or domain in x.get('x_mitre_domains')]
32+
3033
return matrices
3134

32-
def get_tactic_list(src, matrix_id=None):
35+
def get_tactic_list(src, domain, matrix_id=None):
3336
"""Reads the STIX and returns a list of all tactics in the STIX"""
3437

3538
tactics = []
36-
matrix = src.query([
39+
matrices = src.query([
3740
stix2.Filter('type', '=', 'x-mitre-matrix'),
3841
])
3942

43+
matrices = sorted(matrices, key=lambda k: len(k['tactic_refs']), reverse=True)
44+
4045
if matrix_id:
41-
for curr_matrix in matrix:
46+
for curr_matrix in matrices:
4247
if curr_matrix['id'] == matrix_id:
4348
for tactic_id in curr_matrix['tactic_refs']:
4449
tactics.append(src.query([stix2.Filter('id', '=', tactic_id)])[0])
4550
else:
46-
for i in range(len(matrix)):
47-
for tactic_id in matrix[i]['tactic_refs']:
48-
tactics.append(src.query([stix2.Filter('id', '=', tactic_id)])[0])
51+
for matrix in matrices:
52+
for tactic_id in matrix['tactic_refs']:
53+
tactics.append(src.query([stix2.Filter('id', '=', tactic_id)])[0])
54+
55+
# Filter out by domain
56+
tactics = [x for x in tactics if not hasattr(x, 'x_mitre_domains') or domain in x.get('x_mitre_domains')]
4957

5058
return tactics
5159

52-
def get_all_of_type(src, obj_type):
60+
def get_all_of_type(src, types):
5361
"""Reads the STIX and returns a list of all of a particular
54-
type of object in the STIX
62+
type of object in the STIX, removes duplicate STIX and ATT&CK IDs
5563
"""
5664

57-
return src.query([stix2.Filter('type', '=', obj_type)])
65+
def grab_filtered_list_by_type(stix_type):
66+
return src.query([stix2.Filter('type', '=', stix_type)])
67+
68+
stix_objs = {}
69+
attack_id_objs = {}
5870

59-
def get_techniques(src):
60-
"""Reads the STIX and returns a list of all techniques in the STIX"""
71+
for stix_type in types:
72+
result = grab_filtered_list_by_type(stix_type)
73+
for obj in result:
74+
add_replace_or_ignore(stix_objs, attack_id_objs, obj)
75+
76+
return [attack_id_objs[key] for key in attack_id_objs]
77+
78+
def get_techniques(src, domain):
79+
"""Reads the STIX and returns a list of all techniques in the STIX
80+
by given domain
81+
"""
6182

6283
tech_list = src.query([
6384
stix2.Filter('type', '=', 'attack-pattern'),
6485
stix2.Filter('revoked', '=', False)
6586
])
87+
# Filter out by domain
88+
tech_list = [x for x in tech_list if not hasattr(x, 'x_mitre_domains') or domain in x.get('x_mitre_domains')]
6689

6790
tech_list = sorted(tech_list, key=lambda k: k['name'].lower())
6891
return tech_list
@@ -130,8 +153,14 @@ def get_technique_id_domain_map(ms):
130153
for val in curr_list:
131154
technique_id = buildhelpers.get_attack_id(val)
132155
if technique_id:
133-
tech_list[technique_id] = domain['name']
134-
156+
if val.get('x_mitre_domains'):
157+
if domain['name'] in val['x_mitre_domains']:
158+
if tech_list.get(technique_id) and domain['name'] not in tech_list[technique_id]:
159+
tech_list[technique_id].append(domain['name'])
160+
else:
161+
tech_list[technique_id] = domain['name']
162+
else:
163+
tech_list[technique_id] = domain['name']
135164
return tech_list
136165

137166
def add_replace_or_ignore(stix_objs, attack_id_objs, obj_in_question):
@@ -144,7 +173,7 @@ def add_replace_or_ignore(stix_objs, attack_id_objs, obj_in_question):
144173

145174
def has_STIX_ATTACK_ID_conflict(attack_id):
146175
# Check if STIX ID has been seen before, if it has, return ATT&CK ID of conflict ATT&CK if ATT&CK IDs are different
147-
conflict = stix_objs.get(obj_in_question.id)
176+
conflict = stix_objs.get(obj_in_question.get('id'))
148177
if conflict:
149178
conflict_attack_id = buildhelpers.get_attack_id(conflict)
150179
if conflict_attack_id != attack_id and attack_id_objs.get(conflict_attack_id):
@@ -162,7 +191,7 @@ def replace_object(attack_id, conflict_attack_id):
162191
else:
163192
attack_id_objs[attack_id] = obj_in_question
164193

165-
stix_objs[obj_in_question.id] = obj_in_question
194+
stix_objs[obj_in_question.get('id')] = obj_in_question
166195

167196
# Get ATT&CK ID
168197
attack_id = buildhelpers.get_attack_id(obj_in_question)
@@ -176,7 +205,7 @@ def replace_object(attack_id, conflict_attack_id):
176205

177206
# Check if object in conflict exists
178207
# STIX ID
179-
stix_id_obj_in_conflict = stix_objs.get(obj_in_question.id)
208+
stix_id_obj_in_conflict = stix_objs.get(obj_in_question.get('id'))
180209

181210
# Get ATT&CK ID conflicts
182211
if conflict_attack_id:
@@ -189,7 +218,7 @@ def replace_object(attack_id, conflict_attack_id):
189218

190219
if not stix_id_obj_in_conflict:
191220
# Add if object does not exist in STIX ID map
192-
stix_objs[obj_in_question.id] = obj_in_question
221+
stix_objs[obj_in_question.get('id')] = obj_in_question
193222

194223
if not attack_id_obj_in_conflict:
195224
# Add if object does not exist in ATT&CK ID map

0 commit comments

Comments
 (0)