Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

384 add campaigns to website #390

Merged
merged 54 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
408f700
Updated usage of collections.Iterable to work with python 3.9+
ikiril01 Jun 28, 2022
a736337
Lint whitespace
jondricek Aug 11, 2022
f816a36
IInitial draft of adding Campaigns
jondricek Aug 11, 2022
cc71087
Ignore campaign template directory
jondricek Aug 11, 2022
47ad4ac
add first and last seen dates to campaign pages
clemiller Aug 12, 2022
e28ffeb
add campaign pages to random page feature
clemiller Aug 12, 2022
dd0a30d
add campaigns to the attack in excel resource page
clemiller Aug 12, 2022
6ea5cbb
reorder random page list to match website header
clemiller Aug 12, 2022
f3fa999
fix campaign links on technique page
clemiller Aug 12, 2022
f31d100
add group table to campaign page
clemiller Aug 12, 2022
08411b2
view attributed campaigns on group pages
clemiller Aug 12, 2022
4af0e5a
added campaigns table on software pages
clemiller Aug 15, 2022
b6d702f
software used by campaign
clemiller Aug 15, 2022
b80e10a
show techniques related to campaigns on group page
clemiller Aug 15, 2022
04c43b6
Add logging for markdown files used in resources
jondricek Aug 17, 2022
53a622c
Lint docstrings and get true broken link count
jondricek Aug 17, 2022
869b46b
Update test message
jondricek Aug 17, 2022
4cf52c5
Add newlines at end of file
jondricek Aug 17, 2022
b7246ae
Update copyright statement to 2022
jondricek Aug 17, 2022
afe61ea
Remove G0058 special case redirection
jondricek Aug 17, 2022
4d5934c
Remove unused HTML template
jondricek Aug 17, 2022
2be0a23
Update collection usage to be python 3.8+ compliant
jondricek Aug 18, 2022
0e6b55f
Merge pull request #375 from ikiril01/collections_fixes_python_3.9+
jondricek Aug 18, 2022
fe36545
reorder group, campaign, and software tables
clemiller Aug 24, 2022
0735dc3
add groups attributed to campaigns to software page
clemiller Aug 25, 2022
b3e648d
added campaign description text
clemiller Sep 1, 2022
d6ea593
add associated campaigns table; move group table
clemiller Sep 1, 2022
7e3cf8e
moved campaign table
clemiller Sep 1, 2022
9051819
add campaign techniques to group technique table
clemiller Sep 1, 2022
8e7fd48
Merge remote-tracking branch 'origin/develop' into 384-add-campaigns-…
jondricek Sep 2, 2022
72d49ad
Merge branch '384-add-campaigns-to-website' of github.com:mitre-attac…
jondricek Sep 2, 2022
734adbe
Update mitreattack-python dependency to latest
jondricek Sep 7, 2022
68b40ac
Update attackToExcel parameters to match new mitreattack-python changes
jondricek Sep 7, 2022
c2fc569
Merge branch 'develop' into 384-add-campaigns-to-website
clemiller Sep 8, 2022
02d050f
add first/last seen citations to info box
clemiller Sep 9, 2022
531b415
nav color code techniques inherited from campaigns
clemiller Sep 15, 2022
f827a52
Tests report now deduplicates stixtests output
jondricek Sep 15, 2022
c036c1d
add first/last seen columns to campaigns table on group pages
clemiller Sep 21, 2022
b64bec2
add campaign-related software to software table on group pages
clemiller Sep 21, 2022
3f0ac11
Merge branch '384-add-campaigns-to-website' of github.com:mitre-attac…
clemiller Sep 21, 2022
25beb3b
display dates on campaign overview page
clemiller Sep 22, 2022
0ee1ed7
add campaign date citations to group pages
clemiller Sep 22, 2022
b8e6a57
comment out first/last seen, alias cols on campaign overview page
clemiller Sep 27, 2022
bbbeabc
fixed attackcon attribution error
clemiller Oct 11, 2022
d0695cb
Minor whitespace fixes
jondricek Oct 14, 2022
0bae133
Update Campaign overview description
jondricek Oct 14, 2022
de7c580
More minor whitespace changes
jondricek Oct 14, 2022
fa7bec7
Update Group wording and whitespace fixes
jondricek Oct 14, 2022
c81c1ef
concatenate descriptions of duplicate groups on software pages
clemiller Oct 14, 2022
93bc1fa
minor update to handling descriptions of duplicate groups
clemiller Oct 14, 2022
110f1d3
Change name from Description to References
jondricek Oct 19, 2022
f89035d
update group table to display the set of references between direct/in…
clemiller Oct 20, 2022
1fb6243
Bump version of mitreattack-python to latest
jondricek Oct 24, 2022
0928a12
Bump mitreattack-python to 1.7.2
jondricek Oct 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ attack-theme/templates/general/base.html
attack-theme/templates/contribute
attack-theme/templates/datasources
attack-theme/templates/groups
attack-theme/templates/campaigns
attack-theme/templates/matrices
attack-theme/templates/mitigations
attack-theme/templates/resources
Expand Down
5 changes: 3 additions & 2 deletions attack-theme/templates/macros/clean_output.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
data (required, string), the STIX description to format
citations (optional, object), if not None, add citation markers to the data.
firstParagraphOnly (optional, boolean), if true, only return the first paragraph of the data in question.
convert (optional, boolean), if true, convert the data to markdown.
-->

{% macro stixToHTML(data, citations=None, firstParagraphOnly=False) %}
{% macro stixToHTML(data, citations=None, firstParagraphOnly=False, convert=True) %}
{% if data %}
{{ data | stixToHTML(citations, firstParagraphOnly) }}
{{ data | stixToHTML(citations, firstParagraphOnly, convert) }}
{% endif %}
{% endmacro %}
7 changes: 4 additions & 3 deletions custom_jinja_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,17 @@ def filter_urls(data):
return data


def stixToHTML(data, citations, firstParagraphOnly):
def stixToHTML(data, citations, firstParagraphOnly, convert):
"""Clean output of STIX content.

params:
data (required, string), the STIX description to format
citations (optional, object), if not None, add citation markers to the data.
firstParagraphOnly (optional, boolean), if true, only return the first paragraph of the data in question.
"""
# Replace data from markdown format
data = markdown.markdown(data)
if (convert):
# Replace data from markdown format
data = markdown.markdown(data)

# Replace url links
data = filter_urls(data)
Expand Down
2 changes: 1 addition & 1 deletion data/attackcon.json
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@
{
"title": "Helping Your Non-Security Executives Understand ATT&CK in 10 Minutes or Less",
"presenters": [{
"names": ["Emily Searle"],
"names": ["Elly Searle"],
"organization": "CrowdStrike"
}],
"description": "ATT&CK is an incredibly valuable framework for describing and analyzing what’s happening in your environment. Sometimes security professionals not only need a way to understand, but also need a way to clearly articulate to non-security leadership to gain support and investment in needed resourcing. Using UX design methods, CrowdStrike came up with a mental model and more conversational terms to help anyone quickly parse the big picture.",
Expand Down
21 changes: 21 additions & 0 deletions modules/campaigns/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from . import campaigns
from . import campaigns_config


def get_priority():
return campaigns_config.priority


def get_menu():
return {
"display_name": campaigns_config.module_name,
"module_name": campaigns_config.module_name,
"url": "/campaigns",
"external_link": False,
"priority": campaigns_config.priority,
"children": [],
}


def run_module():
return (campaigns.generate_campaigns(), campaigns_config.module_name)
312 changes: 312 additions & 0 deletions modules/campaigns/campaigns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
import collections
import json
import os

from loguru import logger

from modules import util

from . import campaigns_config
from .. import site_config


def generate_campaigns():
"""
Responsible for verifying campaign directory and starting off campaign markdown generation.
"""

# Create content pages directory if does not already exist
util.buildhelpers.create_content_pages_dir()

# Move templates to templates directory
util.buildhelpers.move_templates(campaigns_config.module_name, campaigns_config.campaigns_templates_path)

# Verify if directory exists
if not os.path.isdir(campaigns_config.campaign_markdown_path):
os.mkdir(campaigns_config.campaign_markdown_path)

# Generate redirections
util.buildhelpers.generate_redirections(
redirections_filename=campaigns_config.campaigns_redirection_location, redirect_md=site_config.redirect_md
)

# Generates the markdown files to be used for page generation
campaigns_generated = generate_markdown_files()

if not campaigns_generated:
util.buildhelpers.remove_module_from_menu(campaigns_config.module_name)


def generate_markdown_files():
"""
Responsible for generating campaign index page and getting shared data for all campaigns.
"""

has_campaign = False

campaign_list = util.relationshipgetters.get_campaign_list()

campaign_list_no_deprecated_revoked = util.buildhelpers.filter_deprecated_revoked(campaign_list)

if campaign_list_no_deprecated_revoked:
has_campaign = True

if has_campaign:
data = {}

# Amount of characters per category
group_by = 2

notes = util.relationshipgetters.get_objects_using_notes()
side_menu_data = util.buildhelpers.get_side_menu_data("Campaigns", "/campaigns/", campaign_list_no_deprecated_revoked)
data["side_menu_data"] = side_menu_data

side_menu_mobile_view_data = util.buildhelpers.get_side_menu_mobile_view_data(
"campaigns", "/campaigns/", campaign_list_no_deprecated_revoked, group_by
)
data["side_menu_mobile_view_data"] = side_menu_mobile_view_data

data["campaigns_table"] = get_campaigns_table_data(campaign_list_no_deprecated_revoked)
data["campaigns_list_len"] = str(len(campaign_list_no_deprecated_revoked))

subs = campaigns_config.campaign_index_md + json.dumps(data)

with open(
os.path.join(campaigns_config.campaign_markdown_path, "overview.md"), "w", encoding="utf8"
) as md_file:
md_file.write(subs)

# Create the markdown for the enterprise campaigns in the STIX
for campaign in campaign_list:
generate_campaign_md(campaign, side_menu_data, side_menu_mobile_view_data, notes)

return has_campaign


def generate_campaign_md(campaign, side_menu_data, side_menu_mobile_view_data, notes):
"""Responsible for generating markdown of all campaigns."""

attack_id = util.buildhelpers.get_attack_id(campaign)

if attack_id:
data = {}

data["attack_id"] = attack_id

data["side_menu_data"] = side_menu_data
data["side_menu_mobile_view_data"] = side_menu_mobile_view_data
data["notes"] = notes.get(campaign["id"])

# External references
ext_ref = campaign["external_references"]

dates = util.buildhelpers.get_created_and_modified_dates(campaign)
if dates.get("created"):
data["created"] = dates["created"]
if dates.get("modified"):
data["modified"] = dates["modified"]
if campaign.get("name"):
data["name"] = campaign["name"]
if campaign.get("x_mitre_version"):
data["version"] = campaign["x_mitre_version"]

campaign_dates = util.buildhelpers.get_first_last_seen_dates(campaign)
if campaign_dates.get("first_seen"):
data["first_seen"] = campaign_dates["first_seen"]
if campaign_dates.get("last_seen"):
data["last_seen"] = campaign_dates["last_seen"]

campaign_date_citations = util.buildhelpers.get_first_last_seen_citations(campaign)
if campaign_date_citations.get("first_seen_citation"):
data["first_seen_citation"] = campaign_date_citations["first_seen_citation"]
if campaign_date_citations.get("last_seen_citation"):
data["last_seen_citation"] = campaign_date_citations["last_seen_citation"]

if isinstance(campaign.get("x_mitre_contributors"), collections.abc.Iterable):
data["contributors_list"] = campaign["x_mitre_contributors"]

# Get initial reference list
reference_list = {"current_number": 0}

# Get initial reference list from campaign object
reference_list = util.buildhelpers.update_reference_list(reference_list, campaign)

if campaign.get("description"):
data["descr"] = campaign["description"]

if campaign.get("x_mitre_deprecated"):
data["deprecated"] = True

# Get technique data for techniques used table
data["technique_table_data"] = get_techniques_used_by_campaign_data(campaign, reference_list)

# Get navigator layers for this campaign
layers = util.buildhelpers.get_navigator_layers(
data["name"],
data["attack_id"],
"campaign",
data["version"] if "version" in data else None,
data["technique_table_data"],
)

data["layers"] = []
for layer in layers:
with open(
os.path.join(
campaigns_config.campaign_markdown_path,
"-".join([data["attack_id"], "techniques", layer["domain"]]) + ".md",
),
"w",
encoding="utf8",
) as layer_json:
subs = site_config.layer_md.substitute(
{"attack_id": data["attack_id"], "path": "campaigns/" + data["attack_id"], "domain": layer["domain"]}
)
subs = subs + layer["layer"]
layer_json.write(subs)
data["layers"].append(
{
"domain": layer["domain"],
"filename": "-".join([data["attack_id"], layer["domain"], "layer"]) + ".json",
"navigator_link": site_config.navigator_link,
}
)

# Get group data for Group table
data["group_data"] = get_group_table_data(campaign, reference_list)

# Grab software data for Software table
data["software_data"] = get_software_table_data(campaign, reference_list)

if campaign.get("aliases"):
data["alias_descriptions"] = util.buildhelpers.get_alias_data(campaign["aliases"][1:], ext_ref)

data["citations"] = reference_list

if isinstance(campaign.get("aliases"), collections.abc.Iterable):
data["aliases_list"] = campaign["aliases"][1:]

data["versioning_feature"] = site_config.check_versions_module()

subs = campaigns_config.campaign_md.substitute(data)
subs = subs + json.dumps(data)

# Write out the markdown file
with open(
os.path.join(campaigns_config.campaign_markdown_path, data["attack_id"] + ".md"), "w", encoding="utf8"
) as md_file:
md_file.write(subs)


def get_campaigns_table_data(campaign_list):
"""Responsible for generating campaign table data for the campaign index page"""
campaigns_table_data = []

# Now the table on the right, which is made up of campaign data
for campaign in campaign_list:
attack_id = util.buildhelpers.get_attack_id(campaign)
if attack_id:
campaign_dates = util.buildhelpers.get_first_last_seen_dates(campaign)
row = {
"id": attack_id,
"name": campaign["name"] if campaign.get("name") else attack_id,
"first_seen": campaign_dates["first_seen"] if campaign_dates.get("first_seen") else '',
"last_seen": campaign_dates["last_seen"] if campaign_dates.get("last_seen") else '',
}

if campaign.get("description"):
row["descr"] = campaign["description"]

if campaign.get("x_mitre_deprecated"):
row["deprecated"] = True

if isinstance(campaign.get("aliases"), collections.abc.Iterable):
row["aliases_list"] = campaign["aliases"][1:]

campaigns_table_data.append(row)

return campaigns_table_data


def get_group_table_data(campaign, reference_list):
"""Given a campaign, get the group table data."""
group_list = {} # group stix_id => {attack_id, name, description}
groups_attributed_to_campaign = util.relationshipgetters.get_groups_attributed_to_campaigns()

if groups_attributed_to_campaign.get(campaign.get("id")):
for group in groups_attributed_to_campaign[campaign["id"]]:
group_id = group["object"]["id"]
if group_id not in group_list:
attack_id = util.buildhelpers.get_attack_id(group["object"])
group_list[group_id] = {
"id": attack_id,
"name": group["object"]["name"]
}

if group["relationship"].get("description"):
group_list[group_id]["desc"] = group["relationship"]["description"]

# update reference list
reference_list = util.buildhelpers.update_reference_list(reference_list, group["relationship"])


group_data = [group_list[item] for item in group_list]
group_data = sorted(group_data, key=lambda k: k["name"].lower())
return group_data

def get_techniques_used_by_campaign_data(campaign, reference_list):
"""Given a campaign and its reference list, get the techniques used by the campaign.

Check the reference list for citations, if not found in list, add it.
"""
technique_list = {}
techniques_used_by_campaigns = util.relationshipgetters.get_techniques_used_by_campaigns()

if techniques_used_by_campaigns.get(campaign.get("id")):
for technique in techniques_used_by_campaigns[campaign["id"]]:
# Do not add if technique is deprecated
if not technique["object"].get("x_mitre_deprecated"):
technique_list = util.buildhelpers.technique_used_helper(technique_list, technique, reference_list)

technique_data = []
for item in technique_list:
technique_data.append(technique_list[item])
# Sort by technique name
technique_data = sorted(technique_data, key=lambda k: k["name"].lower())

# Sort by domain name
technique_data = sorted(
technique_data, key=lambda k: [site_config.custom_alphabet.index(c) for c in k["domain"].lower()]
)
return technique_data


def get_software_table_data(campaign, reference_list):
"""Given a campaign, get software table data."""
software_list = {} # software stix_id => {attack_id, name, description}

# Creating map for tools/malware used by campaigns
software_used_by_campaign = [
util.relationshipgetters.get_tools_used_by_campaigns(),
util.relationshipgetters.get_malware_used_by_campaigns()
]

for res in software_used_by_campaign:
if res.get(campaign.get("id")):
for software in res[campaign["id"]]:
software_id = software["object"]["id"]
if software_id not in software_list:
attack_id = util.buildhelpers.get_attack_id(software["object"])
software_list[software_id] = {
"id": attack_id,
"name": software["object"]["name"]
}
if software["relationship"].get("description"):
software_list[software_id]["desc"] = software["relationship"]["description"]

# update reference list
reference_list = util.buildhelpers.update_reference_list(reference_list, software["relationship"])

software_data = [software_list[item] for item in software_list]
software_data = sorted(software_data, key=lambda k: k["name"].lower())
return software_data
Loading