diff --git a/blueprints/assessment_export.py b/blueprints/assessment_export.py index 0b4aff4..dd599ae 100644 --- a/blueprints/assessment_export.py +++ b/blueprints/assessment_export.py @@ -37,7 +37,7 @@ def exportassessment(id, filetype): # Otherwise flatten JSON arrays into comma delimited strings for t, testcase in enumerate(jsonDict): - for field in ["sources", "targets", "tools", "controls", "tags", "redfiles", "bluefiles"]: + for field in ["sources", "targets", "tools", "controls", "tags", "preventionsources", "detectionsources", "redfiles", "bluefiles"]: jsonDict[t][field] = ",".join(testcase[field]) # Convert the JSON dict to CSV and deliver @@ -66,7 +66,7 @@ def exportcampaign(id): # Generate a full JSON dump but then filter to only the applicable fields fullJson = testcase.to_json(raw=True) campaignJson = {} - for field in ["mitreid", "tactic", "name", "objective", "actions", "tools", "uuid", "tags"]: + for field in ["mitreid", "tactic", "name", "objective", "actions", "tools", "uuid", "tags", "priority", "priorityurgency", "expectedalertseverity"]: campaignJson[field] = fullJson[field] jsonDict.append(campaignJson) @@ -110,7 +110,7 @@ def exportreport(id): doc.render({ "assessment": assessment, "testcases": testcases - }) + }, autoescape=True) doc.save(f'files/{id}/report.docx') return send_from_directory('files', f"{id}/report.docx", as_attachment=True) @@ -155,33 +155,38 @@ def exportnavigator(id): "selectSubtechniquesWithParent": False } - for technique in Technique.objects().all(): - if current_user.has_role("Blue"): - testcases = TestCase.objects(assessmentid=id, visible=True, mitreid=technique.mitreid).all() - else: - testcases = TestCase.objects(assessmentid=id, mitreid=technique.mitreid).all() - ttp = { - "techniqueID": technique.mitreid - } + if current_user.has_role("Blue"): + testcases = TestCase.objects(assessmentid=id, visible=True).all() + else: + testcases = TestCase.objects(assessmentid=id).all() + + results = {} + for testcase in testcases: + if not testcase.tactic in results: + results[testcase.tactic] = {} + + if not testcase.mitreid in results[testcase.tactic]: + results[testcase.tactic][testcase.mitreid] = {"Prevented and Alerted": 0, "Prevented": 0, "Alerted": 0, "Logged": 0, "Missed": 0} + + if testcase.outcome in results[testcase.tactic][testcase.mitreid].keys(): + results[testcase.tactic][testcase.mitreid][testcase.outcome] += 1 - if testcases: + for tactic_key, techniques in results.items(): + for technique_key,outcomes in techniques.items(): count = 0 - outcomes = {"Prevented": 0, "Alerted": 0, "Logged": 0, "Missed": 0} - for testcase in testcases: - if testcase.outcome in outcomes.keys(): - count += 1 - outcomes[testcase.outcome] += 1 + for outcome_key, outcome in outcomes.items(): + count += outcome if count: - score = int((outcomes["Prevented"] * 3 + outcomes["Alerted"] * 2 + - outcomes["Logged"]) / (count * 3) * 100) - ttp["score"] = score - - for tactic in technique.tactics: - tactic = tactic.lower().strip().replace(" ", "-") - tacticTTP = dict(ttp) - tacticTTP["tactic"] = tactic - navigator["techniques"].append(tacticTTP) + score = int((outcomes["Prevented and Alerted"] * 4 + outcomes["Prevented"] * 3 + outcomes["Alerted"] * 2 + + outcomes["Logged"]) / (count * 4) * 100) + + ttp = { + "techniqueID": technique_key, + "tactic": tactic_key.lower().strip().replace(" ", "-"), + "score": score + } + navigator["techniques"].append(ttp) with open(f'files/{id}/navigator.json', 'w') as f: json.dump(navigator, f, indent=4) diff --git a/blueprints/assessment_import.py b/blueprints/assessment_import.py index bd0a14d..e438722 100644 --- a/blueprints/assessment_import.py +++ b/blueprints/assessment_import.py @@ -25,6 +25,9 @@ def testcasetemplates(id): objective = template.objective, actions = template.actions, rednotes = template.rednotes, + priority = template.priority, + priorityurgency = template.priorityurgency, + expectedalertseverity = template.expectedalertseverity, assessmentid = id ).save() newcases.append(newcase.to_json()) @@ -72,7 +75,7 @@ def testcasecampaign(id): for testcase in campaignTestcases: newcase = TestCase() newcase.assessmentid = id - for field in ["name", "mitreid", "tactic", "objective", "actions", "tools", "uuid", "tags"]: + for field in ["name", "mitreid", "tactic", "objective", "actions", "tools", "uuid", "tags", "priority", "priorityurgency", "expectedalertseverity"]: if field in testcase: if field not in ["tools", "tags"]: newcase[field] = testcase[field] @@ -128,7 +131,9 @@ def importentire(): "targets": {}, "tools": {}, "controls": {}, - "tags": {} + "tags": {}, + "preventionsources": {}, + "detectionsources": {} } for oldTestcase in export: @@ -140,14 +145,14 @@ def importentire(): for field in ["name", "objective", "actions", "rednotes", "bluenotes", "uuid", "mitreid", "tactic", "state", "prevented", "preventedrating", "alerted", "alertseverity", "logged", "detectionrating", - "priority", "priorityurgency", "visible", "outcome"]: + "priority", "priorityurgency", "expectedalertseverity", "visible", "outcome"]: newTestcase[field] = oldTestcase[field] - for field in ["starttime", "endtime", "detecttime", "modifytime"]: + for field in ["starttime", "endtime", "detecttime", "modifytime", "alerttime", "preventtime"]: if oldTestcase[field] != "None": newTestcase[field] = datetime.datetime.strptime(oldTestcase[field].split(".")[0], "%Y-%m-%d %H:%M:%S") - for field in ["sources", "targets", "tools", "controls", "tags"]: + for field in ["sources", "targets", "tools", "controls", "tags", "preventionsources", "detectionsources"]: newTestcase[field] = [] for multi in oldTestcase[field]: @@ -165,6 +170,10 @@ def importentire(): newMulti = Control(name=name, description=details) elif field == "tags": newMulti = Tag(name=name, colour=details) + elif field == "preventionsources": + newMulti = Preventionsource(name=name, description=details) + elif field == "detectionsources": + newMulti = Detectionsource(name=name, description=details) assessment[field].append(newMulti) assessment[field].save() assessmentMultis[field][f"{newMulti.name}|{newMulti.description if field != 'tags' else newMulti.colour}"] = str(assessment[field][-1].id) diff --git a/blueprints/assessment_utils.py b/blueprints/assessment_utils.py index fe41644..5568033 100644 --- a/blueprints/assessment_utils.py +++ b/blueprints/assessment_utils.py @@ -14,7 +14,7 @@ @roles_accepted('Admin', 'Red', 'Blue') @user_assigned_assessment def assessmentmulti(id, field): - if field not in ["sources", "targets", "tools", "controls", "tags"]: + if field not in ["sources", "targets", "tools", "controls", "tags", "preventionsources", "detectionsources"]: return '', 418 assessment = Assessment.objects(id=id).first() @@ -27,6 +27,8 @@ def assessmentmulti(id, field): "tools": Tool(), "controls": Control(), "tags": Tag(), + "preventionsources": Preventionsource(), + "detectionsources": Detectionsource(), }[field] # If pre-existing, then edit pre-existing to preserve ID @@ -94,7 +96,7 @@ def assessmentstats(id): # Initalise metrics that are captured stats = { "All": { - "Prevented": 0, "Alerted": 0, "Logged": 0, "Missed": 0, + "Prevented and Alerted": 0, "Prevented": 0, "Alerted": 0, "Logged": 0, "Missed": 0, "Critical": 0, "High": 0, "Medium": 0, "Low": 0, "Informational": 0, "scoresPrevent": [], "scoresDetect": [], "priorityType": [], "priorityUrgency": [], @@ -140,7 +142,7 @@ def assessmentstats(id): for tactic in stats: if tactic == "All": continue - for key in ["Prevented", "Alerted", "Logged", "Missed", "Critical", "High", "Medium", "Low", "Informational"]: + for key in ["Prevented and Alerted", "Prevented", "Alerted", "Logged", "Missed", "Critical", "High", "Medium", "Low", "Informational"]: stats["All"][key] += stats[tactic][key] for key in ["scoresPrevent", "scoresDetect", "priorityType", "priorityUrgency", "controls"]: stats["All"][key].extend(stats[tactic][key]) @@ -172,7 +174,8 @@ def assessmenthexagons(id): }) continue - score = (TestCase.objects(assessmentid=id, tactic=tactics[i], outcome="Prevented").count() + + score = (TestCase.objects(assessmentid=id, tactic=tactics[i], outcome="Prevented and Alerted").count() + + TestCase.objects(assessmentid=id, tactic=tactics[i], outcome="Prevented").count() + TestCase.objects(assessmentid=id, tactic=tactics[i], outcome="Alerted").count() - TestCase.objects(assessmentid=id, tactic=tactics[i], outcome="Missed").count()) if score > 1: diff --git a/blueprints/testcase.py b/blueprints/testcase.py index 53dc174..8c5e7d0 100644 --- a/blueprints/testcase.py +++ b/blueprints/testcase.py @@ -43,7 +43,9 @@ def runtestcasepost(id): "targets": assessment.targets, "tools": assessment.tools, "controls": assessment.controls, - "tags": assessment.tags + "tags": assessment.tags, + "preventionsources": assessment.preventionsources, + "detectionsources": assessment.detectionsources } ) @@ -58,10 +60,10 @@ def testcasesave(id): if not testcase.visible and isBlue: return ("", 403) - directFields = ["name", "objective", "actions", "rednotes", "bluenotes", "uuid", "mitreid", "tactic", "state", "prevented", "preventedrating", "alertseverity", "logged", "detectionrating", "priority", "priorityurgency"] if not isBlue else ["bluenotes", "prevented", "alerted", "alertseverity"] - listFields = ["sources", "targets", "tools", "controls", "tags"] + directFields = ["name", "objective", "actions", "rednotes", "bluenotes", "uuid", "mitreid", "tactic", "state", "prevented", "preventedrating", "alertseverity", "logged", "detectionrating", "priority", "priorityurgency", "expectedalertseverity"] if not isBlue else ["bluenotes", "prevented", "alerted", "alertseverity"] + listFields = ["sources", "targets", "tools", "controls", "tags", "preventionsources", "detectionsources"] boolFields = ["alerted", "logged", "visible"] if not isBlue else ["alerted", "logged"] - timeFields = ["starttime", "endtime"] + timeFields = ["starttime", "endtime", "alerttime", "preventtime"] fileFields = ["redfiles", "bluefiles"] if not isBlue else ["bluefiles"] testcase = applyFormData(testcase, request.form, directFields) @@ -100,7 +102,10 @@ def testcasesave(id): testcase.detecttime = datetime.utcnow() if testcase.prevented in ["Yes", "Partial"]: - testcase.outcome = "Prevented" + if testcase.alerted: + testcase.outcome = "Prevented and Alerted" + else: + testcase.outcome = "Prevented" elif testcase.alerted: testcase.outcome = "Alerted" elif testcase.logged: diff --git a/blueprints/testcase_utils.py b/blueprints/testcase_utils.py index d2c70c3..93c5895 100644 --- a/blueprints/testcase_utils.py +++ b/blueprints/testcase_utils.py @@ -26,7 +26,7 @@ def testcasevisibility(id): def testcaseclone(id): orig = TestCase.objects(id=id).first() newcase = TestCase() - copy = ["name", "assessmentid", "objective", "actions", "rednotes", "mitreid", "uuid", "tactic", "tools", "tags"] + copy = ["name", "assessmentid", "objective", "actions", "rednotes", "mitreid", "uuid", "tactic", "tools", "tags", "preventionsources", "detectionsources"] for field in copy: newcase[field] = orig[field] newcase.name = orig["name"] + " (Copy)" diff --git a/model.py b/model.py index 0e649c7..62237ad 100644 --- a/model.py +++ b/model.py @@ -85,6 +85,31 @@ def to_json(self, raw=False): } +class Preventionsource(db.EmbeddedDocument): + id = db.ObjectIdField( required=True, default=ObjectId ) + name = db.StringField() + description = db.StringField(default="") + + def to_json(self, raw=False): + return { + "id": str(self.id), + "name": esc(self.name, raw), + "description": esc(self.description, raw) + } + +class Detectionsource(db.EmbeddedDocument): + id = db.ObjectIdField( required=True, default=ObjectId ) + name = db.StringField() + description = db.StringField(default="") + + def to_json(self, raw=False): + return { + "id": str(self.id), + "name": esc(self.name, raw), + "description": esc(self.description, raw) + } + + class File(db.EmbeddedDocument): name = db.StringField() path = db.StringField() @@ -114,6 +139,9 @@ class TestCaseTemplate(db.Document): rednotes = db.StringField(default="") uuid = db.StringField(default="") provider = db.StringField(default="") + priority = db.StringField(default="") + priorityurgency = db.StringField(default="") + expectedalertseverity = db.StringField(default="") class TestCase(db.Document): @@ -131,6 +159,8 @@ class TestCase(db.Document): tools = db.ListField(db.StringField()) controls = db.ListField(db.StringField()) tags = db.ListField(db.StringField()) + preventionsources = db.ListField(db.StringField()) + detectionsources = db.ListField(db.StringField()) state = db.StringField(default="Pending") prevented = db.StringField() preventedrating = db.StringField() @@ -140,9 +170,12 @@ class TestCase(db.Document): detectionrating = db.StringField() priority = db.StringField() priorityurgency = db.StringField() + expectedalertseverity = db.StringField() starttime = db.DateTimeField() endtime = db.DateTimeField() detecttime = db.DateTimeField() + alerttime = db.DateTimeField() + preventtime = db.DateTimeField() redfiles = db.EmbeddedDocumentListField(File) bluefiles = db.EmbeddedDocumentListField(File) visible = db.BooleanField(default=False) @@ -154,11 +187,11 @@ def to_json(self, raw=False): for field in ["assessmentid", "name", "objective", "actions", "rednotes", "bluenotes", "uuid", "mitreid", "tactic", "state", "prevented", "preventedrating", "alerted", "alertseverity", "logged", "detectionrating", - "priority", "priorityurgency", "visible", "outcome"]: + "priority", "priorityurgency", "expectedalertseverity", "visible", "outcome"]: jsonDict[field] = esc(self[field], raw) - for field in ["id", "detecttime", "modifytime", "starttime", "endtime"]: + for field in ["id", "detecttime", "modifytime", "starttime", "endtime", "alerttime", "preventtime"]: jsonDict[field] = str(self[field]).split(".")[0] - for field in ["tags", "sources", "targets", "tools", "controls"]: + for field in ["tags", "sources", "targets", "tools", "controls", "preventionsources", "detectionsources"]: jsonDict[field] = self.to_json_multi(field) for field in ["redfiles", "bluefiles"]: files = [] @@ -187,15 +220,17 @@ class Assessment(db.Document): tools = db.EmbeddedDocumentListField(Tool) controls = db.EmbeddedDocumentListField(Control) tags = db.EmbeddedDocumentListField(Tag) + preventionsources = db.EmbeddedDocumentListField(Preventionsource) + detectionsources = db.EmbeddedDocumentListField(Detectionsource) navigatorexport = db.StringField(default="") def get_progress(self): - # Returns string with % of "missed|logged|alerted|prevented|pending" + # Returns string with % of "Prevented and Alerted|Prevented|Alerted|Logged|Missed" testcases = TestCase.objects(assessmentid=str(self.id)).count() if testcases == 0: return "0|0|0|0|0" outcomes = [] - for outcome in ["Prevented", "Alerted", "Logged", "Missed"]: + for outcome in ["Prevented and Alerted", "Prevented", "Alerted", "Logged", "Missed"]: outcomes.append(str(round( TestCase.objects(assessmentid=str(self.id), outcome=outcome).count() / testcases * 100 diff --git a/pops-backup.py b/pops-backup.py index 57ab34f..9e79e0a 100644 --- a/pops-backup.py +++ b/pops-backup.py @@ -28,7 +28,7 @@ def exportassessment(assessment, filetype): # Otherwise flatten JSON arrays into comma delimited strings for t, testcase in enumerate(jsonDict): - for field in ["sources", "targets", "tools", "controls", "tags", "redfiles", "bluefiles"]: + for field in ["sources", "targets", "tools", "controls", "tags", "redfiles", "bluefiles", "preventionsources", "detectionsources"]: jsonDict[t][field] = ",".join(testcase[field]) # Convert the JSON dict to CSV and deliver @@ -97,34 +97,43 @@ def exportnavigator(id): }, "showTacticRowBackground": True, "tacticRowBackground": "#593196", - "selectTechniquesAcrossTactics": True, + "selectTechniquesAcrossTactics": False, "selectSubtechniquesWithParent": False } - for technique in Technique.objects().all(): - testcases = TestCase.objects(assessmentid=id, mitreid=technique.mitreid).all() - ttp = { - "techniqueID": technique.mitreid - } + if current_user.has_role("Blue"): + testcases = TestCase.objects(assessmentid=id, visible=True).all() + else: + testcases = TestCase.objects(assessmentid=id).all() - if testcases: + results = {} + for testcase in testcases: + if not testcase.tactic in results: + results[testcase.tactic] = {} + + if not testcase.mitreid in results[testcase.tactic]: + results[testcase.tactic][testcase.mitreid] = {"Prevented and Alerted": 0, "Prevented": 0, "Alerted": 0, "Logged": 0, "Missed": 0} + + if testcase.outcome in results[testcase.tactic][testcase.mitreid].keys(): + results[testcase.tactic][testcase.mitreid][testcase.outcome] += 1 + + for tactic_key, techniques in results.items(): + for technique_key,outcomes in techniques.items(): count = 0 - outcomes = {"Prevented": 0, "Alerted": 0, "Logged": 0, "Missed": 0} - for testcase in testcases: - if testcase.outcome in outcomes.keys(): - count += 1 - outcomes[testcase.outcome] += 1 + for outcome_key, outcome in outcomes.items(): + count += outcome if count: - score = int((outcomes["Prevented"] * 3 + outcomes["Alerted"] * 2 + - outcomes["Logged"]) / (count * 3) * 100) - ttp["score"] = score - - for tactic in technique.tactics: - tactic = tactic.lower().strip().replace(" ", "-") - tacticTTP = dict(ttp) - tacticTTP["tactic"] = tactic - navigator["techniques"].append(tacticTTP) + score = int((outcomes["Prevented and Alerted"] * 4 + outcomes["Prevented"] * 3 + outcomes["Alerted"] * 2 + + outcomes["Logged"]) / (count * 4) * 100) + + ttp = { + "techniqueID": technique_key, + "tactic": tactic_key.lower().strip().replace(" ", "-"), + "score": score + } + navigator["techniques"].append(ttp) + with open(f"{args.backupdir}/{id}/navigator.json", 'w') as f: json.dump(navigator, f, indent=4) diff --git a/seeder.py b/seeder.py index 156a5fa..362c05e 100644 --- a/seeder.py +++ b/seeder.py @@ -152,7 +152,10 @@ def parseCustomTestcases (): tactic = yml["tactic"], objective = yml["objective"], actions = yml["actions"], - provider = yml["provider"] + provider = yml["provider"], + priority = yml["priority"], + priorityurgency = yml["priorityurgency"], + expectedalertseverity = yml["expectedalertseverity"] ).save() def parseCustomKBs (): diff --git a/static/scripts/assessment.js b/static/scripts/assessment.js index b23d32c..9600a1b 100644 --- a/static/scripts/assessment.js +++ b/static/scripts/assessment.js @@ -203,7 +203,10 @@ function bgFormatter(value) { bg = "warning" } else if (["Alerted", "3.0", "3.5"].includes(value)) { bg = "success" - } else if (["Prevented", "4.0", "4.5", "5.0"].includes(value)) { + } else if (["Prevented", "4.0", "4.5"].includes(value)) { + bg = "info" + text = "light" + } else if (["Prevented and Alerted", "5.0"].includes(value)) { bg = "info" text = "light" } else if (["Complete"].includes(value)) { diff --git a/static/scripts/assessment.stats.js b/static/scripts/assessment.stats.js index c70481c..3236ad6 100644 --- a/static/scripts/assessment.stats.js +++ b/static/scripts/assessment.stats.js @@ -83,7 +83,7 @@ var barChartOptionsCustom = { }, xaxis: { type: 'category', - categories: ["Prevented","Alerted","Logged","Missed"], + categories: ["Prevented and Alerted", "Prevented","Alerted","Logged","Missed"], labels: { show: false, rotate: -45, @@ -165,7 +165,7 @@ function boxPlotVals(data) { // Outcomes pie chart var resultsPie = JSON.parse(JSON.stringify(pieChartOptions)); resultsPie.title.text = "Outcomes" -keys = ["Prevented", "Alerted", "Logged", "Missed"] +keys = ["Prevented and Alerted", "Prevented", "Alerted", "Logged", "Missed"] resultsPie.series = keys.map((t) => { return tacticStats["All"][t] }) @@ -177,7 +177,7 @@ chart.render(); // Outcome bar chart (Excluding "All") var results = JSON.parse(JSON.stringify(barChartOptions)); results.title.text = "Outcome per Tactic" -results.series = ["Prevented", "Alerted", "Logged", "Missed"].map((t) => { +results.series = ["Prevented and Alerted", "Prevented", "Alerted", "Logged", "Missed"].map((t) => { return { name: t, data: Object.keys(tacticStats).filter((i) => i !== "All").map((i) => { @@ -272,7 +272,7 @@ function renderTacticChart(name, filteredTacticStats, chartContainerId) { results.title.text = name + " Results"; if (isObjectNotEmpty(filteredTacticStats)) { // Loop over each element and set each value from filteredTacticStats - results.series = ["Prevented", "Alerted", "Logged", "Missed"].map((t) => { + results.series = ["Prevented and Alerted", "Prevented", "Alerted", "Logged", "Missed"].map((t) => { return { name: t, data: [filteredTacticStats[t]] // Put the count of each outcome into an array diff --git a/static/scripts/assessments.js b/static/scripts/assessments.js index ce60a43..61305a6 100644 --- a/static/scripts/assessments.js +++ b/static/scripts/assessments.js @@ -113,10 +113,11 @@ function nameFormatter(name, row) { function progressFormatter(progress) { return `