Skip to content

Commit

Permalink
1.2.0 (CTFd#627)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ColdHeat authored May 3, 2018
1 parent 9c812ad commit 36c83b5
Show file tree
Hide file tree
Showing 40 changed files with 700 additions and 434 deletions.
2 changes: 2 additions & 0 deletions .flaskenv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FLASK_ENV=development
FLASK_RUN_PORT=4000
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions CTFd/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions CTFd/admin/challenges.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
33 changes: 15 additions & 18 deletions CTFd/admin/teams.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -119,31 +127,17 @@ 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)
website = request.form.get('website', None)
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:
Expand Down Expand Up @@ -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']})
Expand Down
4 changes: 2 additions & 2 deletions CTFd/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion CTFd/plugins/challenges/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def attempt(chal, request):
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'

Expand Down
20 changes: 7 additions & 13 deletions CTFd/plugins/challenges/assets/standard-challenge-create.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
// 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();
$('#max_attempts').val('');
}
});

$(document).ready(function(){
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
});
52 changes: 31 additions & 21 deletions CTFd/plugins/challenges/assets/standard-challenge-modal.js
Original file line number Diff line number Diff line change
@@ -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(){

};

window.challenge.render = function(markdown){
return window.challenge.renderer.render(markdown);
};


$(".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 = 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);
});
};
14 changes: 4 additions & 10 deletions CTFd/plugins/challenges/assets/standard-challenge-update.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
});
Expand Down
25 changes: 20 additions & 5 deletions CTFd/plugins/keys/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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


Expand Down
6 changes: 5 additions & 1 deletion CTFd/plugins/keys/assets/regex/create-regex-modal.njk
Original file line number Diff line number Diff line change
@@ -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">
<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>
57 changes: 31 additions & 26 deletions CTFd/plugins/keys/assets/regex/edit-regex-modal.njk
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit 36c83b5

Please sign in to comment.