From ff58ef181a2fa560edf4c440d31525aa902ee0dc Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Thu, 17 Sep 2015 14:13:53 -0400 Subject: [PATCH] Supports PY3, refinements to chal editor and viewer, model changes to resolve issues --- CTFd/__init__.py | 2 +- CTFd/admin.py | 114 +++++++++++++++------------ CTFd/challenges.py | 36 ++++++--- CTFd/config.py | 1 + CTFd/models.py | 16 ++-- CTFd/static/admin/css/style.css | 25 +++++- CTFd/static/admin/js/chalboard.js | 80 +++++++++++++------ CTFd/static/css/style.css | 24 ++++++ CTFd/static/js/chalboard.js | 4 +- CTFd/static/js/scoreboard.js | 17 +++- CTFd/static/js/team.js | 4 +- CTFd/templates/admin/base.html | 4 +- CTFd/templates/admin/chals.html | 82 +++++++++---------- CTFd/templates/admin/scoreboard.html | 2 +- CTFd/templates/admin/team.html | 33 +++++++- CTFd/templates/admin/teams.html | 21 +++-- CTFd/templates/chals.html | 31 ++++---- CTFd/templates/scoreboard.html | 5 +- CTFd/templates/team.html | 4 +- CTFd/templates/teams.html | 16 +++- CTFd/utils.py | 14 ++-- CTFd/views.py | 20 ++++- requirements.txt | 21 ++--- 23 files changed, 382 insertions(+), 194 deletions(-) diff --git a/CTFd/__init__.py b/CTFd/__init__.py index 52a481aa..4b91659d 100644 --- a/CTFd/__init__.py +++ b/CTFd/__init__.py @@ -22,7 +22,7 @@ def create_app(config='CTFd.config'): global mail mail = Mail(app) - Session(app) + #Session(app) from CTFd.views import views from CTFd.challenges import challenges diff --git a/CTFd/admin.py b/CTFd/admin.py index 6b68e436..48d3209e 100644 --- a/CTFd/admin.py +++ b/CTFd/admin.py @@ -147,7 +147,6 @@ def admin_css(): if request.method == 'POST': css = request.form['css'] css = set_config('css', css) - print css return "1" return "0" @@ -198,12 +197,12 @@ def admin_chals(): if request.method == 'POST': chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all() - json = {'game':[]} + json_data = {'game':[]} for x in chals: - json['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5]}) + json_data['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5]}) db.session.close() - return jsonify(json) + return jsonify(json_data) else: return render_template('admin/chals.html') @@ -212,21 +211,25 @@ def admin_chals(): @admins_only def admin_keys(chalid): if request.method == 'GET': - keys = Keys.query.filter_by(chal=chalid).all() - json = {'keys':[]} - for x in keys: - json['keys'].append({'id':x.id, 'key':x.flag, 'type':x.key_type}) - return jsonify(json) + chal = Challenges.query.filter_by(id=chalid).first_or_404() + json_data = {'keys':[]} + flags = json.loads(chal.flags) + for i, x in enumerate(flags): + json_data['keys'].append({'id':i, 'key':x['flag'], 'type':x['type']}) + return jsonify(json_data) elif request.method == 'POST': - keys = Keys.query.filter_by(chal=chalid).all() - for x in keys: - db.session.delete(x) + chal = Challenges.query.filter_by(id=chalid).first() newkeys = request.form.getlist('keys[]') newvals = request.form.getlist('vals[]') + print(list(zip(newkeys, newvals))) + flags = [] for flag, val in zip(newkeys, newvals): - key = Keys(chalid, flag, val) - db.session.add(key) + flag_dict = {'flag':flag, 'type':int(val)} + flags.append(flag_dict) + json_data = json.dumps(flags) + + chal.flags = json_data db.session.commit() db.session.close() @@ -238,10 +241,10 @@ def admin_keys(chalid): def admin_tags(chalid): if request.method == 'GET': tags = Tags.query.filter_by(chal=chalid).all() - json = {'tags':[]} + json_data = {'tags':[]} for x in tags: - json['tags'].append({'id':x.id, 'chal':x.chal, 'tag':x.tag}) - return jsonify(json) + json_data['tags'].append({'id':x.id, 'chal':x.chal, 'tag':x.tag}) + return jsonify(json_data) elif request.method == 'POST': newtags = request.form.getlist('tags[]') @@ -269,10 +272,10 @@ def admin_delete_tags(tagid): def admin_files(chalid): if request.method == 'GET': files = Files.query.filter_by(chal=chalid).all() - json = {'files':[]} + json_data = {'files':[]} for x in files: - json['files'].append({'id':x.id, 'file':x.location}) - return jsonify(json) + json_data['files'].append({'id':x.id, 'file':x.location}) + return jsonify(json_data) if request.method == 'POST': if request.form['method'] == "delete": f = Files.query.filter_by(id=request.form['file']).first_or_404() @@ -305,11 +308,20 @@ def admin_files(chalid): return redirect('/admin/chals') -@admin.route('/admin/teams') +@admin.route('/admin/teams', defaults={'page':'1'}) +@admin.route('/admin/teams/') @admins_only -def admin_teams(): - teams = Teams.query.all() - return render_template('admin/teams.html', teams=teams) +def admin_teams(page): + page = abs(int(page)) + results_per_page = 50 + page_start = results_per_page * ( page - 1 ) + page_end = results_per_page * ( page - 1 ) + results_per_page + + teams = Teams.query.slice(page_start, page_end).all() + count = db.session.query(db.func.count(Teams.id)).first()[0] + print(count) + pages = int(count / results_per_page) + (count % results_per_page > 0) + return render_template('admin/teams.html', teams=teams, pages=pages) @admin.route('/admin/team/', methods=['GET', 'POST']) @@ -317,17 +329,19 @@ def admin_teams(): def admin_team(teamid): user = Teams.query.filter_by(id=teamid).first() solves = Solves.query.filter_by(teamid=teamid).all() - addrs = Tracking.query.filter_by(team=teamid).group_by(Tracking.ip).all() + addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all() + wrong_keys = WrongKeys.query.filter_by(team=teamid).order_by(WrongKeys.date.desc()).all() score = user.score() place = user.place() if request.method == 'GET': - return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place) + return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place, wrong_keys=wrong_keys) elif request.method == 'POST': admin_user = request.form.get('admin', "false") admin_user = 1 if admin_user == "true" else 0 if admin: user.admin = 1 + user.banned = 1 db.session.commit() return jsonify({'data': ['success']}) @@ -407,16 +421,16 @@ def delete_team(teamid): def admin_graph(graph_type): if graph_type == 'categories': categories = db.session.query(Challenges.category, db.func.count(Challenges.category)).group_by(Challenges.category).all() - json = {'categories':[]} + json_data = {'categories':[]} for category, count in categories: - json['categories'].append({'category':category, 'count':count}) - return jsonify(json) + json_data['categories'].append({'category':category, 'count':count}) + return jsonify(json_data) elif graph_type == "solves": solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all() - json = {} + json_data = {} for chal, count in solves: - json[chal.chal.name] = count - return jsonify(json) + json_data[chal.chal.name] = count + return jsonify(json_data) @admin.route('/admin/scoreboard') @@ -436,10 +450,10 @@ def admin_scores(): quickest = db.func.max(Solves.date).label('quickest') teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest) db.session.close() - json = {'teams':[]} + json_data = {'teams':[]} for i, x in enumerate(teams): - json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)}) - return jsonify(json) + json_data['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)}) + return jsonify(json_data) @admin.route('/admin/solves/', methods=['GET']) @@ -450,10 +464,10 @@ def admin_solves(teamid="all"): else: solves = Solves.query.filter_by(teamid=teamid).all() db.session.close() - json = {'solves':[]} + json_data = {'solves':[]} for x in solves: - json['solves'].append({'id':x.id, 'chal':x.chal.name, 'chalid':x.chalid,'team':x.teamid, 'value': x.chal.value, 'category':x.chal.category, 'time':unix_time(x.date)}) - return jsonify(json) + json_data['solves'].append({'id':x.id, 'chal':x.chal.name, 'chalid':x.chalid,'team':x.teamid, 'value': x.chal.value, 'category':x.chal.category, 'time':unix_time(x.date)}) + return jsonify(json_data) @admin.route('/admin/solves///delete', methods=['POST']) @@ -531,14 +545,14 @@ def admin_fails(teamid='all'): fails = WrongKeys.query.count() solves = Solves.query.count() db.session.close() - json = {'fails':str(fails), 'solves': str(solves)} - return jsonify(json) + json_data = {'fails':str(fails), 'solves': str(solves)} + return jsonify(json_data) else: fails = WrongKeys.query.filter_by(team=teamid).count() solves = Solves.query.filter_by(teamid=teamid).count() db.session.close() - json = {'fails':str(fails), 'solves': str(solves)} - return jsonify(json) + json_data = {'fails':str(fails), 'solves': str(solves)} + return jsonify(json_data) @admin.route('/admin/chal/new', methods=['POST']) @@ -546,14 +560,12 @@ def admin_fails(teamid='all'): def admin_create_chal(): files = request.files.getlist('files[]') - # Create challenge - chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category']) - db.session.add(chal) - db.session.commit() + ## TODO: Expand to support multiple flags + flags = [{'flag':request.form['key'], 'type':int(request.form['key_type[0]'])}] - # Add keys - key = Keys(chal.id, request.form['key'], request.form['key_type[0]']) - db.session.add(key) + # Create challenge + chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category'], flags) + db.session.add(chal) db.session.commit() for f in files: @@ -562,7 +574,7 @@ def admin_create_chal(): if len(filename) <= 0: continue - md5hash = hashlib.md5(filename).hexdigest() + md5hash = hashlib.md5(os.urandom(64)).hexdigest() if not os.path.exists(os.path.join(os.path.normpath(app.static_folder), 'uploads', md5hash)): os.makedirs(os.path.join(os.path.normpath(app.static_folder), 'uploads', md5hash)) @@ -581,7 +593,7 @@ def admin_create_chal(): def admin_delete_chal(): challenge = Challenges.query.filter_by(id=request.form['id']).first() if challenge: - WrongKeys.query.filter_by(chal=challenge.id).delete() + WrongKeys.query.filter_by(chalid=challenge.id).delete() Solves.query.filter_by(chalid=challenge.id).delete() Keys.query.filter_by(chal=challenge.id).delete() files = Files.query.filter_by(chal=challenge.id).all() diff --git a/CTFd/challenges.py b/CTFd/challenges.py index aeae42d3..74382afc 100644 --- a/CTFd/challenges.py +++ b/CTFd/challenges.py @@ -6,6 +6,7 @@ from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys import time import re import logging +import json challenges = Blueprint('challenges', __name__) @@ -21,7 +22,7 @@ def challenges_view(): if can_view_challenges(): return render_template('chals.html', ctftime=ctftime()) else: - return redirect(url_for('login', next="challenges")) + return redirect('/login') @challenges.route('/chals', methods=['GET']) @@ -55,7 +56,7 @@ def chals_per_solves(): for chal, count in solves: json[chal.chal.name] = count return jsonify(json) - return redirect(url_for('login', next="/chals/solves")) + return redirect('/login') @challenges.route('/solves') @@ -80,7 +81,7 @@ def attempts(): chals = Challenges.query.add_columns('id').all() json = {'maxattempts':[]} for chal, chalid in chals: - fails = WrongKeys.query.filter_by(team=session['id'], chal=chalid).count() + fails = WrongKeys.query.filter_by(team=session['id'], chalid=chalid).count() if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0: json['maxattempts'].append({'chalid':chalid}) return jsonify(json) @@ -97,7 +98,7 @@ def fails(teamid): @challenges.route('/chal//solves', methods=['GET']) def who_solved(chalid): - solves = Solves.query.filter_by(chalid=chalid) + solves = Solves.query.filter_by(chalid=chalid).order_by(Solves.date.asc()) json = {'teams':[]} for solve in solves: json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date}) @@ -109,12 +110,16 @@ def chal(chalid): if not ctftime(): return redirect('/challenges') if authed(): - fails = WrongKeys.query.filter_by(team=session['id'],chal=chalid).count() + fails = WrongKeys.query.filter_by(team=session['id'], chalid=chalid).count() logger = logging.getLogger('keys') data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id'])) - print "[{0}] {1} submitted {2} with kpm {3}".format(*data) + print("[{0}] {1} submitted {2} with kpm {3}".format(*data)) + + # Hit max attempts if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0: return "4" #too many tries on this challenge + + # Anti-bruteforce / submitting keys too quickly if get_kpm(session['id']) > 10: wrong = WrongKeys(session['id'], chalid, request.form['key']) db.session.add(wrong) @@ -122,21 +127,26 @@ def chal(chalid): db.session.close() logger.warn("[{0}] {1} submitted {2} with kpm {3} [TOO FAST]".format(*data)) return "3" # Submitting too fast + solves = Solves.query.filter_by(teamid=session['id'], chalid=chalid).first() + + # Challange not solved yet if not solves: - keys = Keys.query.filter_by(chal=chalid).all() - key = request.form['key'].strip().lower() + chal = Challenges.query.filter_by(id=chalid).first() + key = str(request.form['key'].strip().lower()) + keys = json.loads(chal.flags) for x in keys: - if x.key_type == 0: #static key - if x.flag.strip().lower() == key: + if x['type'] == 0: #static key + print(x['flag'], key.strip().lower()) + if x['flag'] == key.strip().lower(): solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key) db.session.add(solve) db.session.commit() db.session.close() logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data)) return "1" # key was correct - elif x.key_type == 1: #regex - res = re.match(str(x), key, re.IGNORECASE) + elif x['type'] == 1: #regex + res = re.match(str(x['flag']), key, re.IGNORECASE) if res and res.group() == key: solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key) db.session.add(solve) @@ -151,6 +161,8 @@ def chal(chalid): db.session.close() logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data)) return '0' # key was wrong + + # Challenge already solved else: logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data)) return "2" # challenge was already solved diff --git a/CTFd/config.py b/CTFd/config.py index 11b90439..175f8a69 100644 --- a/CTFd/config.py +++ b/CTFd/config.py @@ -1,6 +1,7 @@ import os ##### SERVER SETTINGS ##### SECRET_KEY = os.urandom(64) +#SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost/ctfd' SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db' SESSION_TYPE = "filesystem" SESSION_FILE_DIR = "/tmp/flask_session" diff --git a/CTFd/models.py b/CTFd/models.py index ab485c1f..cf07747f 100644 --- a/CTFd/models.py +++ b/CTFd/models.py @@ -6,6 +6,7 @@ from passlib.hash import bcrypt_sha256 import datetime import hashlib +import json def sha512(string): return hashlib.sha512(string).hexdigest() @@ -37,13 +38,15 @@ class Challenges(db.Model): description = db.Column(db.Text) value = db.Column(db.Integer) category = db.Column(db.String(80)) - # add open category + flags = db.Column(db.Text) + hidden = db.Column(db.Boolean) - def __init__(self, name, description, value, category): + def __init__(self, name, description, value, category, flags): self.name = name self.description = description self.value = value self.category = category + self.flags = json.dumps(flags) def __repr__(self): return '' % self.name @@ -102,7 +105,7 @@ class Teams(db.Model): def __init__(self, name, email, password): self.name = name self.email = email - self.password = bcrypt_sha256.encrypt(password) + self.password = bcrypt_sha256.encrypt(str(password)) def __repr__(self): return '' % self.name @@ -152,14 +155,15 @@ class Solves(db.Model): class WrongKeys(db.Model): id = db.Column(db.Integer, primary_key=True) - chal = db.Column(db.Integer, db.ForeignKey('challenges.id')) + chalid = db.Column(db.Integer, db.ForeignKey('challenges.id')) team = db.Column(db.Integer, db.ForeignKey('teams.id')) date = db.Column(db.DateTime, default=datetime.datetime.utcnow) flag = db.Column(db.Text) + chal = db.relationship('Challenges', foreign_keys="WrongKeys.chalid", lazy='joined') - def __init__(self, team, chal, flag): + def __init__(self, team, chalid, flag): self.team = team - self.chal = chal + self.chalid = chalid self.flag = flag def __repr__(self): diff --git a/CTFd/static/admin/css/style.css b/CTFd/static/admin/css/style.css index ee2b95fe..b9c1863a 100644 --- a/CTFd/static/admin/css/style.css +++ b/CTFd/static/admin/css/style.css @@ -69,6 +69,7 @@ table{ #score-graph{ max-height: 400px; + clear:both; } #keys-pie-graph{ @@ -81,4 +82,26 @@ table{ width: 600px; float: left; max-height: 330px; -} \ No newline at end of file +} + +.chal-button{ + width: 150px; +} + +.chal-button > p{ + font-family: monospace; + margin-bottom: 0px; + height: 20px; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.chal-button > span{ + font-size: 14px; +} + +.remove-key{ + float:right; +} diff --git a/CTFd/static/admin/js/chalboard.js b/CTFd/static/admin/js/chalboard.js index 7c74d0ce..3524b0a2 100644 --- a/CTFd/static/admin/js/chalboard.js +++ b/CTFd/static/admin/js/chalboard.js @@ -1,3 +1,14 @@ +//http://stackoverflow.com/a/2648463 - wizardry! +String.prototype.format = String.prototype.f = function() { + var s = this, + i = arguments.length; + + while (i--) { + s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]); + } + return s; +}; + //http://stackoverflow.com/a/7616484 String.prototype.hashCode = function() { var hash = 0, i, chr, len; @@ -16,8 +27,9 @@ function loadchal(id) { obj = $.grep(challenges['game'], function (e) { return e.id == id; })[0] + $('.chal-title').text(obj.name); $('.chal-name').val(obj.name); - $('.chal-desc').html(obj.description); + $('.chal-desc').val(obj.description); $('.chal-value').val(obj.value); $('.chal-category').val(obj.category); $('.chal-id').val(obj.id); @@ -44,9 +56,13 @@ function loadkeys(chal){ keys = keys['keys']; $('#current-keys').empty(); for(x=0; x").val(keys[x].key)); - $('#current-keys').append('Static'); - $('#current-keys').append('Regex'); + var elem = $('
'); + elem.append($("").val(keys[x].key)); + elem.append('Static'); + elem.append('Regex'); + elem.append('Remove'); + + $('#current-keys').append(elem); $('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true); } }); @@ -59,7 +75,7 @@ function updatekeys(){ $('.current-key').each(function(){ keys.push($(this).val()); }) - $('#current-keys > input[name*="key_type"]:checked').each(function(){ + $('#current-keys input[name*="key_type"]:checked').each(function(){ vals.push($(this).val()); }) $.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()}) @@ -146,13 +162,10 @@ function loadchals(){ } }; - for (var i = categories.length - 1; i >= 0; i--) { - $('#new-challenge select').append(''); - $('#update-challenge select').append(''); - }; - for (var i = 0; i <= challenges['game'].length - 1; i++) { - $('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append($('')); + var chal = challenges['game'][i] + var chal_button = $(''.format(chal.id, chal.name, chal.value)) + $('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button); }; $('#challenges button').click(function (e) { @@ -162,8 +175,6 @@ function loadchals(){ loadfiles(this.value); }); - $('tr').append(''); - $('.create-challenge').click(function (e) { $('#new-chal-category').val($($(this).siblings()[0]).text().trim()) $('#new-chal-title').text($($(this).siblings()[0]).text().trim()) @@ -177,9 +188,7 @@ $('#submit-key').click(function (e) { }); $('#submit-keys').click(function (e) { - if (confirm('Updating keys. Are you sure?')){ - updatekeys() - } + updatekeys() }); $('#submit-tags').click(function (e) { @@ -212,15 +221,38 @@ $(".tag-insert").keyup(function (e) { } }); -$('.create-category').click(function (e) { - $('#new-category').foundation('reveal', 'open'); + + +// Markdown Preview +$('#desc-edit').on('toggled', function (event, tab) { + if (tab[0].id == 'desc-preview'){ + $(tab[0]).html(marked($('#desc-editor').val(), {'gfm':true, 'breaks':true})) + } }); -count = 1; -$('#create-key').click(function (e) { - $('#current-keys').append(""); - $('#current-keys').append('Static'); - $('#current-keys').append('Regex'); - count++; +$('#new-desc-edit').on('toggled', function (event, tab) { + if (tab[0].id == 'new-desc-preview'){ + $(tab[0]).html(marked($('#new-desc-editor').val(), {'gfm':true, 'breaks':true})) + } +}); + +// Open New Challenge modal when New Challenge button is clicked +$('.create-challenge').click(function (e) { + $('#create-challenge').foundation('reveal', 'open'); +}); + + +$('#create-key').click(function(e){ + var amt = $('#current-keys input[type=text]').length + // $('#current-keys').append(""); + // $('#current-keys').append('Static'.format(amt)); + // $('#current-keys').append('Regex'.format(amt)); + + var elem = $('
'); + elem.append(""); + elem.append('Static'.format(amt)); + elem.append('Regex'.format(amt)); + elem.append('Remove'); + $('#current-keys').append(elem); }); $(function(){ diff --git a/CTFd/static/css/style.css b/CTFd/static/css/style.css index ad499a05..4610c42e 100644 --- a/CTFd/static/css/style.css +++ b/CTFd/static/css/style.css @@ -56,6 +56,7 @@ table{ #score-graph{ max-height: 400px; + clear:both; } .dropdown{ @@ -91,6 +92,24 @@ table{ display: block; } +.chal-button{ + width: 150px; +} + +.chal-button > p{ + font-family: monospace; + margin-bottom: 0px; + height: 20px; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.chal-button > span{ + font-size: 14px; +} + @media only screen and (min-width: 40.063em){ .top-bar .dropdown{ display: block; @@ -103,3 +122,8 @@ table{ padding: 10px; } +.scroll-wrap{ + height: 400px; + overflow-y: auto; + overflow-x: hidden; +} diff --git a/CTFd/static/js/chalboard.js b/CTFd/static/js/chalboard.js index 1e14b330..0d999474 100644 --- a/CTFd/static/js/chalboard.js +++ b/CTFd/static/js/chalboard.js @@ -206,7 +206,9 @@ function loadchals() { }; for (var i = 0; i <= challenges['game'].length - 1; i++) { - $('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append($('')); + var chal = challenges['game'][i] + var chal_button = $(''.format(chal.id, chal.name, chal.value)) + $('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button); }; updatesolves() marktoomanyattempts() diff --git a/CTFd/static/js/scoreboard.js b/CTFd/static/js/scoreboard.js index ce4bac91..8d9f9cfa 100644 --- a/CTFd/static/js/scoreboard.js +++ b/CTFd/static/js/scoreboard.js @@ -9,6 +9,16 @@ String.prototype.format = String.prototype.f = function() { return s; }; +function colorhash (x) { + color = "" + for (var i = 20; i <= 60; i+=20){ + x += i + x *= i + color += x.toString(16) + }; + return "#" + color.substring(0, 6) +} + function htmlentities(string) { return $('
').text(string).html(); } @@ -37,7 +47,6 @@ function UTCtoDate(utc){ d.setUTCSeconds(utc) return d; } - function scoregraph () { var times = [] var scores = [] @@ -52,12 +61,15 @@ function scoregraph () { xs_data = {} column_data = [] + colors = {} for (var i = 0; i < teams.length; i++) { times = [] team_scores = [] for (var x = 0; x < scores[teams[i]].length; x++) { times.push(scores[teams[i]][x].time) team_scores.push(scores[teams[i]][x].value) + console.log(scores[teams[i]]) + colors[teams[i]] = colorhash(scores[teams[i]][x].id) }; team_scores = cumulativesum(team_scores) @@ -79,7 +91,8 @@ function scoregraph () { data: { xs: xs_data, columns: column_data, - type: "step" + type: "step", + colors: colors // labels: true }, axis : { diff --git a/CTFd/static/js/team.js b/CTFd/static/js/team.js index 394a55e8..f633e674 100644 --- a/CTFd/static/js/team.js +++ b/CTFd/static/js/team.js @@ -1,6 +1,6 @@ function teamid (){ loc = window.location.pathname - return loc.substring(loc.lastIndexOf('/')+1, loc.length); + return parseInt(loc.substring(loc.lastIndexOf('/')+1, loc.length)); } function colorhash (x) { @@ -165,4 +165,4 @@ function category_breakdown_graph(){ category_breakdown_graph() keys_percentage_graph() -scoregraph() \ No newline at end of file +scoregraph() diff --git a/CTFd/templates/admin/base.html b/CTFd/templates/admin/base.html index 25d9560c..7caabaa3 100644 --- a/CTFd/templates/admin/base.html +++ b/CTFd/templates/admin/base.html @@ -46,7 +46,7 @@ {% block content %} {% endblock %} - + -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/CTFd/templates/chals.html b/CTFd/templates/chals.html index 839e4823..dbf8483f 100644 --- a/CTFd/templates/chals.html +++ b/CTFd/templates/chals.html @@ -5,18 +5,20 @@

Solved By

- - - - - - - - - -
Name - Date -
+
+ + + + + + + + + +
Name + Date +
+
×
@@ -36,8 +38,9 @@ ×
-
-

Challenges

+
+

Challenges

+
diff --git a/CTFd/templates/scoreboard.html b/CTFd/templates/scoreboard.html index 67c1f167..d5f0ee6a 100644 --- a/CTFd/templates/scoreboard.html +++ b/CTFd/templates/scoreboard.html @@ -4,6 +4,7 @@
+

Scoreboard



@@ -11,7 +12,7 @@
- @@ -33,4 +34,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/CTFd/templates/team.html b/CTFd/templates/team.html index 6492f5d1..20dd659e 100644 --- a/CTFd/templates/team.html +++ b/CTFd/templates/team.html @@ -20,7 +20,8 @@
- +
+
Place + Place Team
@@ -36,7 +37,6 @@ {% endfor %}
-
diff --git a/CTFd/templates/teams.html b/CTFd/templates/teams.html index 8bd80fd2..1b38eaa8 100644 --- a/CTFd/templates/teams.html +++ b/CTFd/templates/teams.html @@ -4,10 +4,12 @@
+

Teams


+ @@ -17,7 +19,8 @@ {% for team in teams %} - + + @@ -25,8 +28,17 @@ {% endfor %}
ID Team Website Affiliation
{{ team.name }}{{ team.id }}{{ team.name | truncate(32) }} {% if team.website and team.website.startswith('http') %}{{ team.website }}{% endif %} {% if team.affiliation %}{{ team.affiliation }}{% endif %} {% if team.country %}{{ team.country }}{% endif %}
+ {% if team_pages > 1 %} +
Page +
+ {% for page in range(1, team_pages + 1) %} + {{ page }} + {% endfor %} + +
+ {% endif %}
{% endblock %} {% block scripts %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/CTFd/utils.py b/CTFd/utils.py index 3cadc1e8..73b84fdf 100644 --- a/CTFd/utils.py +++ b/CTFd/utils.py @@ -1,7 +1,7 @@ from CTFd.models import db, WrongKeys, Pages, Config from CTFd import mail -from urlparse import urlparse, urljoin +from six.moves.urllib.parse import urlparse, urljoin from functools import wraps from flask import current_app as app, g, request, redirect, url_for, session, render_template from flask.ext.mail import Message @@ -140,7 +140,7 @@ def admins_only(f): @wraps(f) def decorated_function(*args, **kwargs): if session.get('admin', None) is None: - return redirect(url_for('login', next=request.url)) + return redirect('/login') return f(*args, **kwargs) return decorated_function @@ -159,23 +159,27 @@ def ctftime(): if start: start = int(start) + else: + start = 0 if end: end = int(end) + else: + end = 0 if start and end: if start < time.time() < end: # Within the two time bounds return True - if start < time.time() and end is None: + if start < time.time() and end == 0: # CTF starts on a date but never ends return True - if start is None and time.time() < end: + if start == 0 and time.time() < end: # CTF started but ends at a date return True - if start is None and end is None: + if start == 0 and end == 0: # CTF has no time requirements return True diff --git a/CTFd/views.py b/CTFd/views.py index 729b9b80..e8f825db 100644 --- a/CTFd/views.py +++ b/CTFd/views.py @@ -29,6 +29,8 @@ def tracker(): @views.before_request def csrf(): if request.method == "POST": + print(session) + print(request.form.get('nonce')) if session['nonce'] != request.form.get('nonce'): abort(403) @@ -62,6 +64,7 @@ def setup(): password = request.form['password'] admin = Teams(name, email, password) admin.admin = True + admin.banned = True ## Index page html = request.form['html'] @@ -96,6 +99,7 @@ def setup(): db.session.commit() app.setup = False return redirect('/') + print(session.get('nonce')) return render_template('setup.html', nonce=session.get('nonce')) return redirect('/') @@ -120,11 +124,19 @@ def static_html(template): abort(404) -@views.route('/teams') -def teams(): - teams = Teams.query.all() - return render_template('teams.html', teams=teams) +@views.route('/teams', defaults={'page':'1'}) +@views.route('/teams/') +def teams(page): + page = abs(int(page)) + results_per_page = 50 + page_start = results_per_page * ( page - 1 ) + page_end = results_per_page * ( page - 1 ) + results_per_page + teams = Teams.query.slice(page_start, page_end).all() + count = db.session.query(db.func.count(Teams.id)).first()[0] + print(count) + pages = int(count / results_per_page) + (count % results_per_page > 0) + return render_template('teams.html', teams=teams, team_pages=pages) @views.route('/team/', methods=['GET', 'POST']) def team(teamid): diff --git a/requirements.txt b/requirements.txt index 035e10ac..0b8127fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,12 @@ -APScheduler==3.0.1 -Flask==0.10.1 -Flask-Mail==0.9.1 -Flask-SQLAlchemy==2.0 -Flask-Session==0.1.1 -SQLAlchemy==0.9.8 -passlib==1.6.2 +APScheduler +Flask +Flask-Mail +Flask-SQLAlchemy +Flask-Session +SQLAlchemy +passlib bcrypt -six==1.8.0 -itsdangerous==0.24 -requests==2.3.0 +six +itsdangerous +requests +PyMySQL