dradis-calculator_mitre 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE +339 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/app/assets/data/dradis/plugins/calculators/mitre/mitre_data.json +6066 -0
- data/app/assets/javascripts/dradis/plugins/calculators/mitre/base.js +5 -0
- data/app/assets/javascripts/dradis/plugins/calculators/mitre/manifests/hera.js +1 -0
- data/app/assets/javascripts/dradis/plugins/calculators/mitre/mitre_calculator.js.erb +283 -0
- data/app/assets/stylesheets/dradis/plugins/calculators/mitre/base.css.scss +10 -0
- data/app/controllers/dradis/plugins/calculators/mitre/base_controller.rb +9 -0
- data/app/controllers/dradis/plugins/calculators/mitre/issues_controller.rb +29 -0
- data/app/models/dradis/plugins/calculators/mitre/v1.rb +26 -0
- data/app/views/dradis/plugins/calculators/mitre/_ce_tools_menu.html.erb +3 -0
- data/app/views/dradis/plugins/calculators/mitre/_tools_menu.html.erb +5 -0
- data/app/views/dradis/plugins/calculators/mitre/base/_v1.html.erb +67 -0
- data/app/views/dradis/plugins/calculators/mitre/base/index.html.erb +5 -0
- data/app/views/dradis/plugins/calculators/mitre/issues/_show-content.html.erb +20 -0
- data/app/views/dradis/plugins/calculators/mitre/issues/_show-tabs.html.erb +3 -0
- data/app/views/dradis/plugins/calculators/mitre/issues/edit.html.erb +32 -0
- data/app/views/layouts/dradis/plugins/calculators/mitre/base.html.erb +28 -0
- data/config/routes.rb +12 -0
- data/lib/dradis/plugins/calculators/mitre/engine.rb +36 -0
- data/lib/dradis/plugins/calculators/mitre/gem_version.rb +21 -0
- data/lib/dradis/plugins/calculators/mitre/version.rb +15 -0
- data/lib/dradis-calculator_mitre.rb +13 -0
- metadata +109 -0
@@ -0,0 +1 @@
|
|
1
|
+
//= require dradis/plugins/calculators/mitre/mitre_calculator
|
@@ -0,0 +1,283 @@
|
|
1
|
+
document.addEventListener('turbo:load', () => {
|
2
|
+
if (!document.querySelector('[data-behavior~=mitre-calc]')) return;
|
3
|
+
|
4
|
+
class MitreCalculator {
|
5
|
+
constructor() {
|
6
|
+
this.matrices = ['enterprise', 'mobile', 'ics'];
|
7
|
+
this.mitreData = {};
|
8
|
+
this.result = document.querySelector('[data-behavior="mitre-result"]');
|
9
|
+
this.selects = {};
|
10
|
+
this.init();
|
11
|
+
}
|
12
|
+
|
13
|
+
async init() {
|
14
|
+
try {
|
15
|
+
await this.loadMitreData();
|
16
|
+
this.initializeSelectors();
|
17
|
+
this.setupEventListeners();
|
18
|
+
this.preSelectFromResult();
|
19
|
+
} catch (error) {
|
20
|
+
console.error('Failed to initialize MITRE Calculator:', error);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
async loadMitreData() {
|
25
|
+
const response = await fetch("<%= asset_path('dradis/plugins/calculators/mitre/mitre_data.json') %>");
|
26
|
+
if (!response.ok)
|
27
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
28
|
+
this.mitreData = await response.json();
|
29
|
+
}
|
30
|
+
|
31
|
+
initializeSelectors() {
|
32
|
+
this.matrices.forEach((matrix) => {
|
33
|
+
const tacticSelect = document.querySelector(
|
34
|
+
`select[data-type="${matrix}-tactic"]`
|
35
|
+
);
|
36
|
+
const techniqueSelect = document.querySelector(
|
37
|
+
`select[data-type="${matrix}-technique"]`
|
38
|
+
);
|
39
|
+
const subtechniqueSelect = document.querySelector(
|
40
|
+
`select[data-type="${matrix}-subtechnique"]`
|
41
|
+
);
|
42
|
+
|
43
|
+
this.selects[matrix] = {
|
44
|
+
tactic: tacticSelect,
|
45
|
+
technique: techniqueSelect,
|
46
|
+
subtechnique: subtechniqueSelect,
|
47
|
+
};
|
48
|
+
|
49
|
+
this.setupMatrix(matrix);
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
setupMatrix(matrix) {
|
54
|
+
const { tactic, technique, subtechnique } = this.selects[matrix];
|
55
|
+
|
56
|
+
this.setPrompt(tactic, 'Select a tactic');
|
57
|
+
this.setPrompt(technique, 'Select a technique');
|
58
|
+
this.setPrompt(subtechnique, 'Select a sub-technique');
|
59
|
+
technique.disabled = true;
|
60
|
+
subtechnique.disabled = true;
|
61
|
+
|
62
|
+
this.mitreData[matrix].tactics.forEach((tacticData) => {
|
63
|
+
const option = document.createElement('option');
|
64
|
+
option.value = tacticData.id;
|
65
|
+
option.textContent = tacticData.name;
|
66
|
+
tactic.appendChild(option);
|
67
|
+
});
|
68
|
+
}
|
69
|
+
|
70
|
+
setupEventListeners() {
|
71
|
+
this.matrices.forEach((matrix) => {
|
72
|
+
const { tactic, technique, subtechnique } = this.selects[matrix];
|
73
|
+
|
74
|
+
tactic.addEventListener('change', () => {
|
75
|
+
this.handleTacticChange(matrix);
|
76
|
+
});
|
77
|
+
|
78
|
+
technique.addEventListener('change', () => {
|
79
|
+
this.handleTechniqueChange(matrix);
|
80
|
+
});
|
81
|
+
|
82
|
+
subtechnique.addEventListener('change', () => {
|
83
|
+
this.handleSubtechniqueChange(matrix);
|
84
|
+
});
|
85
|
+
});
|
86
|
+
}
|
87
|
+
|
88
|
+
handleTacticChange(matrix) {
|
89
|
+
const { tactic, technique, subtechnique } = this.selects[matrix];
|
90
|
+
const selectedTactic = this.mitreData[matrix].tactics.find(
|
91
|
+
(t) => t.id === tactic.value
|
92
|
+
);
|
93
|
+
|
94
|
+
this.setPrompt(technique, 'Select a technique');
|
95
|
+
this.setPrompt(subtechnique, 'Select a sub-technique');
|
96
|
+
technique.disabled = true;
|
97
|
+
subtechnique.disabled = true;
|
98
|
+
|
99
|
+
if (selectedTactic) {
|
100
|
+
this.populateTechniques(selectedTactic, technique);
|
101
|
+
technique.disabled = false;
|
102
|
+
}
|
103
|
+
|
104
|
+
this.updateTacticResults(matrix, selectedTactic);
|
105
|
+
}
|
106
|
+
|
107
|
+
handleTechniqueChange(matrix) {
|
108
|
+
const { tactic, technique, subtechnique } = this.selects[matrix];
|
109
|
+
const selectedTactic = this.mitreData[matrix].tactics.find(
|
110
|
+
(t) => t.id === tactic.value
|
111
|
+
);
|
112
|
+
const selectedTechnique = selectedTactic.techniques.find(
|
113
|
+
(tech) => tech.id === technique.value
|
114
|
+
);
|
115
|
+
|
116
|
+
this.setPrompt(subtechnique, 'Select a sub-technique');
|
117
|
+
subtechnique.disabled = true;
|
118
|
+
|
119
|
+
if (selectedTechnique.subtechniques.length > 0) {
|
120
|
+
this.populateSubtechniques(selectedTechnique, subtechnique);
|
121
|
+
subtechnique.disabled = false;
|
122
|
+
}
|
123
|
+
|
124
|
+
this.updateTechniqueResults(matrix, selectedTechnique);
|
125
|
+
}
|
126
|
+
|
127
|
+
handleSubtechniqueChange(matrix) {
|
128
|
+
const { tactic, technique, subtechnique } = this.selects[matrix];
|
129
|
+
const selectedTactic = this.mitreData[matrix].tactics.find(
|
130
|
+
(t) => t.id === tactic.value
|
131
|
+
);
|
132
|
+
const selectedTechnique = selectedTactic.techniques.find(
|
133
|
+
(tech) => tech.id === technique.value
|
134
|
+
);
|
135
|
+
const selectedSubtechnique = selectedTechnique.subtechniques.find(
|
136
|
+
(s) => s.id === subtechnique.value
|
137
|
+
);
|
138
|
+
|
139
|
+
this.updateSubtechniqueResults(matrix, selectedSubtechnique);
|
140
|
+
}
|
141
|
+
|
142
|
+
preSelectFromResult() {
|
143
|
+
this.matrices.forEach((matrix) => {
|
144
|
+
this.preSelectMatrix(matrix);
|
145
|
+
});
|
146
|
+
}
|
147
|
+
|
148
|
+
preSelectMatrix(matrix) {
|
149
|
+
const base = `MITRE.${this.titleCase(matrix)}`;
|
150
|
+
const tacticId = this.getResultValue(`${base}.Tactic.ID`);
|
151
|
+
|
152
|
+
if (!tacticId || tacticId === 'N/A') return;
|
153
|
+
|
154
|
+
const { tactic, technique, subtechnique } = this.selects[matrix];
|
155
|
+
|
156
|
+
if (this.selectOption(tactic, tacticId)) {
|
157
|
+
const selectedTactic = this.mitreData[matrix].tactics.find(
|
158
|
+
(t) => t.id === tacticId
|
159
|
+
);
|
160
|
+
this.populateTechniques(selectedTactic, technique);
|
161
|
+
technique.disabled = false;
|
162
|
+
|
163
|
+
const techniqueId = this.getResultValue(`${base}.Technique.ID`);
|
164
|
+
if (
|
165
|
+
techniqueId &&
|
166
|
+
techniqueId !== 'N/A' &&
|
167
|
+
this.selectOption(technique, techniqueId)
|
168
|
+
) {
|
169
|
+
const selectedTechnique = selectedTactic.techniques.find(
|
170
|
+
(t) => t.id === techniqueId
|
171
|
+
);
|
172
|
+
|
173
|
+
if (selectedTechnique.subtechniques.length > 0) {
|
174
|
+
this.populateSubtechniques(selectedTechnique, subtechnique);
|
175
|
+
subtechnique.disabled = false;
|
176
|
+
|
177
|
+
const subtechniqueId = this.getResultValue(
|
178
|
+
`${base}.Sub-technique.ID`
|
179
|
+
);
|
180
|
+
if (subtechniqueId && subtechniqueId !== 'N/A') {
|
181
|
+
this.selectOption(subtechnique, subtechniqueId);
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
getResultValue(label) {
|
189
|
+
// Captures the line with the label and returns its value
|
190
|
+
const regex = new RegExp(
|
191
|
+
`\\#\\[${this.escapeRegex(label)}\\]\\#\\n(.*?)(?=\\n|$)`,
|
192
|
+
'i'
|
193
|
+
);
|
194
|
+
const match = this.result.value.match(regex);
|
195
|
+
return match ? match[1].trim() : null;
|
196
|
+
}
|
197
|
+
|
198
|
+
selectOption(select, value) {
|
199
|
+
const option = select.querySelector(`option[value="${value}"]`);
|
200
|
+
if (option) {
|
201
|
+
select.value = value;
|
202
|
+
return true;
|
203
|
+
}
|
204
|
+
return false;
|
205
|
+
}
|
206
|
+
|
207
|
+
escapeRegex(string) {
|
208
|
+
// Escapes special characters in a regex string since . and [] have special meanings
|
209
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
210
|
+
}
|
211
|
+
|
212
|
+
setPrompt(select, prompt) {
|
213
|
+
select.innerHTML = `<option value="" disabled selected>${prompt}</option>`;
|
214
|
+
}
|
215
|
+
|
216
|
+
populateTechniques(tactic, select) {
|
217
|
+
tactic.techniques.forEach((tech) => {
|
218
|
+
const option = document.createElement('option');
|
219
|
+
option.value = tech.id;
|
220
|
+
option.textContent = tech.name;
|
221
|
+
select.appendChild(option);
|
222
|
+
});
|
223
|
+
}
|
224
|
+
|
225
|
+
populateSubtechniques(technique, select) {
|
226
|
+
technique.subtechniques.forEach((sub) => {
|
227
|
+
const option = document.createElement('option');
|
228
|
+
option.value = sub.id;
|
229
|
+
option.textContent = sub.name;
|
230
|
+
select.appendChild(option);
|
231
|
+
});
|
232
|
+
}
|
233
|
+
|
234
|
+
updateResult(label, value) {
|
235
|
+
// Captures the line with the label and replaces its value
|
236
|
+
const regex = new RegExp(`(\\#\\[${label}\\]\\#\\n)(.*?)(\\n|$)`, 'gi');
|
237
|
+
this.result.value = this.result.value.replace(regex, `$1${value}$3`);
|
238
|
+
}
|
239
|
+
|
240
|
+
updateTacticResults(matrix, tactic) {
|
241
|
+
const base = `MITRE.${this.titleCase(matrix)}`;
|
242
|
+
this.updateResult(`${base}.Tactic`, tactic.name);
|
243
|
+
this.updateResult(`${base}.Tactic.ID`, tactic.id);
|
244
|
+
this.resetTechniqueAndSubtechniqueResults(matrix);
|
245
|
+
}
|
246
|
+
|
247
|
+
updateTechniqueResults(matrix, technique) {
|
248
|
+
const base = `MITRE.${this.titleCase(matrix)}`;
|
249
|
+
this.updateResult(`${base}.Technique`, technique.name);
|
250
|
+
this.updateResult(`${base}.Technique.ID`, technique.id);
|
251
|
+
this.resetSubtechniqueResults(matrix);
|
252
|
+
}
|
253
|
+
|
254
|
+
updateSubtechniqueResults(matrix, subtechnique) {
|
255
|
+
const base = `MITRE.${this.titleCase(matrix)}`;
|
256
|
+
this.updateResult(`${base}.Sub-technique`, subtechnique.name);
|
257
|
+
this.updateResult(`${base}.Sub-technique.ID`, subtechnique.id);
|
258
|
+
}
|
259
|
+
|
260
|
+
resetTechniqueAndSubtechniqueResults(matrix) {
|
261
|
+
this.resetTechniqueResults(matrix);
|
262
|
+
this.resetSubtechniqueResults(matrix);
|
263
|
+
}
|
264
|
+
|
265
|
+
resetTechniqueResults(matrix) {
|
266
|
+
const base = `MITRE.${this.titleCase(matrix)}`;
|
267
|
+
this.updateResult(`${base}.Technique`, 'N/A');
|
268
|
+
this.updateResult(`${base}.Technique.ID`, 'N/A');
|
269
|
+
}
|
270
|
+
|
271
|
+
resetSubtechniqueResults(matrix) {
|
272
|
+
const base = `MITRE.${this.titleCase(matrix)}`;
|
273
|
+
this.updateResult(`${base}.Sub-technique`, 'N/A');
|
274
|
+
this.updateResult(`${base}.Sub-technique.ID`, 'N/A');
|
275
|
+
}
|
276
|
+
|
277
|
+
titleCase(str) {
|
278
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
new MitreCalculator();
|
283
|
+
});
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dradis::Plugins::Calculators::MITRE
|
2
|
+
class IssuesController < ::IssuesController
|
3
|
+
|
4
|
+
before_action only: :edit
|
5
|
+
|
6
|
+
def edit
|
7
|
+
@issue_fields = Dradis::Plugins::Calculators::MITRE::V1::FIELDS.map do |field|
|
8
|
+
value = @issue.fields[field]
|
9
|
+
value = 'N/A' if value.blank?
|
10
|
+
"#[#{field}]#\n#{value}"
|
11
|
+
end.join("\n\n")
|
12
|
+
end
|
13
|
+
|
14
|
+
def update
|
15
|
+
raw = params[:mitre_fields].to_s
|
16
|
+
mitre_fields = Hash[*raw.scan(FieldParser::FIELDS_REGEX).flatten.map(&:strip)]
|
17
|
+
|
18
|
+
mitre_fields.each do |name, value|
|
19
|
+
@issue.set_field(name, value)
|
20
|
+
end
|
21
|
+
|
22
|
+
if @issue.save
|
23
|
+
redirect_to main_app.project_issue_path(current_project, @issue), notice: 'MITRE fields updated.'
|
24
|
+
else
|
25
|
+
render :edit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Dradis::Plugins::Calculators::MITRE
|
2
|
+
class V1
|
3
|
+
FIELD_NAMES = %i[
|
4
|
+
Enterprise.Tactic
|
5
|
+
Enterprise.Tactic.ID
|
6
|
+
Enterprise.Technique
|
7
|
+
Enterprise.Technique.ID
|
8
|
+
Enterprise.Sub-technique
|
9
|
+
Enterprise.Sub-technique.ID
|
10
|
+
Mobile.Tactic
|
11
|
+
Mobile.Tactic.ID
|
12
|
+
Mobile.Technique
|
13
|
+
Mobile.Technique.ID
|
14
|
+
Mobile.Sub-technique
|
15
|
+
Mobile.Sub-technique.ID
|
16
|
+
ICS.Tactic
|
17
|
+
ICS.Tactic.ID
|
18
|
+
ICS.Technique
|
19
|
+
ICS.Technique.ID
|
20
|
+
ICS.Sub-technique
|
21
|
+
ICS.Sub-technique.ID
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
FIELDS = FIELD_NAMES.map { |name| "MITRE.#{name}".freeze }.freeze
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
<div data-behavior="mitre-calc">
|
2
|
+
<ul class="nav nav-pills w-100">
|
3
|
+
<li class="nav-item">
|
4
|
+
<a href="#enterprise" class="nav-link active" data-bs-toggle="tab"><strong>Enterprise</strong></a>
|
5
|
+
</li>
|
6
|
+
<li class="nav-item">
|
7
|
+
<a href="#mobile" class="nav-link" data-bs-toggle="tab"><strong>Mobile</strong></a>
|
8
|
+
</li>
|
9
|
+
<li class="nav-item">
|
10
|
+
<a href="#ics" class="nav-link" data-bs-toggle="tab"><strong>ICS</strong></a>
|
11
|
+
</li>
|
12
|
+
</ul>
|
13
|
+
|
14
|
+
<div class="row mt-3">
|
15
|
+
<div class="col-md-6">
|
16
|
+
<div class="tab-content">
|
17
|
+
<div id="enterprise" class="tab-pane active show">
|
18
|
+
<div class="mb-4">
|
19
|
+
<label class="form-label" for="enterprise-tactic-select">Enterprise Tactic</label>
|
20
|
+
<select id="enterprise-tactic-select" class="form-select" data-type="enterprise-tactic" data-combobox-config="no-combobox"></select>
|
21
|
+
</div>
|
22
|
+
<div class="mb-4">
|
23
|
+
<label class="form-label" for="enterprise-technique-select">Enterprise Technique</label>
|
24
|
+
<select id="enterprise-technique-select" class="form-select" data-type="enterprise-technique" data-combobox-config="no-combobox"></select>
|
25
|
+
</div>
|
26
|
+
<div class="mb-1">
|
27
|
+
<label class="form-label" for="enterprise-subtechnique-select">Enterprise Sub-Technique</label>
|
28
|
+
<select id="enterprise-subtechnique-select" class="form-select" data-type="enterprise-subtechnique" data-combobox-config="no-combobox"></select>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
<div id="mobile" class="tab-pane">
|
32
|
+
<div class="mb-4">
|
33
|
+
<label class="form-label" for="mobile-tactic-select">Mobile Tactic</label>
|
34
|
+
<select id="mobile-tactic-select" class="form-select" data-type="mobile-tactic" data-combobox-config="no-combobox"></select>
|
35
|
+
</div>
|
36
|
+
<div class="mb-4">
|
37
|
+
<label class="form-label" for="mobile-technique-select">Mobile Technique</label>
|
38
|
+
<select id="mobile-technique-select" class="form-select" data-type="mobile-technique" data-combobox-config="no-combobox"></select>
|
39
|
+
</div>
|
40
|
+
<div class="mb-1">
|
41
|
+
<label class="form-label" for="mobile-subtechnique-select">Mobile Sub-Technique</label>
|
42
|
+
<select id="mobile-subtechnique-select" class="form-select" data-type="mobile-subtechnique" data-combobox-config="no-combobox"></select>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
<div id="ics" class="tab-pane">
|
46
|
+
<div class="mb-4">
|
47
|
+
<label class="form-label" for="ics-tactic-select">ICS Tactic</label>
|
48
|
+
<select id="ics-tactic-select" class="form-select" data-type="ics-tactic" data-combobox-config="no-combobox"></select>
|
49
|
+
</div>
|
50
|
+
<div class="mb-4">
|
51
|
+
<label class="form-label" for="ics-technique-select">ICS Technique</label>
|
52
|
+
<select id="ics-technique-select" class="form-select" data-type="ics-technique" data-combobox-config="no-combobox"></select>
|
53
|
+
</div>
|
54
|
+
<div class="mb-1">
|
55
|
+
<label class="form-label" for="ics-subtechnique-select">ICS Sub-Technique</label>
|
56
|
+
<select id="ics-subtechnique-select" class="form-select" data-type="ics-subtechnique" data-combobox-config="no-combobox"></select>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div class="col-md-6 d-flex flex-column">
|
63
|
+
<label for="mitre_fields" class="form-label">Result</label>
|
64
|
+
<%= text_area_tag :mitre_fields, @issue_fields, class: 'form-control flex-grow-1', data: { behavior: 'mitre-result' } %>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
</div>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div class="tab-pane" id="mitre-tab">
|
2
|
+
<div class="inner">
|
3
|
+
<h4 class="header-underline">MITRE ATT&CK -
|
4
|
+
<span class="actions">
|
5
|
+
<%= link_to mitre_calculator.mitre_project_issue_path(current_project, @issue) do %>
|
6
|
+
<i class="fa-solid fa-pencil"></i> Edit
|
7
|
+
<% end %>
|
8
|
+
</h4>
|
9
|
+
|
10
|
+
<div class="mb-4 content-textile">
|
11
|
+
<%=
|
12
|
+
markup(
|
13
|
+
@issue.fields
|
14
|
+
.select { |k,v| Dradis::Plugins::Calculators::MITRE::V1::FIELDS.include?(k) }
|
15
|
+
.map { |k,v| "#[#{k}]#\n#{v}" }.join("\n\n")
|
16
|
+
)
|
17
|
+
%>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</div>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<% content_for :title, 'Edit MITRE' %>
|
2
|
+
|
3
|
+
<% content_for :sidebar do %>
|
4
|
+
<%= render 'issues/sidebar' %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<ol class="breadcrumb">
|
8
|
+
<li class="breadcrumb-item">
|
9
|
+
<%= link_to current_project.name, main_app.project_path(current_project) %>
|
10
|
+
</li>
|
11
|
+
<li class="breadcrumb-item">
|
12
|
+
<%= link_to 'All issues', main_app.project_issues_path(current_project) %>
|
13
|
+
</li>
|
14
|
+
<li class="breadcrumb-item">
|
15
|
+
<%= link_to @issue.title? ? @issue.title : "Issue ##{@issue.id}", main_app.project_issue_path(current_project, @issue) %>
|
16
|
+
</li>
|
17
|
+
<li class="breadcrumb-item active">
|
18
|
+
MITRE ATT&CK
|
19
|
+
</li>
|
20
|
+
</ol>
|
21
|
+
|
22
|
+
<div class="content-container">
|
23
|
+
<h4 class="header-underline mb-2">Edit MITRE ATT&CK</h4>
|
24
|
+
|
25
|
+
<%= simple_form_for [:mitre, current_project, @issue], method: :patch do |f| %>
|
26
|
+
<%= render 'dradis/plugins/calculators/mitre/base/v1' %>
|
27
|
+
<div class="form-actions mt-2">
|
28
|
+
<%= f.button :submit, nil, class: 'btn btn-primary' %> or
|
29
|
+
<%= link_to 'Cancel', main_app.project_issue_path(current_project, @issue), class: 'cancel-link' %>
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
</div>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>MITRE ATT&CK Calculator | Dradis Framework</title>
|
5
|
+
<%= stylesheet_link_tag 'dradis/plugins/calculators/mitre/base', media: 'all', 'data-turbo-track': 'reload' %>
|
6
|
+
<%= javascript_include_tag 'dradis/plugins/calculators/mitre/base', 'data-turbo-track': 'reload' %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
<%= javascript_importmap_tags %>
|
9
|
+
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
11
|
+
</head>
|
12
|
+
<body class="authenticated">
|
13
|
+
<div class="container">
|
14
|
+
<nav class="navbar">
|
15
|
+
<a href="javascript:void(0)" class="navbar-brand">MITRE ATT&CK Calculator</a>
|
16
|
+
<ul class="navbar-nav pull-right">
|
17
|
+
<li class="nav-item">
|
18
|
+
<%= link_to main_app.root_path, class: 'nav-link', data: { turbo: false } do %>
|
19
|
+
Back to the app →
|
20
|
+
<% end %>
|
21
|
+
</li>
|
22
|
+
</ul>
|
23
|
+
</nav>
|
24
|
+
|
25
|
+
<%= yield %>
|
26
|
+
</div>
|
27
|
+
</body>
|
28
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Dradis::Plugins::Calculators::MITRE::Engine.routes.draw do
|
2
|
+
get '/calculators/mitre' => 'base#index'
|
3
|
+
|
4
|
+
resources :projects, only: [] do
|
5
|
+
resources :issues, only: [] do
|
6
|
+
member do
|
7
|
+
get 'mitre' => 'issues#edit'
|
8
|
+
patch 'mitre' => 'issues#update'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Dradis::Plugins::Calculators::MITRE
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Dradis::Plugins::Calculators::MITRE
|
4
|
+
|
5
|
+
include Dradis::Plugins::Base
|
6
|
+
provides :addon
|
7
|
+
description 'Risk Calculators: MITRE'
|
8
|
+
|
9
|
+
initializer 'calculator_mitre.asset_precompile_paths' do |app|
|
10
|
+
app.config.assets.precompile += [
|
11
|
+
'dradis/plugins/calculators/mitre/base.css',
|
12
|
+
'dradis/plugins/calculators/mitre/base.js',
|
13
|
+
'dradis/plugins/calculators/mitre/manifests/hera.css',
|
14
|
+
'dradis/plugins/calculators/mitre/manifests/hera.js',
|
15
|
+
'dradis/plugins/calculators/mitre/mitre_data.json'
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
initializer "calculator_mitre.inflections" do |app|
|
20
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
21
|
+
inflect.acronym('MITRE')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
initializer 'calculator_mitre.mount_engine' do
|
26
|
+
Rails.application.routes.append do
|
27
|
+
# Enabling/disabling integrations calls Rails.application.reload_routes! we need the enable
|
28
|
+
# check inside the block to ensure the routes can be re-enabled without a server restart
|
29
|
+
if Engine.enabled?
|
30
|
+
mount Engine => '/', as: :mitre_calculator
|
31
|
+
end
|
32
|
+
end
|
33
|
+
# end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
module Calculators
|
4
|
+
module MITRE
|
5
|
+
# Returns the version of the currently loaded Nessus as a <tt>Gem::Version</tt>
|
6
|
+
def self.gem_version
|
7
|
+
Gem::Version.new VERSION::STRING
|
8
|
+
end
|
9
|
+
|
10
|
+
module VERSION
|
11
|
+
MAJOR = 4
|
12
|
+
MINOR = 16
|
13
|
+
TINY = 0
|
14
|
+
PRE = nil
|
15
|
+
|
16
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'gem_version'
|
2
|
+
|
3
|
+
module Dradis
|
4
|
+
module Plugins
|
5
|
+
module Calculators
|
6
|
+
module MITRE
|
7
|
+
# Returns the version of the currently loaded Nessus as a
|
8
|
+
# <tt>Gem::Version</tt>.
|
9
|
+
def self.version
|
10
|
+
gem_version
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|