From 5e405325fea0fa5554b31ac28f53080d09cc109b Mon Sep 17 00:00:00 2001 From: 1modm Date: Thu, 9 Sep 2021 12:42:54 +0100 Subject: [PATCH] deciduous integration --- Pipfile | 1 + django/config/Dockerfile | 3 +- .../petereport/static/deciduous/deciduous.js | 433 ++++++++++++++++++ django/preport/forms.py | 30 +- .../migrations/0002_auto_20210909_1050.py | 38 ++ django/preport/models.py | 13 +- .../templates/attacktree/attacktree_add.html | 164 +++++++ .../templates/attacktree/attacktree_view.html | 104 +++++ .../attacktree/reportattacktree.html | 165 +++++++ .../templates/findings/finding_view.html | 36 +- django/preport/templates/home/aside.html | 30 +- .../templates/products/product_view.html | 13 +- .../templates/reports/report_list.html | 128 ++++++ .../templates/reports/report_view.html | 200 ++++++-- .../templates/tpl/html/html_finding.md | 13 +- .../tpl/html/html_finding_close_table.html | 5 - .../preport/templates/tpl/html/html_report.md | 2 + .../tpl/html/md_appendix_in_finding.md | 1 - .../templates/tpl/html/md_attacktree.md | 5 + django/preport/templates/tpl/html_finding.md | 51 --- .../tpl/html_finding_close_table.html | 5 - .../templates/tpl/html_finding_end_table.html | 3 - .../templates/tpl/html_finding_summary.html | 5 - .../tpl/html_finding_summary_table.html | 9 - django/preport/templates/tpl/html_report.md | 60 --- .../templates/tpl/jupyter/attacktree.ipynb | 11 + .../tpl/jupyter/attacktree_in_finding.ipynb | 9 + .../templates/tpl/jupyter/attacktrees.ipynb | 8 + .../templates/tpl/jupyter/report.ipynb | 1 + .../templates/tpl/markdown/md_appendix.md | 4 +- .../tpl/markdown/md_appendix_in_finding.md | 1 - .../templates/tpl/markdown/md_attacktree.md | 3 + .../templates/tpl/markdown/md_finding.md | 23 +- .../templates/tpl/markdown/md_report.md | 3 +- django/preport/templates/tpl/md_appendix.md | 3 - .../templates/tpl/md_appendix_in_finding.md | 1 - django/preport/templates/tpl/md_finding.md | 30 -- .../templates/tpl/md_finding_summary.md | 1 - django/preport/templates/tpl/md_report.md | 52 --- .../tpl/pdf/pdf_appendix_in_finding.md | 3 - .../tpl/pdf/pdf_appendix_na_in_finding.md | 3 - .../templates/tpl/pdf/pdf_attacktree.md | 4 + .../preport/templates/tpl/pdf/pdf_finding.md | 8 +- django/preport/templates/tpl/pdf_appendix.md | 3 - .../templates/tpl/pdf_appendix_in_finding.md | 3 - .../tpl/pdf_appendix_na_in_finding.md | 3 - django/preport/templates/tpl/pdf_finding.md | 35 -- .../templates/tpl/pdf_finding_summary.md | 5 - django/preport/templates/tpl/pdf_header.tex | 77 ---- django/preport/templates/tpl/pdf_header.yaml | 44 -- django/preport/templates/tpl/pdf_report.md | 48 -- django/preport/urls.py | 7 + django/preport/views.py | 280 +++++++++-- licenses/OSCP-Exam-Report-Template-Markdown | 21 + licenses/deciduous | 339 ++++++++++++++ 55 files changed, 1999 insertions(+), 551 deletions(-) create mode 100644 django/petereport/static/deciduous/deciduous.js create mode 100644 django/preport/migrations/0002_auto_20210909_1050.py create mode 100644 django/preport/templates/attacktree/attacktree_add.html create mode 100644 django/preport/templates/attacktree/attacktree_view.html create mode 100644 django/preport/templates/attacktree/reportattacktree.html create mode 100644 django/preport/templates/reports/report_list.html delete mode 100644 django/preport/templates/tpl/html/html_finding_close_table.html delete mode 100644 django/preport/templates/tpl/html/md_appendix_in_finding.md create mode 100644 django/preport/templates/tpl/html/md_attacktree.md delete mode 100644 django/preport/templates/tpl/html_finding.md delete mode 100644 django/preport/templates/tpl/html_finding_close_table.html delete mode 100644 django/preport/templates/tpl/html_finding_end_table.html delete mode 100644 django/preport/templates/tpl/html_finding_summary.html delete mode 100644 django/preport/templates/tpl/html_finding_summary_table.html delete mode 100644 django/preport/templates/tpl/html_report.md create mode 100644 django/preport/templates/tpl/jupyter/attacktree.ipynb create mode 100644 django/preport/templates/tpl/jupyter/attacktree_in_finding.ipynb create mode 100644 django/preport/templates/tpl/jupyter/attacktrees.ipynb delete mode 100644 django/preport/templates/tpl/markdown/md_appendix_in_finding.md create mode 100644 django/preport/templates/tpl/markdown/md_attacktree.md delete mode 100644 django/preport/templates/tpl/md_appendix.md delete mode 100644 django/preport/templates/tpl/md_appendix_in_finding.md delete mode 100644 django/preport/templates/tpl/md_finding.md delete mode 100644 django/preport/templates/tpl/md_finding_summary.md delete mode 100644 django/preport/templates/tpl/md_report.md delete mode 100644 django/preport/templates/tpl/pdf/pdf_appendix_in_finding.md delete mode 100644 django/preport/templates/tpl/pdf/pdf_appendix_na_in_finding.md create mode 100644 django/preport/templates/tpl/pdf/pdf_attacktree.md delete mode 100644 django/preport/templates/tpl/pdf_appendix.md delete mode 100644 django/preport/templates/tpl/pdf_appendix_in_finding.md delete mode 100644 django/preport/templates/tpl/pdf_appendix_na_in_finding.md delete mode 100644 django/preport/templates/tpl/pdf_finding.md delete mode 100644 django/preport/templates/tpl/pdf_finding_summary.md delete mode 100644 django/preport/templates/tpl/pdf_header.tex delete mode 100644 django/preport/templates/tpl/pdf_header.yaml delete mode 100644 django/preport/templates/tpl/pdf_report.md create mode 100644 licenses/OSCP-Exam-Report-Template-Markdown create mode 100644 licenses/deciduous diff --git a/Pipfile b/Pipfile index c3fce73..31e0636 100644 --- a/Pipfile +++ b/Pipfile @@ -12,5 +12,6 @@ requests = ">=2.25.1" martor = "==1.6.3" pypandoc = "==1.6.3" termcolor = "==1.1.0" +cairosvg = "==2.5.2" [dev-packages] \ No newline at end of file diff --git a/django/config/Dockerfile b/django/config/Dockerfile index 122dbab..cb169d4 100644 --- a/django/config/Dockerfile +++ b/django/config/Dockerfile @@ -24,6 +24,7 @@ RUN apt-get -y install python3 python3-dev python3-pip # install dependencies RUN apt-get -y install pipenv texlive-full python3-pypandoc RUN apt-get -y install wget +RUN apt-get -y install libpangocairo-1.0-0 # alias "python" to "python3" RUN ln -s /usr/bin/python3 /usr/bin/python @@ -46,4 +47,4 @@ RUN wget ${EISVOGEL_REPO}/v${EISVOGEL_VERSION}/eisvogel.tex -O ${TEMPLATES_DIR}/ WORKDIR /opt/petereport COPY Pipfile ./ -RUN pipenv install --system --deploy --ignore-pipfile \ No newline at end of file +RUN pipenv install --system --deploy --ignore-pipfile diff --git a/django/petereport/static/deciduous/deciduous.js b/django/petereport/static/deciduous/deciduous.js new file mode 100644 index 0000000..20bc617 --- /dev/null +++ b/django/petereport/static/deciduous/deciduous.js @@ -0,0 +1,433 @@ +/* + * Adapted version of https://github.com/rpetrich/deciduous + */ + + +function wordwrap(text, limit) { + text = String(text); + if (text.indexOf("\n") != -1) { + return text; + } + const split = text.split(" "); + let all = []; + let current = []; + let currentLength = 0; + for (let i = 0; i < split.length; i++) { + const line = split[i]; + if (currentLength == 0 || (currentLength + line.length < limit && line[0] != "(")) { + current.push(line); + currentLength += line.length; + } else { + all.push(current.join(" ")); + current = [line]; + currentLength = line.length; + } + } + all.push(current.join(" ")); + return all.join("\n"); +} + + + +function mangleName(name) { + if (/^[A-Za-z]\w*$/.test(name)) { + return name; + } + return JSON.stringify(name); +} + + + +function line(name, properties) { + const entries = Object.entries(properties); + if (entries.length == 0) { + return name; + } + return name + " [ " + entries.map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ") + " ]"; +} + + +function parseFrom(raw) { + if (typeof raw == "object") { + const [fromName, label] = Object.entries(raw)[0]; + return [fromName, label, raw] + } + return [String(raw), null, {}]; +} + + +const themes = { + "classic": { + "edge": "#2B303A", + "edge-text": "#DB2955", + "backwards-edge": "#7692FF", + "reality-fill": "#2B303A", + "reality-text": "#FFFFFF", + "fact-fill": "#C6CCD2", + "attack-fill": "#ED96AC", + "mitigation-fill": "#ABD2FA", + "goal-fill": "#DB2955", + "goal-text": "#FFFFFF", + }, + "default": { + "edge": "#2B303A", + "edge-text": "#010065", + "backwards-edge": "#7692FF", + "reality-fill": "#272727", + "reality-text": "#FFFFFF", + "fact-fill": "#D2D5DD", + "attack-fill": "#ffa6d5", + "mitigation-fill": "#B9D6F2", + "goal-fill": "#5f00c2", + "goal-text": "#FFFFFF", + }, +} + + + + +function convertToDot(yaml) { + const parsed = jsyaml.load(yaml); + const font = 'Arial' + const theme = themes[Object.hasOwnProperty.call(themes, parsed.theme) ? parsed.theme : "default"]; + + const title_attacktree = document.getElementById("id_title"); + + + + const header = `// Generated from https://swagitda.com/deciduous/ +digraph { +// base graph styling +rankdir="TB"; +splines=true; +overlap=false; +nodesep="0.2"; +ranksep="0.4"; +//label=${title_attacktree.value}; +labelloc="t"; +fontname=${JSON.stringify(font)}; +node [ shape="plaintext" style="filled, rounded" fontname=${JSON.stringify(font)} margin=0.2 ] +edge [ fontname=${JSON.stringify(font)} fontsize=12 color="${theme["edge"]}" ] + +// is reality a hologram? +reality [ label="Reality" fillcolor="${theme["reality-fill"]}" fontcolor="${theme["reality-text"]}" ] + +`; + const goals = parsed.goals || []; + const facts = parsed.facts || []; + const attacks = parsed.attacks || []; + const mitigations = parsed.mitigations || []; + const filter = parsed.filter || []; + const subgraphs = []; + const forwards = {}; + const forwardsAll = {}; + const backwards = {}; + const allNodes = [...facts, ...attacks, ...mitigations, ...goals]; + const types = {}; + for (const node of allNodes) { + const [toName] = Object.entries(node)[0]; + const fromNames = backwards[toName] || (backwards[toName] = []); + if (node.from) { + for (const from of node.from) { + const [fromName, label, props] = parseFrom(from); + if (!from.backwards && !from.ungrouped) { + const toNames = forwards[fromName] || (forwards[fromName] = []); + toNames.push(toName); + fromNames.push(fromName); + } + const toNames = forwardsAll[fromName] || (forwardsAll[fromName] = []); + toNames.push(toName); + } + } + } + function anyDominates(forwards, d, n) { + // search to see if any nodes in d dominate n + // nodes dominate themselves + const search = []; + const added = {}; + for (const other of d) { + added[other] = true; + search.push(other); + } + while ((d = search.shift()) !== undefined) { + if (d === n) { + return true; + } + const others = forwards[d]; + if (others !== undefined) { + for (const other of others) { + if (!Object.hasOwnProperty.call(added, other)) { + added[other] = true; + search.push(other); + } + } + } + } + return false; + } + function shouldShow(n) { + if (filter.length == 0 || anyDominates(forwardsAll, filter, n)) { + return true; + } + const arrayN = [n]; + return filter.find(other => anyDominates(forwardsAll, arrayN, other)); + } + function defaultLabelForName(name) { + return name.replace(/_/g, " ").replace(/^[a-z]/, c => c.toUpperCase()); + } + function nodes(type, values, properties) { + const result = []; + for (const value of values) { + const [name, label] = Object.entries(value)[0]; + types[name] = type; + if (shouldShow(name)) { + result.push(line(mangleName(name), { + label: wordwrap(label === null ? defaultLabelForName(name) : label, 18), + ...properties, + })); + } + } + return result; + } + const allNodeLines = [ + `// facts`, + ...nodes("fact", facts, { + fillcolor: theme["fact-fill"], + }), + `// attacks`, + ...nodes("attack", attacks, { + fillcolor: theme["attack-fill"], + }), + `// mitigations`, + ...nodes("mitigation", mitigations, { + fillcolor: theme["mitigation-fill"], + }), + `// goals`, + ...nodes("goal", goals, { + fillcolor: theme["goal-fill"], + fontcolor: theme["goal-text"], + }) + ]; + function edges(entries, properties) { + return entries.reduce((edges, value) => { + const [name] = Object.entries(value)[0]; + if (!shouldShow(name)) { + return edges; + } + (value.from || []).forEach((from) => { + const [fromName, label, fromProps] = parseFrom(from); + if (!shouldShow(fromName)) { + return; + } + const props = { + ...properties, + }; + if (label !== null) { + props.xlabel = wordwrap(label, 20); + props.fontcolor = theme["edge-text"]; + } + if (typeof fromProps.implemented == "boolean" && !fromProps.implemented) { + props.style = "dotted"; + } + if (fromProps.backwards) { + props.style = "dotted"; + props.color = theme["backwards-edge"]; + props.weight = "0"; + } + edges.push(line(`${mangleName(fromName)} -> ${mangleName(name)}`, props)); + }); + return edges; + }, []); + } + const allEdgeLines = [...edges(goals, {}), ...edges(attacks, {}), ...edges(mitigations, {}), ...edges(facts, {})]; + const goalNames = goals.map((goal) => { + const [goalName] = Object.entries(goal)[0]; + return goalName; + }); + for (const [fromName, toNames] of Object.entries(forwards)) { + if (!shouldShow(fromName)) { + continue; + } + const copy = toNames.concat(); + const filteredToNames = []; + for (let i = 0; i < toNames.length; i++) { + copy.splice(i, 1); + if (!anyDominates(forwards, copy, toNames[i]) && goalNames.indexOf(toNames[i]) == -1 && shouldShow(toNames[i])) { + filteredToNames.push(toNames[i]); + } + copy.splice(i, 0, toNames[i]); + } + if (filteredToNames.length > 1) { + subgraphs.push(` subgraph ${mangleName(fromName)}_order { +rank=same; +${filteredToNames.map(toName => mangleName(toName) + ";").join("\n ")} +} +${line(filteredToNames.map(mangleName).join(" -> "), { style: "invis" })}`); + } + } + const shownGoals = goalNames.filter(shouldShow); + if (shownGoals > 1) { + + subgraphs.push(` subgraph goal_order { +rank=same; +${shownGoals.map(goalName => mangleName(goalName) + ";").join("\n ")} +}`); + subgraphs.push(" " + line(shownGoals.join(" -> "), { style: "invis" })); + } + subgraphs.push(` { rank=min; reality; }`); + for (const node of allNodes) { + const [toName] = Object.entries(node)[0]; + if (shouldShow(toName) && !forwards[toName] && shownGoals.indexOf(toName) === -1) { + for (const goalName of shownGoals) { + subgraphs.push(" " + line(mangleName(toName) + " -> " + mangleName(goalName), { style: "invis", weight: 0 })); + } + } + } + subgraphs.push(` { rank=max; ${shownGoals.map(goalName => mangleName(goalName) + "; ").join("")}}`); + const footer = "\n\n}\n"; + return [header + " " + allNodeLines.join("\n ") + "\n\n " + allEdgeLines.join("\n ") + "\n\n // subgraphs to give proper layout\n" + subgraphs.join("\n\n") + footer, title_attacktree.value, types]; +} + + + +const renderTarget = document.getElementById("renderTarget"); +const errorTarget = document.getElementById("errorTarget"); +const inputSource = document.getElementById("id_attacktree"); + +const downloadLink = document.getElementById("downloadLink"); +const downloadSvgLink = document.getElementById("downloadSvgLink"); + +const UpdateRenderTarget = document.getElementById("UpdateRenderTarget"); +const FindingTarget = document.getElementById("id_finding"); + + +window["@hpcc-js/wasm"].graphvizSync().then(graphviz => { + let lastInput = ""; + let lastObjectURL = ""; + let lastSvgObjectURL = ""; + let types = {}; + + + + function rerender() { + + const newInput = inputSource.value; + + if (newInput != lastInput) { + lastInput = newInput; + + + + try { + let dot, title; + [dot, title, types] = convertToDot(newInput); + + document.title = `Deciduous - Security Decision Tree Generator (${title})`; + const svg = graphviz.layout(dot, "svg", "dot"); + + renderTarget.innerHTML = svg; + + const svgElement = renderTarget.querySelector("svg"); + + if (svgElement) { + const scale = 0.75; + svgElement.setAttribute("width", parseInt(svgElement.getAttribute("width"), 10) * scale + "pt"); + svgElement.setAttribute("height", parseInt(svgElement.getAttribute("height"), 10) * scale + "pt"); + } + + + // Create a download link + if (window.File && URL.createObjectURL) { + // DOT + const file = new File([dot], "graph.dot", { + "type": "text/vnd.graphviz", + }); + downloadLink.download = title + ".dot"; + const newObjectURL = URL.createObjectURL(file); + downloadLink.href = newObjectURL; + if (lastObjectURL != "") { + URL.revokeObjectURL(lastObjectURL); + } + lastObjectURL = newObjectURL; + + // SVG + const svgFile = new File([svg], "graph.svg", { + "type": "image/svg+xml", + }); + downloadSvgLink.download = title + ".svg"; + const newSvgObjectURL = URL.createObjectURL(svgFile); + downloadSvgLink.href = newSvgObjectURL; + if (lastSvgObjectURL != "") { + URL.revokeObjectURL(lastSvgObjectURL); + } + lastSvgObjectURL = newSvgObjectURL; + } + + + // Add quick linky links + for (const title of renderTarget.querySelectorAll("title")) { + title.parentNode.style.cursor = "pointer"; + title.parentNode.addEventListener("click", () => { + const node = title.textContent; + const index = lastInput.indexOf("\n- " + node); + if (index != -1) { + inputSource.blur(); + inputSource.selectionEnd = inputSource.selectionStart = index + 3; + inputSource.focus(); + inputSource.selectionEnd = index + 3 + node.length; + + } + }, false); + } + + // Clear any error text + errorTarget.innerText = ""; + } catch (e) { + errorTarget.innerText = String(e); + } + } + } + + + function updateImage(){ + + const newInput = inputSource.value; + + try { + let dot, title; + [dot, title, types] = convertToDot(newInput); + + document.title = `Deciduous - Security Decision Tree Generator (${title})`; + const svg = graphviz.layout(dot, "svg", "dot"); + + renderTarget.innerHTML = svg; + + const svgElement = renderTarget.querySelector("svg"); + + if (svgElement) { + const scale = 0.75; + svgElement.setAttribute("width", parseInt(svgElement.getAttribute("width"), 10) * scale + "pt"); + svgElement.setAttribute("height", parseInt(svgElement.getAttribute("height"), 10) * scale + "pt"); + + document.getElementById("id_svg_file").value = svg; + } + + // Clear any error text + errorTarget.innerText = ""; + } catch (e) { + errorTarget.innerText = String(e); + } + + rerender(); + } + + + + inputSource.addEventListener("change", rerender, false); + inputSource.addEventListener("input", rerender, false); + + UpdateRenderTarget.addEventListener("click", updateImage, false); + + rerender(); + +}); \ No newline at end of file diff --git a/django/preport/forms.py b/django/preport/forms.py index 15927b1..80ec558 100644 --- a/django/preport/forms.py +++ b/django/preport/forms.py @@ -2,7 +2,7 @@ from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User, Group from django.forms import ModelForm, Textarea, TextInput, DateField, DateInput, ModelChoiceField, CheckboxInput, CheckboxSelectMultiple, PasswordInput, EmailField, BooleanField -from .models import DB_Report, DB_Finding, DB_Product, DB_Finding_Template, DB_Appendix, DB_CWE +from .models import DB_Report, DB_Finding, DB_Product, DB_Finding_Template, DB_Appendix, DB_CWE, DB_AttackTree from martor.fields import MartorFormField import datetime @@ -27,7 +27,6 @@ class NewReportForm(forms.ModelForm): product = ProductModelChoiceField(queryset=DB_Product.objects.all(), empty_label="(Select a product)", widget=forms.Select(attrs={'class': 'form-control'})) class Meta: - today = datetime.date.today().strftime('%Y-%m-%d') nowformat = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') model = DB_Report @@ -145,11 +144,32 @@ class Meta: widgets = { 'username': TextInput(attrs={'class': 'form-control', 'type': "text", 'required': "required", 'placeholder': "Username"}), - #'password1': PasswordInput(attrs={'class': 'form-control', 'required': "required", 'placeholder': "P@assW0rd"}), + #'password1': PasswordInput(attrs={'class': 'form-control', 'required': "required", 'placeholder': "P@ssW0rd"}), } def __init__(self, *args, **kwargs): super(AddUserForm, self).__init__(*args, **kwargs) self.fields['username'].widget.attrs.update({'class': 'form-control', 'type': "text", 'required': "required", 'placeholder': "Username"}) - self.fields['password1'].widget.attrs.update({'class': 'form-control', 'placeholder': 'Secret P@assW0rd'}) - self.fields['password2'].widget.attrs.update({'class': 'form-control', 'placeholder': 'Secret P@assW0rd'}) + self.fields['password1'].widget.attrs.update({'class': 'form-control', 'placeholder': 'Secret P@ssW0rd'}) + self.fields['password2'].widget.attrs.update({'class': 'form-control', 'placeholder': 'Secret P@ssW0rd'}) + + + +class NewAttackTreeForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + reportpk = kwargs.pop('reportpk') + super(NewAttackTreeForm, self).__init__(*args, **kwargs) + + DB_finding_query = DB_Finding.objects.filter(report=reportpk) + + self.fields["finding"] = FindingModelChoiceField(queryset=DB_finding_query, empty_label="(Select a finding)", widget=forms.Select(attrs={'class': 'form-control'})) + + class Meta: + model = DB_AttackTree + fields = ('finding', 'title', 'attacktree', 'svg_file') + + widgets = { + 'title': TextInput(attrs={'class': 'form-control', 'type': "text", 'required': "required", 'placeholder': "Title"}), + 'attacktree': Textarea(attrs={'class': 'form-control', 'rows': "20", 'required': "required", 'placeholder': "Attack Tree"}), + } \ No newline at end of file diff --git a/django/preport/migrations/0002_auto_20210909_1050.py b/django/preport/migrations/0002_auto_20210909_1050.py new file mode 100644 index 0000000..c62a220 --- /dev/null +++ b/django/preport/migrations/0002_auto_20210909_1050.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.5 on 2021-09-09 10:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preport', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='db_appendix', + name='title', + field=models.CharField(max_length=200), + ), + migrations.AlterField( + model_name='db_finding_template', + name='finding_id', + field=models.CharField(max_length=200), + ), + migrations.AlterField( + model_name='db_finding_template', + name='title', + field=models.CharField(max_length=200), + ), + migrations.CreateModel( + name='DB_AttackTree', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('attacktree', models.TextField(blank=True, null=True)), + ('svg_file', models.TextField(blank=True, null=True)), + ('finding', models.ManyToManyField(blank=True, related_name='attacktree_finding', to='preport.DB_Finding')), + ], + ), + ] diff --git a/django/preport/models.py b/django/preport/models.py index e848889..ad2109d 100644 --- a/django/preport/models.py +++ b/django/preport/models.py @@ -55,8 +55,8 @@ class DB_Finding(models.Model): # ---------- Finding templates ------------ class DB_Finding_Template(models.Model): - finding_id = models.CharField(blank=True, max_length=200) - title = models.CharField(blank=True, max_length=200) + finding_id = models.CharField(blank=False, max_length=200) + title = models.CharField(blank=False, max_length=200) severity = models.CharField(blank=True, max_length=200) cvss_base_score = models.CharField(blank=True, max_length=200) cvss_score = models.DecimalField(max_digits=3, decimal_places=1, default=0) @@ -71,7 +71,14 @@ class DB_Finding_Template(models.Model): class DB_Appendix(models.Model): finding = models.ManyToManyField(DB_Finding, related_name='appendix_finding', blank=True) - title = models.CharField(blank=True, max_length=200) + title = models.CharField(blank=False, max_length=200) description = MartorField() +# ---------- Attack Tree ------------ + +class DB_AttackTree(models.Model): + finding = models.ManyToManyField(DB_Finding, related_name='attacktree_finding', blank=True) + title = models.CharField(blank=False, max_length=200) + attacktree = models.TextField(blank=True, null=True) + svg_file = models.TextField(blank=True, null=True) diff --git a/django/preport/templates/attacktree/attacktree_add.html b/django/preport/templates/attacktree/attacktree_add.html new file mode 100644 index 0000000..d5b3e61 --- /dev/null +++ b/django/preport/templates/attacktree/attacktree_add.html @@ -0,0 +1,164 @@ +{% extends 'home/template.html' %} + +{% block title %} Attack Tree {% endblock title %} + +{% block stylesheets %} + {{ block.super }} + +{% endblock stylesheets %} + +{% block content %} + + +
+
+
+
+

Report: {{DB_report_query.title}} - {{ DB_report_query.report_id }}

+
+ +
+
+
+ + +
+
+
+ +
+ +
+
+

Security decision trees

+
+ +
+

+ The visualization of the attack path of a vulnerability or finding has been implemented adapting a web app that simplifies building attack decision trees as described in the Security Chaos Engineering report: Deciduous +

+ +

+ So all the credits to @swagitda. How to / getting started guide: https://swagitda.com/blog/posts/deciduous-attack-tree-app/ +

+ +

+ Also if needed to attach an Attack Path Planner I recommend you to take a look into: Walter: Attack Path Planner +

+ +
+
+ +
+
+
+ + +
+
+
+ +
+ +
+
+

Attack Tree

+
+ +
+ {% csrf_token %} +
+ +
+ +
+ {{ form.finding }} +
+
+ +
+ +
+ {{ form.title }} +
+
+ +
+ +
+ {{ form.attacktree }} +
+
+ +
+
+ {{ form.svg_file.as_hidden }} +
+
+ + +
+ + +
+
+ +
+ + +
+
+
+ + + + +
+
+
+ +
+
+
+

Attack Tree

+
+ +
+
+
+
+
+
+ +
+
+
+ + + +{% endblock content %} + +{% block javascripts %} + + {{ block.super}} + + + + + + +{% endblock javascripts %} + + + + diff --git a/django/preport/templates/attacktree/attacktree_view.html b/django/preport/templates/attacktree/attacktree_view.html new file mode 100644 index 0000000..3c3f9b5 --- /dev/null +++ b/django/preport/templates/attacktree/attacktree_view.html @@ -0,0 +1,104 @@ +{% extends 'home/template.html' %} + +{% load martortags %} + +{% block title %} Attack Tree Details {% endblock title %} + +{% block stylesheets %} + {{ block.super }} +{% endblock stylesheets %} + +{% block content %} + + +
+
+
+
+

Attack Tree

+
+ +
+
+
+ + +
+
+ +
+
+ +
+
+

+ Title +

+
+ +
+ +
+
+ {{ DB_attacktree.title|safe_markdown }} +
+
+ +
+
+ + +
+
+
+
+

+ Attack Tree +

+
+ +
+ +
+
+ +
+ {{ DB_attacktree.svg_file|safe }} +
+ +
+
+
+
+ +
+
+ + + +{% endblock content %} + +{% block javascripts %} + {{ block.super }} + +{% endblock javascripts %} + + + + + + + + diff --git a/django/preport/templates/attacktree/reportattacktree.html b/django/preport/templates/attacktree/reportattacktree.html new file mode 100644 index 0000000..510f113 --- /dev/null +++ b/django/preport/templates/attacktree/reportattacktree.html @@ -0,0 +1,165 @@ +{% extends 'home/template.html' %} + +{% load martortags %} + +{% block title %} Attack Tree {% endblock title %} + +{% block stylesheets %} + {{ block.super }} +{% endblock stylesheets %} + +{% block content %} + + +
+
+
+ +
+

{{DB_report_query.title}} Attack Trees

+
+ +
+ +
+ + {% if user.groups.all.0|stringformat:'s' == "administrator" %} + + {% endif %} + +
+
+ + + + + +
+
+ + +
+
+ +
+
+

+ {{ count_attacktree_query }} Attack Tree +

+
+ +
+ +
+
+ + + + + + + + + + + + + {% for attacktree in DB_attacktree_query %} + + + + + + + + + + + + + + {% endfor %} + + +
Attack TreeFinding Actions
+ {{attacktree.title}} + + {% for finding in attacktree.finding.all %} + {{finding.title}} + {% endfor %} + + + + {% if user.groups.all.0|stringformat:'s' == "administrator" %} + + + {% endif %} + +
+ + +
+
+ +
+
+ + + +
+
+ + +{% endblock content %} + +{% block javascripts %} + {{ block.super }} + + + +{% endblock javascripts %} + + + + + + + + diff --git a/django/preport/templates/findings/finding_view.html b/django/preport/templates/findings/finding_view.html index 415f6e9..7290522 100644 --- a/django/preport/templates/findings/finding_view.html +++ b/django/preport/templates/findings/finding_view.html @@ -319,7 +319,41 @@

{% for appendix in DB_appendix %} -

{{appendix.title}}

+

{{ appendix.title|safe_markdown }}

+

{{ appendix.description|safe_markdown }}

+
+ {% endfor %} +
+ + + + {% endif %} + + + {% if DB_attacktree %} + +
+
+ +
+
+

+ Attack Tree +

+
+ +
+ +
+
+ {% for attacktree in DB_attacktree %} +

{{ attacktree.title|safe_markdown }}

+
+

{{ attacktree.svg_file|safe }}

+
+
{% endfor %}
diff --git a/django/preport/templates/home/aside.html b/django/preport/templates/home/aside.html index cb71b6d..7c7473f 100644 --- a/django/preport/templates/home/aside.html +++ b/django/preport/templates/home/aside.html @@ -83,17 +83,39 @@ - {% if user.groups.all.0|stringformat:'s' == "administrator" %} + + + + + {% if user.groups.all.0|stringformat:'s' == "administrator" %} + {% endif %} + + +