Add length error content that is too long (#1787)

* Add length checking to some sensitive fields in Pages and Challenges.
* Works on #1786

This is enough to fix most of the issues but this is really a systemic problem for most of the API endpoints. We should have something that verifies data consistency. Marshmallow is not good enough at this. Pydantic seems like it would be superior here.
This commit is contained in:
Kevin Chung
2021-01-28 16:55:15 -05:00
committed by GitHub
parent 2e6ce0f695
commit 7f115bf458
9 changed files with 116 additions and 8 deletions

View File

@@ -23,6 +23,7 @@ from CTFd.models import (
db,
)
from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class
from CTFd.schemas.challenges import ChallengeSchema
from CTFd.schemas.flags import FlagSchema
from CTFd.schemas.hints import HintSchema
from CTFd.schemas.tags import TagSchema
@@ -223,6 +224,13 @@ class ChallengeList(Resource):
)
def post(self):
data = request.form or request.get_json()
# Load data through schema for validation but not for insertion
schema = ChallengeSchema()
response = schema.load(data)
if response.errors:
return {"success": False, "errors": response.errors}, 400
challenge_type = data["type"]
challenge_class = get_chal_class(challenge_type)
challenge = challenge_class.create(request)
@@ -427,6 +435,14 @@ class Challenge(Resource):
},
)
def patch(self, challenge_id):
data = request.get_json()
# Load data through schema for validation but not for insertion
schema = ChallengeSchema()
response = schema.load(data)
if response.errors:
return {"success": False, "errors": response.errors}, 400
challenge = Challenges.query.filter_by(id=challenge_id).first_or_404()
challenge_class = get_chal_class(challenge.type)
challenge = challenge_class.update(challenge, request)

View File

@@ -1,3 +1,5 @@
from marshmallow import ValidationError, pre_load
from CTFd.models import Challenges, ma
@@ -6,3 +8,30 @@ class ChallengeSchema(ma.ModelSchema):
model = Challenges
include_fk = True
dump_only = ("id",)
@pre_load
def validate_name(self, data):
name = data.get("name", "")
if len(name) > 80:
raise ValidationError(
"Challenge could not be saved. Challenge name too long",
field_names=["name"],
)
@pre_load
def validate_category(self, data):
category = data.get("category", "")
if len(category) > 80:
raise ValidationError(
"Challenge could not be saved. Challenge category too long",
field_names=["category"],
)
@pre_load
def validate_description(self, data):
description = data.get("description", "")
if len(description) >= 65536:
raise ValidationError(
"Challenge could not be saved. Challenge description is too long.",
field_names=["description"],
)

View File

@@ -1,4 +1,4 @@
from marshmallow import pre_load
from marshmallow import ValidationError, pre_load
from CTFd.models import Pages, ma
from CTFd.utils import string_types
@@ -10,11 +10,34 @@ class PageSchema(ma.ModelSchema):
include_fk = True
dump_only = ("id",)
@pre_load
def validate_title(self, data):
title = data.get("title", "")
if len(title) > 128:
raise ValidationError(
"Page could not be saved. Your title is too long.",
field_names=["title"],
)
@pre_load
def validate_route(self, data):
route = data.get("route")
if route and route.startswith("/"):
route = data.get("route", "")
if route.startswith("/"):
data["route"] = route.strip("/")
if len(route) > 128:
raise ValidationError(
"Page could not be saved. Your route is too long.",
field_names=["route"],
)
@pre_load
def validate_content(self, data):
content = data.get("content", "")
if len(content) >= 65536:
raise ValidationError(
"Page could not be saved. Your content is too long.",
field_names=["content"],
)
def __init__(self, view=None, *args, **kwargs):
if view:

View File

@@ -149,6 +149,18 @@ function loadChalTemplate(challenge) {
response.data.id
);
$("#challenge-create-options").modal();
} else {
let body = "";
for (const k in response.errors) {
body += response.errors[k].join("\n");
body += "\n";
}
ezAlert({
title: "Error",
body: body,
button: "OK"
});
}
});
});
@@ -385,6 +397,18 @@ $(() => {
title: "Success",
body: "Your challenge has been updated!"
});
} else {
let body = "";
for (const k in response.errors) {
body += response.errors[k].join("\n");
body += "\n";
}
ezAlert({
title: "Error",
body: body,
button: "OK"
});
}
});
};

View File

@@ -5,7 +5,7 @@ import $ from "jquery";
import CTFd from "core/CTFd";
import CodeMirror from "codemirror";
import "codemirror/mode/htmlmixed/htmlmixed.js";
import { ezToast } from "core/ezq";
import { ezAlert, ezToast } from "core/ezq";
import Vue from "vue/dist/vue.esm.browser";
import CommentBox from "../components/comments/CommentBox.vue";
@@ -35,6 +35,22 @@ function submit_form() {
return response.json();
})
.then(function(response) {
// Show errors reported by API
if (response.success === false) {
let body = "";
for (const k in response.errors) {
body += response.errors[k].join("\n");
body += "\n";
}
ezAlert({
title: "Error",
body: body,
button: "OK"
});
return;
}
if (method === "PATCH" && response.success) {
ezToast({
title: "Saved",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -162,7 +162,7 @@
/***/ (function(module, exports, __webpack_require__) {
;
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _styles = __webpack_require__(/*! ../styles */ \"./CTFd/themes/admin/assets/js/styles.js\");\n\n__webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _codemirror = _interopRequireDefault(__webpack_require__(/*! codemirror */ \"./node_modules/codemirror/lib/codemirror.js\"));\n\n__webpack_require__(/*! codemirror/mode/htmlmixed/htmlmixed.js */ \"./node_modules/codemirror/mode/htmlmixed/htmlmixed.js\");\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nvar _vueEsm = _interopRequireDefault(__webpack_require__(/*! vue/dist/vue.esm.browser */ \"./node_modules/vue/dist/vue.esm.browser.js\"));\n\nvar _CommentBox = _interopRequireDefault(__webpack_require__(/*! ../components/comments/CommentBox.vue */ \"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nfunction submit_form() {\n // Save the CodeMirror data to the Textarea\n window.editor.save();\n var params = (0, _jquery[\"default\"])(\"#page-edit\").serializeJSON();\n var target = \"/api/v1/pages\";\n var method = \"POST\";\n var part = window.location.pathname.split(\"/\").pop();\n\n if (part !== \"new\") {\n target += \"/\" + part;\n method = \"PATCH\";\n }\n\n _CTFd[\"default\"].fetch(target, {\n method: method,\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(params)\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n if (method === \"PATCH\" && response.success) {\n (0, _ezq.ezToast)({\n title: \"Saved\",\n body: \"Your changes have been saved\"\n });\n } else {\n window.location = _CTFd[\"default\"].config.urlRoot + \"/admin/pages/\" + response.data.id;\n }\n });\n}\n\nfunction preview_page() {\n window.editor.save(); // Save the CodeMirror data to the Textarea\n\n (0, _jquery[\"default\"])(\"#page-edit\").attr(\"action\", _CTFd[\"default\"].config.urlRoot + \"/admin/pages/preview\");\n (0, _jquery[\"default\"])(\"#page-edit\").attr(\"target\", \"_blank\");\n (0, _jquery[\"default\"])(\"#page-edit\").submit();\n}\n\n(0, _jquery[\"default\"])(function () {\n window.editor = _codemirror[\"default\"].fromTextArea(document.getElementById(\"admin-pages-editor\"), {\n lineNumbers: true,\n lineWrapping: true,\n mode: \"htmlmixed\",\n htmlMode: true\n });\n (0, _jquery[\"default\"])(\"#media-button\").click(function (_e) {\n (0, _styles.showMediaLibrary)(window.editor);\n });\n (0, _jquery[\"default\"])(\"#save-page\").click(function (e) {\n e.preventDefault();\n submit_form();\n });\n (0, _jquery[\"default\"])(\".preview-page\").click(function () {\n preview_page();\n }); // Insert CommentBox element\n\n if (window.PAGE_ID) {\n var commentBox = _vueEsm[\"default\"].extend(_CommentBox[\"default\"]);\n\n var vueContainer = document.createElement(\"div\");\n document.querySelector(\"#comment-box\").appendChild(vueContainer);\n new commentBox({\n propsData: {\n type: \"page\",\n id: window.PAGE_ID\n }\n }).$mount(vueContainer);\n }\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/editor.js?");
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _styles = __webpack_require__(/*! ../styles */ \"./CTFd/themes/admin/assets/js/styles.js\");\n\n__webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _codemirror = _interopRequireDefault(__webpack_require__(/*! codemirror */ \"./node_modules/codemirror/lib/codemirror.js\"));\n\n__webpack_require__(/*! codemirror/mode/htmlmixed/htmlmixed.js */ \"./node_modules/codemirror/mode/htmlmixed/htmlmixed.js\");\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nvar _vueEsm = _interopRequireDefault(__webpack_require__(/*! vue/dist/vue.esm.browser */ \"./node_modules/vue/dist/vue.esm.browser.js\"));\n\nvar _CommentBox = _interopRequireDefault(__webpack_require__(/*! ../components/comments/CommentBox.vue */ \"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nfunction submit_form() {\n // Save the CodeMirror data to the Textarea\n window.editor.save();\n var params = (0, _jquery[\"default\"])(\"#page-edit\").serializeJSON();\n var target = \"/api/v1/pages\";\n var method = \"POST\";\n var part = window.location.pathname.split(\"/\").pop();\n\n if (part !== \"new\") {\n target += \"/\" + part;\n method = \"PATCH\";\n }\n\n _CTFd[\"default\"].fetch(target, {\n method: method,\n credentials: \"same-origin\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(params)\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n // Show errors reported by API\n if (response.success === false) {\n var body = \"\";\n\n for (var k in response.errors) {\n body += response.errors[k].join(\"\\n\");\n body += \"\\n\";\n }\n\n (0, _ezq.ezAlert)({\n title: \"Error\",\n body: body,\n button: \"OK\"\n });\n return;\n }\n\n if (method === \"PATCH\" && response.success) {\n (0, _ezq.ezToast)({\n title: \"Saved\",\n body: \"Your changes have been saved\"\n });\n } else {\n window.location = _CTFd[\"default\"].config.urlRoot + \"/admin/pages/\" + response.data.id;\n }\n });\n}\n\nfunction preview_page() {\n window.editor.save(); // Save the CodeMirror data to the Textarea\n\n (0, _jquery[\"default\"])(\"#page-edit\").attr(\"action\", _CTFd[\"default\"].config.urlRoot + \"/admin/pages/preview\");\n (0, _jquery[\"default\"])(\"#page-edit\").attr(\"target\", \"_blank\");\n (0, _jquery[\"default\"])(\"#page-edit\").submit();\n}\n\n(0, _jquery[\"default\"])(function () {\n window.editor = _codemirror[\"default\"].fromTextArea(document.getElementById(\"admin-pages-editor\"), {\n lineNumbers: true,\n lineWrapping: true,\n mode: \"htmlmixed\",\n htmlMode: true\n });\n (0, _jquery[\"default\"])(\"#media-button\").click(function (_e) {\n (0, _styles.showMediaLibrary)(window.editor);\n });\n (0, _jquery[\"default\"])(\"#save-page\").click(function (e) {\n e.preventDefault();\n submit_form();\n });\n (0, _jquery[\"default\"])(\".preview-page\").click(function () {\n preview_page();\n }); // Insert CommentBox element\n\n if (window.PAGE_ID) {\n var commentBox = _vueEsm[\"default\"].extend(_CommentBox[\"default\"]);\n\n var vueContainer = document.createElement(\"div\");\n document.querySelector(\"#comment-box\").appendChild(vueContainer);\n new commentBox({\n propsData: {\n type: \"page\",\n id: window.PAGE_ID\n }\n }).$mount(vueContainer);\n }\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/editor.js?");
/***/ })

File diff suppressed because one or more lines are too long