Skip to content

Commit 6b02229

Browse files
committed
add csrf protection
1 parent eef7e57 commit 6b02229

File tree

14 files changed

+55
-10
lines changed

14 files changed

+55
-10
lines changed

blueprints/testcase_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
blueprint_testcase_utils = Blueprint('blueprint_testcase_utils', __name__)
1010

11-
@blueprint_testcase_utils.route('/testcase/<id>/toggle-visibility', methods = ['GET'])
11+
@blueprint_testcase_utils.route('/testcase/<id>/toggle-visibility', methods = ['POST'])
1212
@auth_required()
1313
@roles_accepted('Admin', 'Red')
1414
@user_assigned_assessment
@@ -19,7 +19,7 @@ def testcasevisibility(id):
1919

2020
return jsonify(newcase.to_json()), 200
2121

22-
@blueprint_testcase_utils.route('/testcase/<id>/clone', methods = ['GET'])
22+
@blueprint_testcase_utils.route('/testcase/<id>/clone', methods = ['POST'])
2323
@auth_required()
2424
@roles_accepted('Admin', 'Red')
2525
@user_assigned_assessment
@@ -34,7 +34,7 @@ def testcaseclone(id):
3434

3535
return jsonify(newcase.to_json()), 200
3636

37-
@blueprint_testcase_utils.route('/testcase/<id>/delete', methods = ['GET'])
37+
@blueprint_testcase_utils.route('/testcase/<id>/delete', methods = ['POST'])
3838
@auth_required()
3939
@roles_accepted('Admin', 'Red')
4040
@user_assigned_assessment

flask.cfg

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,4 @@ SESSION_COOKIE_SAMESITE = "Strict"
4545
REMEMBER_COOKIE_SECURE = True
4646
REMEMBER_COOKIE_SAMESITE = "Strict"
4747

48-
# WTF CSRF protection is enabled by default
49-
# WTF_CSRF_ENABLED = True
50-
# SECURITY_CSRF_COOKIE = {"samesite": "Strict", "httponly": False, "secure": True}
48+
WTF_CSRF_TIME_LIMIT = None

purpleops.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from flask import Flask, render_template, redirect
55
from flask_security import Security, auth_required, current_user
66

7+
from flask_wtf.csrf import CSRFProtect
8+
79
from blueprints import access, assessment, assessment_utils, assessment_import, assessment_export, testcase, testcase_utils
810

911

@@ -24,6 +26,7 @@
2426
db.init_app(app)
2527

2628
security = Security(app, user_datastore)
29+
csrf = CSRFProtect(app)
2730

2831
@app.route('/')
2932
@app.route('/index')

static/scripts/access.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,13 @@ $("#userDetailForm").submit(function(e){
9292

9393
// AJAX DELETE user call
9494
$('#deleteUserButton').click(function() {
95+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
9596
$.ajax({
9697
url: '/manage/access/user/' + rowData.id,
9798
type: 'DELETE',
99+
headers: {
100+
'X-CSRFToken': csrf_token
101+
},
98102
success: function(result) {
99103
$('#userTable').bootstrapTable('removeByUniqueId', rowData.id)
100104
$('#deleteUserModal').modal('hide')

static/scripts/assessment.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ $("#newTestcaseForm").submit(function(e){
5353

5454
// AJAX new testcase from template POST and table append
5555
$('#testcaseTemplatesButton').click(function() {
56+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
5657
$.ajax({
5758
url: `/assessment/${window.location.href.split("/").slice(-1)[0]}/import/template`,
5859
type: 'POST',
60+
headers: {
61+
'X-CSRFToken': csrf_token
62+
},
5963
data: JSON.stringify({
6064
ids: $('#testcaseTemplateTable').bootstrapTable('getSelections').map(row => row.id)
6165
}),
@@ -107,12 +111,16 @@ $("#campaignTemplateForm").submit(function(e){
107111

108112
// Toggle visibility of testcase AJAX
109113
function visibleTest(event) {
114+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
110115
event.stopPropagation();
111116
row = $(event.target).closest("tr")
112117
rowData = $('#assessmentTable').bootstrapTable('getData')[row.data("index")]
113118
$.ajax({
114119
url: `/testcase/${rowData.id}/toggle-visibility`,
115-
type: 'GET',
120+
type: 'POST',
121+
headers: {
122+
'X-CSRFToken': csrf_token
123+
},
116124
success: function(body) {
117125
$('#assessmentTable').bootstrapTable('updateByUniqueId', {
118126
id: body.id,
@@ -125,12 +133,16 @@ function visibleTest(event) {
125133

126134
// Testcase clone AJAX POST and row update
127135
function cloneTest(event) {
136+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
128137
event.stopPropagation();
129138
row = $(event.target).closest("tr")
130139
rowData = $('#assessmentTable').bootstrapTable('getData')[row.data("index")]
131140
$.ajax({
132141
url: `/testcase/${rowData.id}/clone`,
133-
type: 'GET',
142+
type: 'POST',
143+
headers: {
144+
'X-CSRFToken': csrf_token
145+
},
134146
success: function(body) {
135147
$('#assessmentTable').bootstrapTable('insertRow', {
136148
index: row.data("index") + 1,
@@ -152,9 +164,13 @@ function deleteTestcaseModal(event) {
152164

153165
// AJAX DELETE testcase call and remove from table
154166
$('#deleteTestcaseButton').click(function() {
167+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
155168
$.ajax({
156169
url: `/testcase/${rowData.id}/delete`,
157-
type: 'GET',
170+
type: 'POST',
171+
headers: {
172+
'X-CSRFToken': csrf_token
173+
},
158174
success: function(result) {
159175
$('#assessmentTable').bootstrapTable('removeByUniqueId', rowData.id)
160176
$('#deleteTestcaseModal').modal('hide')

static/scripts/assessments.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,13 @@ $("#importAssessmentForm").submit(function(e){
9696

9797
// AJAX DELETE assessment call
9898
$('#deleteAssessmentButton').click(function() {
99+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
99100
$.ajax({
100101
url: `/assessment/${rowData.id}`,
101102
type: 'DELETE',
103+
headers: {
104+
'X-CSRFToken': csrf_token
105+
},
102106
success: function(result) {
103107
$('#assessmentsTable').bootstrapTable('removeByUniqueId', rowData.id)
104108
$('#deleteAssessmentModal').modal('hide')

static/scripts/testcase.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ $('.multiNew').click(function(event) {
1212

1313
// When the source/target etc. table/modal is saved, post updates and refresh table
1414
$('.multiButton').click(function(event) {
15+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
1516
type = event.target.id.replace("multi", "").replace("Button", "").toLowerCase() + "s" // Hacky
1617
$.ajax({
1718
url: `${$("#assessment-crumb-button").attr("href")}/multi/${type}`,
1819
type: 'POST',
19-
20+
headers: {
21+
'X-CSRFToken': csrf_token
22+
},
2023
data: JSON.stringify({
2124
data: $(`#${type}Table`).bootstrapTable("getData")
2225
}),
@@ -281,6 +284,7 @@ $("#run-button").click(function(){
281284

282285
// Delete evidence AJAX handler
283286
$(document).on("click", ".evidence-delete", function(event) {
287+
var csrf_token = $('meta[name="csrf-token"]').attr('content');
284288
target = event.target.tagName == "I" ? event.target.parentNode : event.target
285289
colour = $(target).attr("class").includes("evidence-red") ? "red" : "blue"
286290
url = $(target).next("a").attr("href").split("?")[0]
@@ -289,6 +293,9 @@ $(document).on("click", ".evidence-delete", function(event) {
289293
$.ajax({
290294
url: url,
291295
type: 'DELETE',
296+
headers: {
297+
'X-CSRFToken': csrf_token
298+
},
292299
success: function(result) {
293300
$(target).parent().remove()
294301
}

templates/access_modals.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
{% endfor %}
3131
</select>
3232
</div>
33+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
3334
</form>
3435
{{ macros.modalTail(name="userDetail", cancelLabel="Close", actionLabel="Create") }}
3536
</form>

templates/assessment_modals.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
{% endfor %}
2424
</select>
2525
</div>
26+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
2627
{{ macros.modalTail(name="newTestcase", cancelLabel="Close", actionLabel="Create") }}
2728
</form>
2829

@@ -63,6 +64,7 @@
6364
<label for="formFile" class="form-label">Select your threat actor / ad-hoc TTP at <a href="https://mitre-attack.github.io/attack-navigator/" target="_blank">the Mitre ATT&CK Navigator</a> &gt; <code>Download Controls</code> &gt; <code>Download Layer as JSON</code>.</label>
6465
<input class="form-control" type="file" id="formFile" name="file" accept=".json,application/json">
6566
</div>
67+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
6668
{{ macros.modalTail(name="testcaseNavigator", cancelLabel="Close", actionLabel="Import") }}
6769
</form>
6870

@@ -73,6 +75,7 @@
7375
<label for="campaignFile" class="form-label">Import Campaign from <code>Export</code> &gt; <code>Campaign Template</code>.</label>
7476
<input class="form-control" type="file" id="campaignFile" name="file" accept=".json,application/json">
7577
</div>
78+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
7679
{{ macros.modalTail(name="testcaseCampaign", cancelLabel="Close", actionLabel="Import") }}
7780
</form>
7881

@@ -88,6 +91,7 @@
8891
{% endfor %}
8992
</select>
9093
</div>
94+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
9195
{{ macros.modalTail(name="exportReport", cancelLabel="Close", actionLabel="Generate") }}
9296
</form>
9397

@@ -103,5 +107,6 @@
103107
<label for="variablesFile" class="form-label">Import Variables from <code>Json File</code>.</label>
104108
<input type="file" id="variablesFile" name="file" accept=".json,application/json">
105109
</div>
110+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
106111
{{ macros.modalTail(name="variables", cancelLabel="Close", actionLabel="Import") }}
107112
</form>

templates/assessments_modals.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<label class="col-form-label">Description</label>
1111
<textarea class="form-control" name="description" placeholder="The purpose of this engagement is to..." required id="description" autocomplete="off"></textarea>
1212
</div>
13+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
1314
</form>
1415
{{ macros.modalTail(name="newAssessment", cancelLabel="Cancel", actionLabel="Create") }}
1516
</form>
@@ -26,5 +27,6 @@
2627
<p>Select the <code>Assessment.zip</code> file output from <code>Export > Entire Assessment</code>.</p>
2728
<input class="form-control" type="file" id="formFile" name="file" accept=".zip,application/zip" autocomplete="off">
2829
</div>
30+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
2931
{{ macros.modalTail(name="deleteAssessment", cancelLabel="Cancel", actionLabel="Import") }}
3032
</form>

0 commit comments

Comments
 (0)