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

Some changes for PurpleOps after internal PoC #26

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
57 changes: 31 additions & 26 deletions blueprints/assessment_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 14 additions & 5 deletions blueprints/assessment_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -128,7 +131,9 @@ def importentire():
"targets": {},
"tools": {},
"controls": {},
"tags": {}
"tags": {},
"preventionsources": {},
"detectionsources": {}
}

for oldTestcase in export:
Expand All @@ -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]:
Expand All @@ -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)
Expand Down
11 changes: 7 additions & 4 deletions blueprints/assessment_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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": [],
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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:
Expand Down
15 changes: 10 additions & 5 deletions blueprints/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
)

Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion blueprints/testcase_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
45 changes: 40 additions & 5 deletions model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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):
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -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 = []
Expand Down Expand Up @@ -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
Expand Down
Loading