* Changing to a new plugin oriented challenge type plugin and fixing extra width on admin chal description

* Add window.challenge.submit, renderSubmissionResponse, and csrf_nonce

* Update admin side renderer calls

* Updating to Flask 1.0 and adding files for flask run

* Adding a preliminary case-insensitive key

* Adding case insensitive keys

* Adding CTF Logo

* Reducing the amount of team information shown on the main page

* Add better base64 helpers

* Switch from button to badge

* Rudimentary solve checking from admin panel

* Refine admin chals solves view & fix PEP8

* Compare base64 encoded data with bytestring

* Removing need to urlencode/urldecode in base64 wrappers

* Adding decorator documentation

* Randomly order tests & add test for case_insensitive flags

* Add regex flag case_insensitive test

* Add tests for /admin/chal/1/solves and ctf_logo
selenium-screenshot-testing
Kevin Chung 2018-05-03 18:04:39 -04:00 committed by GitHub
parent 9c812ad52e
commit 36c83b59bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 695 additions and 429 deletions

2
.flaskenv Normal file
View File

@ -0,0 +1,2 @@
FLASK_ENV=development
FLASK_RUN_PORT=4000

View File

@ -15,6 +15,6 @@ before_script:
- psql -c 'create database ctfd;' -U postgres
script:
- pep8 --ignore E501,E712 CTFd/ tests/
- nosetests -d
- nosetests -v -d --with-randomly
after_success:
- codecov

View File

@ -147,6 +147,13 @@ def admin_config():
utils.set_config("mail_username", None)
utils.set_config("mail_password", None)
if request.files.get('ctf_logo', None):
ctf_logo = request.files['ctf_logo']
file_id, file_loc = utils.upload_file(ctf_logo, None)
utils.set_config("ctf_logo", file_loc)
elif request.form.get('ctf_logo') == '':
utils.set_config("ctf_logo", None)
utils.set_config("ctf_name", request.form.get('ctf_name', None))
utils.set_config("ctf_theme", request.form.get('ctf_theme', None))
utils.set_config('css', request.form.get('css', None))
@ -176,6 +183,7 @@ def admin_config():
cache.clear()
ctf_name = utils.get_config('ctf_name')
ctf_logo = utils.get_config('ctf_logo')
ctf_theme = utils.get_config('ctf_theme')
hide_scores = utils.get_config('hide_scores')
css = utils.get_config('css')
@ -216,6 +224,7 @@ def admin_config():
return render_template(
'admin/config.html',
ctf_name=ctf_name,
ctf_logo=ctf_logo,
ctf_theme_config=ctf_theme,
css=css,
start=start,

View File

@ -100,6 +100,19 @@ def admin_chal_detail(chalid):
return jsonify(data)
@admin_challenges.route('/admin/chal/<int:chalid>/solves', methods=['GET'])
@admins_only
def admin_chal_solves(chalid):
response = {'teams': []}
if utils.hide_scores():
return jsonify(response)
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid).order_by(
Solves.date.asc())
for solve in solves:
response['teams'].append({'id': solve.team.id, 'name': solve.team.name, 'date': solve.date})
return jsonify(response)
@admin_challenges.route('/admin/tags/<int:chalid>', methods=['GET', 'POST'])
@admins_only
def admin_tags(chalid):

View File

@ -57,6 +57,10 @@ def admin_create_team():
affiliation = request.form.get('affiliation', None)
country = request.form.get('country', None)
admin_user = True if request.form.get('admin', None) == 'on' else False
verified = True if request.form.get('verified', None) == 'on' else False
hidden = True if request.form.get('hidden', None) == 'on' else False
errors = []
if not name:
@ -92,6 +96,10 @@ def admin_create_team():
team.affiliation = affiliation
team.country = country
team.admin = admin_user
team.verified = verified
team.hidden = hidden
db.session.add(team)
db.session.commit()
db.session.close()
@ -119,24 +127,6 @@ def admin_team(teamid):
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
place=place, wrong_keys=wrong_keys, awards=awards)
elif request.method == 'POST':
admin_user = request.form.get('admin', None)
if admin_user:
admin_user = True if admin_user == 'true' else False
user.admin = admin_user
# Set user.banned to hide admins from scoreboard
user.banned = admin_user
db.session.commit()
db.session.close()
return jsonify({'data': ['success']})
verified = request.form.get('verified', None)
if verified:
verified = True if verified == 'true' else False
user.verified = verified
db.session.commit()
db.session.close()
return jsonify({'data': ['success']})
name = request.form.get('name', None)
password = request.form.get('password', None)
email = request.form.get('email', None)
@ -144,6 +134,10 @@ def admin_team(teamid):
affiliation = request.form.get('affiliation', None)
country = request.form.get('country', None)
admin_user = True if request.form.get('admin', None) == 'on' else False
verified = True if request.form.get('verified', None) == 'on' else False
hidden = True if request.form.get('hidden', None) == 'on' else False
errors = []
if email:
@ -177,6 +171,9 @@ def admin_team(teamid):
user.website = website
user.affiliation = affiliation
user.country = country
user.admin = admin_user
user.verified = verified
user.banned = hidden
db.session.commit()
db.session.close()
return jsonify({'data': ['success']})

View File

@ -29,7 +29,7 @@ def confirm_user(data=None):
if data and request.method == "GET":
try:
s = TimedSerializer(app.config['SECRET_KEY'])
email = s.loads(utils.base64decode(data, urldecode=True), max_age=1800)
email = s.loads(utils.base64decode(data), max_age=1800)
except BadTimeSignature:
return render_template('confirm.html', errors=['Your confirmation link has expired'])
except (BadSignature, TypeError, base64.binascii.Error):
@ -86,7 +86,7 @@ def reset_password(data=None):
if data is not None:
try:
s = TimedSerializer(app.config['SECRET_KEY'])
name = s.loads(utils.base64decode(data, urldecode=True), max_age=1800)
name = s.loads(utils.base64decode(data), max_age=1800)
except BadTimeSignature:
return render_template('reset_password.html', errors=['Your link has expired'])
except (BadSignature, TypeError, base64.binascii.Error):

View File

@ -146,7 +146,7 @@ class CTFdStandardChallenge(BaseChallenge):
provided_key = request.form['key'].strip()
chal_keys = Keys.query.filter_by(chal=chal.id).all()
for chal_key in chal_keys:
if get_key_class(chal_key.type).compare(chal_key.flag, provided_key):
if get_key_class(chal_key.type).compare(chal_key, provided_key):
return True, 'Correct'
return False, 'Incorrect'

View File

@ -1,28 +1,22 @@
// Markdown Preview
$('#desc-edit').on('shown.bs.tab', function (event) {
var md = window.markdownit({
html: true,
});
if (event.target.hash == '#desc-preview'){
if (event.target.hash == '#desc-preview') {
var editor_value = $('#desc-editor').val();
$(event.target.hash).html(
md.render(editor_value)
window.challenge.render(editor_value)
);
}
});
$('#new-desc-edit').on('shown.bs.tab', function (event) {
var md = window.markdownit({
html: true,
});
if (event.target.hash == '#new-desc-preview'){
if (event.target.hash == '#new-desc-preview') {
var editor_value = $('#new-desc-editor').val();
$(event.target.hash).html(
md.render(editor_value)
window.challenge.render(editor_value)
);
}
});
$("#solve-attempts-checkbox").change(function() {
if(this.checked) {
$("#solve-attempts-checkbox").change(function () {
if (this.checked) {
$('#solve-attempts-input').show();
} else {
$('#solve-attempts-input').hide();
@ -30,6 +24,6 @@ $("#solve-attempts-checkbox").change(function() {
}
});
$(document).ready(function(){
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
});

View File

@ -1,25 +1,35 @@
$('#submit-key').unbind('click');
$('#submit-key').click(function (e) {
e.preventDefault();
submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val())
window.challenge.renderer = new markdownit({
html: true,
});
$("#answer-input").keyup(function(event){
if(event.keyCode == 13){
$("#submit-key").click();
}
});
window.challenge.preRender = function(){
$(".input-field").bind({
focus: function() {
$(this).parent().addClass('input--filled' );
$label = $(this).siblings(".input-label");
},
blur: function() {
if ($(this).val() === '') {
$(this).parent().removeClass('input--filled' );
$label = $(this).siblings(".input-label");
$label.removeClass('input--hide' );
}
};
window.challenge.render = function(markdown){
return window.challenge.renderer.render(markdown);
};
window.challenge.postRender = function(){
};
window.challenge.submit = function(cb, preview){
var chal_id = $('#chal-id').val();
var answer = $('#answer-input').val();
var nonce = $('#nonce').val();
var url = "/chal/";
if (preview) {
url = "/admin/chal/";
}
});
$.post(script_root + url + chal_id, {
key: answer,
nonce: nonce
}, function (data) {
cb(data);
});
};

View File

@ -18,24 +18,18 @@ $('#limit_max_attempts').change(function() {
// Markdown Preview
$('#desc-edit').on('shown.bs.tab', function (event) {
var md = window.markdownit({
html: true,
});
if (event.target.hash == '#desc-preview'){
if (event.target.hash == '#desc-preview') {
var editor_value = $('#desc-editor').val();
$(event.target.hash).html(
md.render(editor_value)
window.challenge.render(editor_value)
);
}
});
$('#new-desc-edit').on('shown.bs.tab', function (event) {
var md = window.markdownit({
html: true,
});
if (event.target.hash == '#new-desc-preview'){
if (event.target.hash == '#new-desc-preview') {
var editor_value = $('#new-desc-editor').val();
$(event.target.hash).html(
md.render(editor_value)
window.challenge.render(editor_value)
);
}
});

View File

@ -24,12 +24,20 @@ class CTFdStaticKey(BaseKey):
}
@staticmethod
def compare(saved, provided):
def compare(chal_key_obj, provided):
saved = chal_key_obj.flag
data = chal_key_obj.data
if len(saved) != len(provided):
return False
result = 0
for x, y in zip(saved, provided):
result |= ord(x) ^ ord(y)
if data == "case_insensitive":
for x, y in zip(saved.lower(), provided.lower()):
result |= ord(x) ^ ord(y)
else:
for x, y in zip(saved, provided):
result |= ord(x) ^ ord(y)
return result == 0
@ -42,8 +50,15 @@ class CTFdRegexKey(BaseKey):
}
@staticmethod
def compare(saved, provided):
res = re.match(saved, provided, re.IGNORECASE)
def compare(chal_key_obj, provided):
saved = chal_key_obj.flag
data = chal_key_obj.data
if data == "case_insensitive":
res = re.match(saved, provided, re.IGNORECASE)
else:
res = re.match(saved, provided)
return res and res.group() == provided

View File

@ -1,2 +1,6 @@
<label for="create-key-regex" class="control-label">Enter Regex Key Data</label>
<input type="text" id="create-key-regex" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive">
<label class="form-check-label" for="keydata">Case Insensitive</label>
</div>

View File

@ -1,28 +1,33 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-center">
<div class="container">
<div class="row">
<div class="col-md-12">
<h3 class="text-center">Regex Key</h3>
</div>
</div>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form method="POST" action="{{ script_root }}/admin/keys/{{id}}">
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
<input type="hidden" id="key-type" name="key_type" value="regex">
<input type="hidden" id="key-id">
<hr>
<div class="form-group">
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
<button id="submit-keys" class="btn btn-success float-right">Update</button>
</div>
</form>
</div>
</div>
<div class="modal-content">
<div class="modal-header text-center">
<div class="container">
<div class="row">
<div class="col-md-12">
<h3 class="text-center">Regex Key</h3>
</div>
</div>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form method="POST" action="{{ script_root }}/admin/keys/{{id}}">
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter regex key data">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive"
{% if data %}checked{% endif %}>
<label class="form-check-label" for="keydata">Case Insensitive</label>
</div>
<input type="hidden" id="key-type" name="key_type" value="regex">
<input type="hidden" id="key-id">
<hr>
<div class="form-group">
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
<button id="submit-keys" class="btn btn-success float-right">Update</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,2 +1,6 @@
<label for="create-key-static" class="control-label">Enter Static Key Data</label>
<input type="text" id="create-key-static" class="form-control" name="key" value="{{key}}" placeholder="Enter static key data">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive">
<label class="form-check-label" for="keydata">Case Insensitive</label>
</div>

View File

@ -1,28 +1,34 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-center">
<div class="container">
<div class="row">
<div class="col-md-12">
<h3 class="text-center">Static Key</h3>
</div>
</div>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form method="POST" action="{{ script_root }}/admin/keys/{{id}}">
<input type="text" id="key-data" class="form-control" name="key" value="{{key}}" placeholder="Enter static key data">
<input type="hidden" id="key-type" name="key_type" value="static">
<input type="hidden" id="key-id">
<hr>
<div class="form-group">
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
<button id="submit-keys" class="btn btn-success float-right">Update</button>
</div>
</form>
</div>
</div>
<div class="modal-content">
<div class="modal-header text-center">
<div class="container">
<div class="row">
<div class="col-md-12">
<h3 class="text-center">Static Key</h3>
</div>
</div>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form method="POST" action="{{ script_root }}/admin/keys/{{ id }}">
<input type="text" id="key-data" class="form-control" name="key" value="{{ key }}"
placeholder="Enter static key data">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="keydata" name="keydata" value="case_insensitive"
{% if data %}checked{% endif %}>
<label class="form-check-label" for="keydata">Case Insensitive</label>
</div>
<input type="hidden" id="key-type" name="key_type" value="static">
<input type="hidden" id="key-id">
<hr>
<div class="form-group">
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
<button id="submit-keys" class="btn btn-success float-right">Update</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -4,8 +4,6 @@ pre {
}
.chal-desc {
padding-left: 30px;
padding-right: 30px;
font-size: 14px;
}

View File

@ -1,11 +1,16 @@
$.ajaxSetup({ cache: false });
window.challenge = new Object();
function load_chal_template(challenge){
$.get(script_root + challenge.templates.create, function(template_data){
var template = nunjucks.compile(template_data);
$("#create-chal-entry-div").html(template.render({'nonce':nonce, 'script_root':script_root}));
$.getScript(script_root + challenge.scripts.create, function(){
console.log('loaded');
$.getScript(script_root + challenge.scripts.modal, function () {
console.log('loaded renderer');
$.get(script_root + challenge.templates.create, function (template_data) {
var template = nunjucks.compile(template_data);
$("#create-chal-entry-div").html(template.render({'nonce': nonce, 'script_root': script_root}));
$.getScript(script_root + challenge.scripts.create, function () {
console.log('loaded');
});
});
});
}

View File

@ -1,24 +1,24 @@
var challenges = {};
window.challenge = new Object();
function load_chal_template(id, success_cb){
var obj = $.grep(challenges['game'], function (e) {
return e.id == id;
})[0];
$.get(script_root + "/admin/chal/" + id, function (challenge_data) {
$.get(script_root + obj.type_data.templates.update, function (template_data) {
var template = nunjucks.compile(template_data);
$.get(script_root + "/admin/chal/" + id, function (obj) {
$.getScript(script_root + obj.type_data.scripts.modal, function () {
console.log('loaded renderer');
$.get(script_root + obj.type_data.templates.update, function (template_data) {
var template = nunjucks.compile(template_data);
challenge_data['nonce'] = $('#nonce').val();
challenge_data['script_root'] = script_root;
obj['nonce'] = $('#nonce').val();
obj['script_root'] = script_root;
$("#update-modals-entry-div").html(template.render(challenge_data));
$("#update-modals-entry-div").html(template.render(obj));
$.ajax({
url: script_root + obj.type_data.scripts.update,
dataType: "script",
success: success_cb,
cache: false,
$.ajax({
url: script_root + obj.type_data.scripts.update,
dataType: "script",
success: success_cb,
cache: false,
});
});
});
});
@ -30,55 +30,63 @@ function load_challenge_preview(id){
function render_challenge_preview(chal_id){
var preview_window = $('#challenge-preview');
var md = window.markdownit({
html: true,
});
$.get(script_root + "/admin/chal/" + chal_id, function(challenge_data){
$.get(script_root + challenge_data.type_data.templates.modal, function (template_data) {
preview_window.empty();
var template = nunjucks.compile(template_data);
$.get(script_root + "/admin/chal/" + chal_id, function(obj){
$.getScript(script_root + obj.type_data.scripts.modal, function () {
console.log('loaded renderer');
challenge_data['description'] = md.render(challenge_data['description']);
challenge_data['script_root'] = script_root;
$.get(script_root + obj.type_data.templates.modal, function (template_data) {
var template = nunjucks.compile(template_data);
var challenge = template.render(challenge_data);
window.challenge.preRender()
preview_window.append(challenge);
obj['description'] = window.challenge.render(obj['description']);
obj['script_root'] = script_root;
var challenge = template.render(obj);
preview_window.html(challenge);
$('#submit-key').click(function (e) {
e.preventDefault();
$('#submit-key').addClass("disabled-button");
$('#submit-key').prop('disabled', true);
window.challenge.submit(function (data) {
renderSubmissionResponse(data)
}, preview=true);
});
$("#answer-input").keyup(function (event) {
if (event.keyCode == 13) {
$("#submit-key").click();
}
});
window.challenge.postRender()
$.getScript(script_root + challenge_data.type_data.scripts.modal, function () {
preview_window.modal();
});
});
});
}
function loadchals(cb){
$.post(script_root + "/admin/chals", {
'nonce': $('#nonce').val()
}, function (data) {
var categories = [];
challenges = $.parseJSON(JSON.stringify(data));
for (var i = challenges['game'].length - 1; i >= 0; i--) {
if ($.inArray(challenges['game'][i].category, categories) == -1) {
categories.push(challenges['game'][i].category)
}
}
if (cb) {
cb();
function loadsolves(id) {
$.get(script_root + '/admin/chal/' + id + '/solves', function (data) {
var teams = data['teams'];
var box = $('#challenge-solves-body');
var modal = $('#challenge-solves-modal')
box.empty();
for (var i = 0; i < teams.length; i++) {
var id = teams[i].id;
var name = teams[i].name;
var date = moment(teams[i].date).local().format('MMMM Do, h:mm:ss A');
box.append('<tr><td><a href="team/{0}">{1}</td><td><small>{2}</small></td></tr>'.format(id, htmlentities(name), date));
}
modal.modal();
});
}
loadchals(function(){
$('.edit-challenge').click(function (e) {
var id = $(this).attr('chal-id');
load_chal_template(id, function () {
openchal(id);
});
});
});
function loadhint(hintid) {
var md = window.markdownit({
@ -107,64 +115,62 @@ function loadhint(hintid) {
});
}
function submitkey(chal, key, nonce){
$.post(script_root + "/admin/chal/" + chal, {
key: key,
nonce: nonce
}, function (data) {
console.log(data);
var result = $.parseJSON(JSON.stringify(data));
function renderSubmissionResponse(data, cb) {
var result = $.parseJSON(JSON.stringify(data));
var result_message = $('#result-message');
var result_notification = $('#result-notification');
var answer_input = $("#answer-input");
result_notification.removeClass();
result_message.text(result.message);
var result_message = $('#result-message');
var result_notification = $('#result-notification');
var answer_input = $("#answer-input");
result_notification.removeClass();
result_message.text(result.message);
if (result.status == -1) {
window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash
return
}
else if (result.status == 0) { // Incorrect key
result_notification.addClass('alert alert-danger alert-dismissable text-center');
result_notification.slideDown();
answer_input.removeClass("correct");
answer_input.addClass("wrong");
setTimeout(function () {
answer_input.removeClass("wrong");
}, 3000);
}
else if (result.status == 1) { // Challenge Solved
result_notification.addClass('alert alert-success alert-dismissable text-center');
result_notification.slideDown();
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
}
else if (result.status == 2) { // Challenge already solved
result_notification.addClass('alert alert-info alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("correct");
}
else if (result.status == 3) { // Keys per minute too high
result_notification.addClass('alert alert-warning alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("too-fast");
setTimeout(function () {
answer_input.removeClass("too-fast");
}, 3000);
}
if (result.status == -1) {
window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash
return
}
else if (result.status == 0) { // Incorrect key
result_notification.addClass('alert alert-danger alert-dismissable text-center');
result_notification.slideDown();
answer_input.removeClass("correct");
answer_input.addClass("wrong");
setTimeout(function () {
$('.alert').slideUp();
$('#submit-key').removeClass("disabled-button");
$('#submit-key').prop('disabled', false);
answer_input.removeClass("wrong");
}, 3000);
});
}
else if (result.status == 1) { // Challenge Solved
result_notification.addClass('alert alert-success alert-dismissable text-center');
result_notification.slideDown();
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
}
else if (result.status == 2) { // Challenge already solved
result_notification.addClass('alert alert-info alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("correct");
}
else if (result.status == 3) { // Keys per minute too high
result_notification.addClass('alert alert-warning alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("too-fast");
setTimeout(function () {
answer_input.removeClass("too-fast");
}, 3000);
}
setTimeout(function () {
$('.alert').slideUp();
$('#submit-key').removeClass("disabled-button");
$('#submit-key').prop('disabled', false);
}, 3000);
if (cb) {
cb(result);
}
}
$(document).ready(function () {
@ -194,9 +200,24 @@ $(document).ready(function () {
});
});
$('.edit-challenge').click(function (e) {
var id = $(this).attr('chal-id');
load_chal_template(id, function () {
openchal(id);
});
});
$('.preview-challenge').click(function (e) {
var chal_id = $(this).attr('chal-id');
load_challenge_preview(chal_id);
});
$('.stats-challenge').click(function (e) {
var chal_id = $(this).attr('chal-id');
var title = $(this).attr('title') || $(this).attr('data-original-title');
$('#challenge-solves-title').text(title);
loadsolves(chal_id);
});
});

View File

@ -17,13 +17,9 @@ function load_edit_key_modal(key_id, key_type_name) {
}
function create_key(chal, key, key_type) {
$.post(script_root + "/admin/keys", {
chal: chal,
key: key,
key_type: key_type,
nonce: $('#nonce').val()
}, function (data) {
function create_key(chal, chal_data) {
chal_data.push({name: 'nonce', value: $('#nonce').val()});
$.post(script_root + "/admin/keys", chal_data, function (data) {
if (data == "1"){
loadkeys(chal);
$("#create-keys").modal('toggle');
@ -73,17 +69,12 @@ function deletekey(key_id){
}
function updatekey(){
var edit_key_modal = $('#edit-keys form').serializeArray();
var key_id = $('#key-id').val();
var chal = $("#update-keys").attr('chal-id');
var key_data = $('#key-data').val();
var key_type = $('#key-type').val();
var nonce = $('#nonce').val();
$.post(script_root + '/admin/keys/'+key_id, {
'chal':chal,
'key':key_data,
'key_type': key_type,
'nonce': nonce
}, function(data){
$.post(script_root + '/admin/keys/'+key_id, edit_key_modal, function(data){
if (data == "1") {
loadkeys(chal);
$('#edit-keys').modal('toggle');
@ -132,9 +123,13 @@ $(document).ready(function () {
$('#create-keys-submit').click(function (e) {
e.preventDefault();
var chal_data = $('#create-keys-entry-div :input').serializeArray();
var chalid = $("#update-keys").attr('chal-id');
var key_data = $('#create-keys').find('input[name=key]').val();
chal_data.push({name: 'chal', value: chalid});
var key_type = $('#create-keys-select').val();
create_key(chalid, key_data, key_type);
chal_data.push({name: 'key_type', value: key_type});
create_key(chalid, chal_data);
});
});

View File

@ -19,6 +19,7 @@
<script src="{{ request.script_root }}/themes/admin/static/js/vendor/nunjucks.min.js"></script>
<script type="text/javascript">
var script_root = "{{ request.script_root }}";
var csrf_nonce = "{{ nonce }}";
</script>
{% block stylesheets %} {% endblock %}
</head>

View File

@ -7,6 +7,7 @@
{% block content %}
{% include "admin/modals/challenges/challenges.html" %}
{% include "admin/modals/challenges/challenges-solves.html" %}
{% include "admin/modals/tags/tags.html" %}
{% include "admin/modals/files/files.html" %}
@ -57,9 +58,9 @@
<td class="d-none d-md-table-cell d-lg-table-cell">{{ challenge.type }}</td>
<td class="d-none d-md-table-cell d-lg-table-cell text-center">
{% if challenge.hidden %}
<button class="btn-sm btn-danger" type="submit" disabled>Hidden</button>
<span class="badge badge-danger">hidden</span>
{% else %}
<button class="btn-sm btn-success" type="submit" disabled>Visible</button>
<span class="badge badge-success">visible</span>
{% endif %}
</td>
<td>
@ -75,6 +76,13 @@
<i class="btn-fa fas fa-file-alt" aria-hidden="true" chal-id="{{ challenge.id }}"></i>
</span>
<span class="stats-challenge" data-toggle="tooltip"
data-placement="top" chal-id="{{ challenge.id }}"
title="{{ challenge.name }} solves">
<i class="btn-fa fas fa-chart-bar" aria-hidden="true"
chal-id="{{ challenge.id }}"></i>
</span>
<span>&nbsp; &nbsp;</span>
<span class="edit-keys" data-toggle="tooltip" data-placement="top"

View File

@ -28,7 +28,7 @@
</ul>
</div>
<div class="col-md-9">
<form method="POST" autocomplete="off" class="w-100">
<form method="POST" enctype="multipart/form-data" autocomplete="off" class="w-100">
{% for error in errors %}
<div class="alert alert-danger alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
@ -43,7 +43,31 @@
<div role="tabpanel" class="tab-pane active" id="appearance-section">
<div class="form-group">
<label for="ctf_name">CTF Name:</label>
<input class="form-control" id='ctf_name' name='ctf_name' type='text' placeholder="CTF Name" {% if ctf_name is defined and ctf_name != None %}value="{{ ctf_name }}"{% endif %}>
<input class="form-control" id='ctf_name' name='ctf_name' type='text' placeholder="CTF Name"
{% if ctf_name is defined and ctf_name != None %}value="{{ ctf_name }}"{% endif %}>
</div>
<div class="form-group">
<label for="ctf_logo">CTF Logo:</label>
{% if ctf_logo %}
<div class="d-block py-3">
<img id="ctf_logo_preview" class="img-responsive ctf_logo" src="{{ request.script_root }}/files/{{ ctf_logo }}"
height="25">
<button type="button" class="btn-sm btn-danger float-right" onclick="
$('#ctf_logo').attr('type', 'text');
$('#ctf_logo').attr('placeholder', 'Logo will be removed on next update');
$('#ctf_logo').attr('value', '');
$('#ctf_logo').attr('readonly', 'true');
$('#ctf_logo_preview').css('visibility', 'hidden');
">
Remove Logo
</button>
</div>
{% endif %}
<input class="form-control" id='ctf_logo' name='ctf_logo' type='file' placeholder="CTF Logo"
{% if ctf_logo is defined and ctf_logo != None %}value="{{ ctf_logo }}"{% endif %}>
</div>
<div class="form-group">

View File

@ -0,0 +1,38 @@
<div id="challenge-solves-modal" class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-center">
<div class="container">
<div class="row">
<div class="col-md-12">
<h3 id="challenge-solves-title" class="text-center">Solves</h3>
</div>
</div>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<div class="col-md-12">
<table class="table table-striped text-center">
<thead>
<tr>
<td><b>Name</b>
</td>
<td><b>Date</b>
</td>
</tr>
</thead>
<tbody id="challenge-solves-body">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,2 +1,3 @@
<div id="challenge-preview" class="modal fade" tabindex="-1">
</div>

View File

@ -82,6 +82,20 @@
<input type="text" class="form-control" name="country" id="country"
placeholder="Enter country">
</div>
<div class="form-group">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="admin" id="admin-checkbox">
<label class="form-check-label" for="admin-checkbox">Admin</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="verified" id="verified-checkbox">
<label class="form-check-label" for="verified-checkbox">Verified</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="hidden" id="hidden-checkbox">
<label class="form-check-label" for="hidden-checkbox">Hidden</label>
</div>
</div>
<div id="results">
</div>
@ -133,16 +147,18 @@
</td>
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Email</b>
</td>
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Website</b>
<td class="d-none"><b>Website</b>
</td>
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Affiliation</b>
<td class="d-none"><b>Affiliation</b>
</td>
<td class="d-none d-md-table-cell d-lg-table-cell text-center"><b>Country</b>
<td class="d-none"><b>Country</b>
</td>
<td class="text-center"><b>Admin</b>
</td>
<td class="text-center"><b>Verified</b>
</td>
<td class="text-center"><b>Hidden</b>
</td>
<td class="text-center"><b>Settings</b>
</td>
</tr>
@ -154,7 +170,7 @@
<td class="team-name" value="{{ team.name }}"><a href="{{ request.script_root }}/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
</td>
<td class="team-email d-none d-md-table-cell d-lg-table-cell" value="{{ team.email }}">{{ team.email | truncate(32) }}</td>
<td class="team-website d-none d-md-table-cell d-lg-table-cell text-center">
<td class="team-website d-none text-center">
{% if team.website %}
<a href="{{ team.website }}" target="_blank">
<i class="btn-fa fas fa-external-link-alt" data-toggle="tooltip" data-placement="top"
@ -162,21 +178,26 @@
</a>
{% endif %}
</td>
<td class="team-affiliation d-none d-md-table-cell d-lg-table-cell" value="{{ team.affiliation if team.affiliation is not none }}">
<td class="team-affiliation d-none" value="{{ team.affiliation if team.affiliation is not none }}">
<span>{% if team.affiliation %}{{ team.affiliation | truncate(20) }}{% endif %}</span>
</td>
<td class="team-country d-none d-md-table-cell d-lg-table-cell" value="{{ team.country if team.country is not none }}">
<td class="team-country d-none" value="{{ team.country if team.country is not none }}">
<span>{% if team.country %}{{ team.country }}{% endif %}</span>
</td>
<td class="team-admin">
<div class="center-block checkbox text-center">
<input type="checkbox" {% if team.admin %}checked{% endif %}>
</div>
<td class="team-admin d-none d-md-table-cell d-lg-table-cell text-center" value="{{ team.admin }}">
{% if team.admin %}
<span class="badge badge-primary">admin</span>
{% endif %}
</td>
<td class="team-verified">
<div class="center-block checkbox text-center">
<input type="checkbox" {% if team.verified %}checked{% endif %}>
</div>
<td class="team-verified d-none d-md-table-cell d-lg-table-cell text-center" value="{{ team.verified }}">
{% if team.verified %}
<span class="badge badge-success">verified</span>
{% endif %}
</td>
<td class="team-hidden d-none d-md-table-cell d-lg-table-cell text-center" value="{{ team.banned }}">
{% if team.banned %}
<span class="badge badge-danger">hidden</span>
{% endif %}
</td>
<td class="text-center"><span>
<span class="edit-team" data-toggle="tooltip" data-placement="top"
@ -222,7 +243,7 @@
<script>
var nonce = "{{ nonce }}";
function load_update_modal(id, name, email, website, affiliation, country){
function load_update_modal(id, name, email, website, affiliation, country, admin, verified, hidden){
var modal_form = $('#update-user-modal form');
modal_form.find('input[name=name]').val(name);
@ -233,6 +254,10 @@ function load_update_modal(id, name, email, website, affiliation, country){
modal_form.find('input[name=country]').val(country);
modal_form.find('input[name=password]').val('');
modal_form.find('input[name=admin]').prop('checked', admin);
modal_form.find('input[name=verified]').prop('checked', verified);
modal_form.find('input[name=hidden]').prop('checked', hidden);
if (id == 'new'){
$('#update-user-modal .modal-action').text('Create Team');
} else {
@ -280,24 +305,6 @@ $(document).ready(function () {
})
});
$('.team-admin input').on('change', function () {
var elem = $(this).parent().parent().parent();
var id = elem.find('.team-id').text().trim();
var admin = $(this).prop('checked');
console.log(admin);
$.post('{{ request.script_root }}/admin/team/' + id, {'admin': admin, 'nonce': nonce});
});
$('.team-verified input').on('change', function () {
var elem = $(this).parent().parent().parent();
var id = elem.find('.team-id').text().trim();
var verified = $(this).prop('checked');
console.log(verified);
$.post('{{ request.script_root }}/admin/team/' + id, {'verified': verified, 'nonce': nonce});
});
$('#send-user-email').click(function (e) {
e.preventDefault();
var id = $('#email-user input[name="id"]').val();
@ -329,11 +336,15 @@ $(document).ready(function () {
var affiliation = elem.find('.team-affiliation').attr('value') || '';
var country = elem.find('.team-country').attr('value') || '';
load_update_modal(id, name, email, website, affiliation, country);
var admin = elem.find('.team-admin').attr('value') == 'True' || false;
var verified = elem.find('.team-verified').attr('value') == 'True' || false;
var hidden = elem.find('.team-hidden').attr('value') == 'True' || false;
load_update_modal(id, name, email, website, affiliation, country, admin, verified, hidden);
});
$('.create-team').click(function () {
load_update_modal('new', '', '', '', '', '');
load_update_modal('new', '', '', '', '', '', false, false, false);
});
$('.delete-team').click(function () {

View File

@ -2,6 +2,8 @@ var challenges;
var user_solves = [];
var templates = {};
window.challenge = new Object();
function loadchal(id) {
var obj = $.grep(challenges['game'], function (e) {
return e.id == id;
@ -20,51 +22,77 @@ function loadchalbyname(chalname) {
function updateChalWindow(obj) {
$.get(script_root + "/chals/" + obj.id, function(challenge_data){
$.get(script_root + obj.template, function (template_data) {
$('#chal-window').empty();
var template = nunjucks.compile(template_data);
var solves = obj.solves == 1 ? " Solve" : " Solves";
var solves = obj.solves + solves;
$.getScript(script_root + obj.script, function(){
$.get(script_root + obj.template, function (template_data) {
$('#chal-window').empty();
var nonce = $('#nonce').val();
var template = nunjucks.compile(template_data);
var md = window.markdownit({
html: true,
});
var solves = obj.solves == 1 ? " Solve" : " Solves";
var solves = obj.solves + solves;
challenge_data['description'] = md.render(challenge_data['description']);
challenge_data['script_root'] = script_root;
challenge_data['solves'] = solves;
var nonce = $('#nonce').val();
$('#chal-window').append(template.render(challenge_data));
$.getScript(script_root + obj.script,
function () {
// Handle Solves tab
$('.chal-solves').click(function (e) {
getsolves($('#chal-id').val())
});
$('.nav-tabs a').click(function (e) {
e.preventDefault();
$(this).tab('show')
});
window.challenge.preRender();
// Handle modal toggling
$('#chal-window').on('hide.bs.modal', function (event) {
$("#answer-input").removeClass("wrong");
$("#answer-input").removeClass("correct");
$("#incorrect-key").slideUp();
$("#correct-key").slideUp();
$("#already-solved").slideUp();
$("#too-fast").slideUp();
});
challenge_data['description'] = window.challenge.render(challenge_data['description']);
challenge_data['script_root'] = script_root;
challenge_data['solves'] = solves;
// $('pre code').each(function(i, block) {
// hljs.highlightBlock(block);
// });
$('#chal-window').append(template.render(challenge_data));
window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
$('#chal-window').modal();
$('.chal-solves').click(function (e) {
getsolves($('#chal-id').val())
});
$('.nav-tabs a').click(function (e) {
e.preventDefault();
$(this).tab('show')
});
// Handle modal toggling
$('#chal-window').on('hide.bs.modal', function (event) {
$("#answer-input").removeClass("wrong");
$("#answer-input").removeClass("correct");
$("#incorrect-key").slideUp();
$("#correct-key").slideUp();
$("#already-solved").slideUp();
$("#too-fast").slideUp();
});
$('#submit-key').click(function (e) {
e.preventDefault();
$('#submit-key').addClass("disabled-button");
$('#submit-key').prop('disabled', true);
window.challenge.submit(function (data) {
renderSubmissionResponse(data)
});
});
$("#answer-input").keyup(function (event) {
if (event.keyCode == 13) {
$("#submit-key").click();
}
});
$(".input-field").bind({
focus: function () {
$(this).parent().addClass('input--filled');
$label = $(this).siblings(".input-label");
},
blur: function () {
if ($(this).val() === '') {
$(this).parent().removeClass('input--filled');
$label = $(this).siblings(".input-label");
$label.removeClass('input--hide');
}
}
});
window.challenge.postRender();
window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
$('#chal-window').modal();
});
});
});
}
@ -76,72 +104,65 @@ $("#answer-input").keyup(function(event){
});
function submitkey(chal, key, nonce, cb) {
$('#submit-key').addClass("disabled-button");
$('#submit-key').prop('disabled', true);
$.post(script_root + "/chal/" + chal, {
key: key,
nonce: nonce
}, function (data) {
var result = $.parseJSON(JSON.stringify(data));
function renderSubmissionResponse(data, cb){
var result = $.parseJSON(JSON.stringify(data));
var result_message = $('#result-message');
var result_notification = $('#result-notification');
var answer_input = $("#answer-input");
result_notification.removeClass();
result_message.text(result.message);
var result_message = $('#result-message');
var result_notification = $('#result-notification');
var answer_input = $("#answer-input");
result_notification.removeClass();
result_message.text(result.message);
if (result.status == -1){
window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash
return
}
else if (result.status == 0){ // Incorrect key
result_notification.addClass('alert alert-danger alert-dismissable text-center');
result_notification.slideDown();
if (result.status == -1) {
window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash
return
}
else if (result.status == 0) { // Incorrect key
result_notification.addClass('alert alert-danger alert-dismissable text-center');
result_notification.slideDown();
answer_input.removeClass("correct");
answer_input.addClass("wrong");
setTimeout(function () {
answer_input.removeClass("wrong");
}, 3000);
}
else if (result.status == 1){ // Challenge Solved
result_notification.addClass('alert alert-success alert-dismissable text-center');
result_notification.slideDown();
$('.chal-solves').text((parseInt($('.chal-solves').text().split(" ")[0]) + 1 + " Solves") );
answer_input.val("");
answer_input.removeClass("correct");
answer_input.addClass("wrong");
setTimeout(function () {
answer_input.removeClass("wrong");
answer_input.addClass("correct");
}
else if (result.status == 2){ // Challenge already solved
result_notification.addClass('alert alert-info alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("correct");
}
else if (result.status == 3){ // Keys per minute too high
result_notification.addClass('alert alert-warning alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("too-fast");
setTimeout(function() {
answer_input.removeClass("too-fast");
}, 3000);
}
marksolves();
updatesolves();
setTimeout(function(){
$('.alert').slideUp();
$('#submit-key').removeClass("disabled-button");
$('#submit-key').prop('disabled', false);
}, 3000);
}
else if (result.status == 1) { // Challenge Solved
result_notification.addClass('alert alert-success alert-dismissable text-center');
result_notification.slideDown();
if (cb) {
cb(result);
}
})
$('.chal-solves').text((parseInt($('.chal-solves').text().split(" ")[0]) + 1 + " Solves"));
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
}
else if (result.status == 2) { // Challenge already solved
result_notification.addClass('alert alert-info alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("correct");
}
else if (result.status == 3) { // Keys per minute too high
result_notification.addClass('alert alert-warning alert-dismissable text-center');
result_notification.slideDown();
answer_input.addClass("too-fast");
setTimeout(function () {
answer_input.removeClass("too-fast");
}, 3000);
}
marksolves();
updatesolves();
setTimeout(function () {
$('.alert').slideUp();
$('#submit-key').removeClass("disabled-button");
$('#submit-key').prop('disabled', false);
}, 3000);
if (cb) {
cb(result);
}
}
function marksolves(cb) {

View File

@ -28,12 +28,19 @@
<script src="{{ request.script_root }}/themes/{{ ctf_theme() }}/static/js/vendor/nunjucks.min.js"></script>
<script type="text/javascript">
var script_root = "{{ request.script_root }}";
var csrf_nonce = "{{ nonce }}"
</script>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<div class="container">
<a href="{{ request.script_root }}/" class="navbar-brand">{{ ctf_name() }}</a>
<a href="{{ request.script_root }}/" class="navbar-brand">
{% if ctf_logo() %}
<img class="img-responsive ctf_logo" src="{{ request.script_root }}/files/{{ ctf_logo() }}" height="25" alt="{{ ctf_name() }}">
{% else %}
{{ ctf_name() }}
{% endif %}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#base-navbars"
aria-controls="base-navbars" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>

View File

@ -150,6 +150,7 @@ def init_utils(app):
app.jinja_env.globals.update(can_register=can_register)
app.jinja_env.globals.update(can_send_mail=can_send_mail)
app.jinja_env.globals.update(ctf_name=ctf_name)
app.jinja_env.globals.update(ctf_logo=ctf_logo)
app.jinja_env.globals.update(ctf_theme=ctf_theme)
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
app.jinja_env.globals.update(get_registered_scripts=get_registered_scripts)
@ -211,6 +212,11 @@ def ctf_name():
return name if name else 'CTFd'
@cache.memoize()
def ctf_logo():
return get_config('ctf_logo')
@cache.memoize()
def ctf_theme():
theme = get_config('ctf_theme')
@ -661,7 +667,7 @@ def verify_email(addr):
text = """Please click the following link to confirm your email address for {ctf_name}: {url}/{token}""".format(
ctf_name=get_config('ctf_name'),
url=url_for('auth.confirm_user', _external=True),
token=base64encode(token, urlencode=True)
token=base64encode(token)
)
sendmail(addr, text)
@ -673,7 +679,7 @@ def forgot_password(email, team_name):
{0}/{1}
""".format(url_for('auth.reset_password', _external=True), base64encode(token, urlencode=True))
""".format(url_for('auth.reset_password', _external=True), base64encode(token))
sendmail(email, text)
@ -700,35 +706,30 @@ def sha512(string):
return hashlib.sha512(string).hexdigest()
def base64encode(s, urlencode=False):
def base64encode(s):
if six.PY3 and isinstance(s, six.string_types):
s = s.encode('utf-8')
else:
# Python 2 support because the base64 module doesnt like unicode
s = str(s)
encoded = base64.urlsafe_b64encode(s)
encoded = base64.urlsafe_b64encode(s).rstrip(b'\n=')
if six.PY3:
try:
encoded = encoded.decode('utf-8')
except UnicodeDecodeError:
pass
if urlencode:
encoded = quote(encoded)
return encoded
def base64decode(s, urldecode=False):
if urldecode:
s = unquote(s)
def base64decode(s):
if six.PY3 and isinstance(s, six.string_types):
s = s.encode('utf-8')
else:
# Python 2 support because the base64 module doesnt like unicode
s = str(s)
decoded = base64.urlsafe_b64decode(s)
decoded = base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b'='))
if six.PY3:
try:
decoded = decoded.decode('utf-8')

View File

@ -4,11 +4,11 @@ import functools
def during_ctf_time_only(f):
'''
"""
Decorator to restrict an endpoint to only be seen during a CTF
:param f:
:return:
'''
"""
@functools.wraps(f)
def during_ctf_time_only_wrapper(*args, **kwargs):
if utils.ctftime() or utils.is_admin():
@ -28,6 +28,11 @@ def during_ctf_time_only(f):
def require_verified_emails(f):
"""
Decorator to restrict an endpoint to users with confirmed active email addresses
:param f:
:return:
"""
@functools.wraps(f)
def require_verified_emails_wrapper(*args, **kwargs):
if utils.get_config('verify_emails'):
@ -39,6 +44,11 @@ def require_verified_emails(f):
def viewable_without_authentication(status_code=None):
"""
Decorator that allows users to view the specified endpoint if viewing challenges without authentication is enabled
:param status_code:
:return:
"""
def viewable_without_authentication_decorator(f):
@functools.wraps(f)
def viewable_without_authentication_wrapper(*args, **kwargs):
@ -55,6 +65,11 @@ def viewable_without_authentication(status_code=None):
def authed_only(f):
"""
Decorator that requires the user to be authenticated
:param f:
:return:
"""
@functools.wraps(f)
def authed_only_wrapper(*args, **kwargs):
if session.get('id'):
@ -66,6 +81,11 @@ def authed_only(f):
def admins_only(f):
"""
Decorator that requires the user to be authenticated and an admin
:param f:
:return:
"""
@functools.wraps(f)
def admins_only_wrapper(*args, **kwargs):
if session.get('admin'):

View File

View File

@ -6,4 +6,6 @@ rednose>=1.1.1
pep8>=1.7.0
freezegun>=0.3.9
psycopg2>=2.7.3.1
psycopg2-binary>=2.7.3.1
codecov>=2.0.9
nose-randomly>=1.2.5

View File

@ -1,4 +1,4 @@
Flask==0.12.2
Flask==1.0.0
Flask-SQLAlchemy==2.3.2
Flask-Session==0.3.1
Flask-Caching==1.3.3
@ -19,3 +19,4 @@ netaddr==0.7.19
redis==2.10.6
datafreeze==0.1.0
gevent==1.2.2
python-dotenv==0.8.2

View File

@ -16,6 +16,7 @@ def test_admin_post_config_values():
data = {
'nonce': sess.get('nonce'),
'ctf_name': 'CTFd',
'ctf_logo': '',
'ctf_theme': 'core',
'workshop_mode': 'on',
'paused': 'on',
@ -59,6 +60,7 @@ def test_admin_post_config_values():
result = {
'ctf_name': 'CTFd',
'ctf_logo': None,
'ctf_theme': 'core',
'workshop_mode': True,
'paused': True,

View File

@ -208,7 +208,7 @@ def test_admins_can_delete_challenges():
def test_admins_can_delete_challenges_with_extras():
""""Test that admins can delete challenges that have a hint"""
"""Test that admins can delete challenges that have a hint"""
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
@ -274,6 +274,24 @@ def test_admin_chal_detail_returns_proper_data():
destroy_ctfd(app)
def test_admin_load_chal_solves():
app = create_ctfd()
with app.app_context():
client = login_as_user(app, name="admin", password="password")
chal1 = gen_challenge(app.db)
flag1 = gen_flag(app.db, chal=chal1.id, flag='flag')
chal1_id = chal1.id
gen_solve(app.db, teamid=1, chalid=chal1_id)
r = client.get('/admin/chal/1/solves')
data = r.get_data(as_text=True)
assert json.loads(data)
destroy_ctfd(app)
def test_admins_can_create_teams():
'''Test that admins can create new teams'''
app = create_ctfd()

View File

@ -117,8 +117,10 @@ def gen_file(db, chal, location):
return f
def gen_flag(db, chal, flag='flag', key_type='static'):
def gen_flag(db, chal, flag='flag', key_type='static', data=None):
key = Keys(chal, flag, key_type)
if data:
key.data = data
db.session.add(key)
db.session.commit()
return key

View File

@ -63,19 +63,14 @@ def test_base64encode():
if six.PY2:
assert base64encode('abc123') == 'YWJjMTIz'
assert base64encode(unicode('abc123')) == 'YWJjMTIz'
assert base64encode(unicode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'), urlencode=True) == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D'
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8='
assert base64encode('user+user@ctfd.io', urlencode=True) == 'dXNlcit1c2VyQGN0ZmQuaW8%3D'
assert base64encode('😆') == '8J-Yhg=='
assert base64encode('😆', urlencode=True) == '8J-Yhg%3D%3D'
assert base64encode(unicode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4')) == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ'
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8'
assert base64encode('😆') == '8J-Yhg'
else:
assert base64encode('abc123') == 'YWJjMTIz'
assert base64encode('abc123') == 'YWJjMTIz'
assert base64encode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4', urlencode=True) == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D'
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8='
assert base64encode('user+user@ctfd.io', urlencode=True) == 'dXNlcit1c2VyQGN0ZmQuaW8%3D'
assert base64encode('😆') == '8J-Yhg=='
assert base64encode('😆', urlencode=True) == '8J-Yhg%3D%3D'
assert base64encode('"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4') == 'InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ'
assert base64encode('user+user@ctfd.io') == 'dXNlcit1c2VyQGN0ZmQuaW8'
assert base64encode('😆') == '8J-Yhg'
def test_base64decode():
@ -83,17 +78,13 @@ def test_base64decode():
if six.PY2:
assert base64decode('YWJjMTIz') == 'abc123'
assert base64decode(unicode('YWJjMTIz')) == 'abc123'
assert base64decode(unicode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D'), urldecode=True) == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
assert base64decode('8J-Yhg==') == '😆'
assert base64decode('8J-Yhg%3D%3D', urldecode=True) == '😆'
assert base64decode(unicode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ')) == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
assert base64decode('8J-Yhg') == '😆'
else:
assert base64decode('YWJjMTIz') == 'abc123'
assert base64decode('YWJjMTIz') == 'abc123'
assert base64decode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ%3D', urldecode=True) == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
assert base64decode('dXNlcit1c2VyQGN0ZmQuaW8=') == 'user+user@ctfd.io'
assert base64decode('dXNlcit1c2VyQGN0ZmQuaW8%3D', urldecode=True) == 'user+user@ctfd.io'
assert base64decode('8J-Yhg==') == '😆'
assert base64decode('8J-Yhg%3D%3D', urldecode=True) == '😆'
assert base64decode('InRlc3RAbWFpbGluYXRvci5jb20iLkRHeGVvQS5sQ3NzVTNNMlF1QmZvaE8tRnRkZ0RRTEtiVTQ') == '"test@mailinator.com".DGxeoA.lCssU3M2QuBfohO-FtdgDQLKbU4'
assert base64decode('dXNlcit1c2VyQGN0ZmQuaW8') == 'user+user@ctfd.io'
assert base64decode('8J-Yhg') == '😆'
def test_override_template():
@ -251,7 +242,7 @@ def test_verify_email(mock_smtp):
# This is currently not actually validated
msg = ("Please click the following link to confirm"
" your email address for CTFd:"
" http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc%3D")
" http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc")
ctf_name = get_config('ctf_name')
email_msg = MIMEText(msg)

View File

@ -118,7 +118,7 @@ def test_expired_confirmation_links():
client = login_as_user(app, name="user", password="password")
# user@user.com "2012-01-14 03:21:34"
confirm_link = 'http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc%3D'
confirm_link = 'http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc'
r = client.get(confirm_link)
assert "Your confirmation link has expired" in r.get_data(as_text=True)
@ -137,7 +137,7 @@ def test_invalid_confirmation_links():
client = login_as_user(app, name="user", password="password")
# user@user.com "2012-01-14 03:21:34"
confirm_link = 'http://localhost/confirm/a8375iyu<script>alert(1)<script>hn3048wueorighkgnsfg%3D%3D'
confirm_link = 'http://localhost/confirm/a8375iyu<script>alert(1)<script>hn3048wueorighkgnsfg'
r = client.get(confirm_link)
assert "Your confirmation token is invalid" in r.get_data(as_text=True)
@ -180,7 +180,7 @@ def test_invalid_reset_password_link():
with app.test_client() as client:
# user@user.com "2012-01-14 03:21:34"
forgot_link = 'http://localhost/reset_password/5678ytfghjiu876tyfg<>hvbnmkoi9u87y6trdfcgvhbnm,lp09iujmk%3D'
forgot_link = 'http://localhost/reset_password/5678ytfghjiu876tyfg<>hvbnmkoi9u87y6trdfcgvhbnm,lp09iujmk'
r = client.get(forgot_link)
assert "Your reset token is invalid" in r.get_data(as_text=True)

View File

@ -132,6 +132,46 @@ def test_submitting_correct_flag():
destroy_ctfd(app)
def test_submitting_correct_static_case_insensitive_flag():
"""Test that correct static flags are correct if the static flag is marked case_insensitive"""
app = create_ctfd()
with app.app_context():
register_user(app)
client = login_as_user(app)
chal = gen_challenge(app.db)
flag = gen_flag(app.db, chal=chal.id, flag='flag', data="case_insensitive")
with client.session_transaction() as sess:
data = {
"key": 'FLAG',
"nonce": sess.get('nonce')
}
r = client.post('/chal/{}'.format(chal.id), data=data)
assert r.status_code == 200
resp = json.loads(r.data.decode('utf8'))
assert resp.get('status') == 1 and resp.get('message') == "Correct"
destroy_ctfd(app)
def test_submitting_correct_regex_case_insensitive_flag():
"""Test that correct regex flags are correct if the regex flag is marked case_insensitive"""
app = create_ctfd()
with app.app_context():
register_user(app)
client = login_as_user(app)
chal = gen_challenge(app.db)
flag = gen_flag(app.db, chal=chal.id, key_type='regex', flag='flag', data="case_insensitive")
with client.session_transaction() as sess:
data = {
"key": 'FLAG',
"nonce": sess.get('nonce')
}
r = client.post('/chal/{}'.format(chal.id), data=data)
assert r.status_code == 200
resp = json.loads(r.data.decode('utf8'))
assert resp.get('status') == 1 and resp.get('message') == "Correct"
destroy_ctfd(app)
def test_submitting_incorrect_flag():
"""Test that incorrect flags are incorrect"""
app = create_ctfd()

View File

@ -567,7 +567,7 @@ def test_user_can_confirm_email(mock_smtp):
assert r.location == "http://localhost/confirm" # We got redirected to /confirm
# Use precalculated confirmation secret
r = client.get('http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc%3D')
r = client.get('http://localhost/confirm/InVzZXJAdXNlci5jb20iLkFmS0dQZy5kLUJnVkgwaUhadzFHaXVENHczWTJCVVJwdWc')
assert r.location == 'http://localhost/challenges'
# The team is now verified

6
wsgi.py Normal file
View File

@ -0,0 +1,6 @@
from CTFd import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True, threaded=True, host="127.0.0.1", port=4000)