Supports PY3, refinements to chal editor and viewer, model changes to resolve issues

This commit is contained in:
Kevin Chung
2015-10-10 21:09:25 -04:00
parent 09594892f9
commit df21544f13
23 changed files with 382 additions and 194 deletions

View File

@@ -22,7 +22,7 @@ def create_app(config='CTFd.config'):
global mail global mail
mail = Mail(app) mail = Mail(app)
Session(app) #Session(app)
from CTFd.views import views from CTFd.views import views
from CTFd.challenges import challenges from CTFd.challenges import challenges

View File

@@ -147,7 +147,6 @@ def admin_css():
if request.method == 'POST': if request.method == 'POST':
css = request.form['css'] css = request.form['css']
css = set_config('css', css) css = set_config('css', css)
print css
return "1" return "1"
return "0" return "0"
@@ -198,12 +197,12 @@ def admin_chals():
if request.method == 'POST': if request.method == 'POST':
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all() chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
json = {'game':[]} json_data = {'game':[]}
for x in chals: 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() db.session.close()
return jsonify(json) return jsonify(json_data)
else: else:
return render_template('admin/chals.html') return render_template('admin/chals.html')
@@ -212,21 +211,25 @@ def admin_chals():
@admins_only @admins_only
def admin_keys(chalid): def admin_keys(chalid):
if request.method == 'GET': if request.method == 'GET':
keys = Keys.query.filter_by(chal=chalid).all() chal = Challenges.query.filter_by(id=chalid).first_or_404()
json = {'keys':[]} json_data = {'keys':[]}
for x in keys: flags = json.loads(chal.flags)
json['keys'].append({'id':x.id, 'key':x.flag, 'type':x.key_type}) for i, x in enumerate(flags):
return jsonify(json) json_data['keys'].append({'id':i, 'key':x['flag'], 'type':x['type']})
return jsonify(json_data)
elif request.method == 'POST': elif request.method == 'POST':
keys = Keys.query.filter_by(chal=chalid).all() chal = Challenges.query.filter_by(id=chalid).first()
for x in keys:
db.session.delete(x)
newkeys = request.form.getlist('keys[]') newkeys = request.form.getlist('keys[]')
newvals = request.form.getlist('vals[]') newvals = request.form.getlist('vals[]')
print(list(zip(newkeys, newvals)))
flags = []
for flag, val in zip(newkeys, newvals): for flag, val in zip(newkeys, newvals):
key = Keys(chalid, flag, val) flag_dict = {'flag':flag, 'type':int(val)}
db.session.add(key) flags.append(flag_dict)
json_data = json.dumps(flags)
chal.flags = json_data
db.session.commit() db.session.commit()
db.session.close() db.session.close()
@@ -238,10 +241,10 @@ def admin_keys(chalid):
def admin_tags(chalid): def admin_tags(chalid):
if request.method == 'GET': if request.method == 'GET':
tags = Tags.query.filter_by(chal=chalid).all() tags = Tags.query.filter_by(chal=chalid).all()
json = {'tags':[]} json_data = {'tags':[]}
for x in tags: for x in tags:
json['tags'].append({'id':x.id, 'chal':x.chal, 'tag':x.tag}) json_data['tags'].append({'id':x.id, 'chal':x.chal, 'tag':x.tag})
return jsonify(json) return jsonify(json_data)
elif request.method == 'POST': elif request.method == 'POST':
newtags = request.form.getlist('tags[]') newtags = request.form.getlist('tags[]')
@@ -269,10 +272,10 @@ def admin_delete_tags(tagid):
def admin_files(chalid): def admin_files(chalid):
if request.method == 'GET': if request.method == 'GET':
files = Files.query.filter_by(chal=chalid).all() files = Files.query.filter_by(chal=chalid).all()
json = {'files':[]} json_data = {'files':[]}
for x in files: for x in files:
json['files'].append({'id':x.id, 'file':x.location}) json_data['files'].append({'id':x.id, 'file':x.location})
return jsonify(json) return jsonify(json_data)
if request.method == 'POST': if request.method == 'POST':
if request.form['method'] == "delete": if request.form['method'] == "delete":
f = Files.query.filter_by(id=request.form['file']).first_or_404() f = Files.query.filter_by(id=request.form['file']).first_or_404()
@@ -305,11 +308,20 @@ def admin_files(chalid):
return redirect('/admin/chals') return redirect('/admin/chals')
@admin.route('/admin/teams') @admin.route('/admin/teams', defaults={'page':'1'})
@admin.route('/admin/teams/<page>')
@admins_only @admins_only
def admin_teams(): def admin_teams(page):
teams = Teams.query.all() page = abs(int(page))
return render_template('admin/teams.html', teams=teams) 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/<teamid>', methods=['GET', 'POST']) @admin.route('/admin/team/<teamid>', methods=['GET', 'POST'])
@@ -317,17 +329,19 @@ def admin_teams():
def admin_team(teamid): def admin_team(teamid):
user = Teams.query.filter_by(id=teamid).first() user = Teams.query.filter_by(id=teamid).first()
solves = Solves.query.filter_by(teamid=teamid).all() 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() score = user.score()
place = user.place() place = user.place()
if request.method == 'GET': 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': elif request.method == 'POST':
admin_user = request.form.get('admin', "false") admin_user = request.form.get('admin', "false")
admin_user = 1 if admin_user == "true" else 0 admin_user = 1 if admin_user == "true" else 0
if admin: if admin:
user.admin = 1 user.admin = 1
user.banned = 1
db.session.commit() db.session.commit()
return jsonify({'data': ['success']}) return jsonify({'data': ['success']})
@@ -407,16 +421,16 @@ def delete_team(teamid):
def admin_graph(graph_type): def admin_graph(graph_type):
if graph_type == 'categories': if graph_type == 'categories':
categories = db.session.query(Challenges.category, db.func.count(Challenges.category)).group_by(Challenges.category).all() 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: for category, count in categories:
json['categories'].append({'category':category, 'count':count}) json_data['categories'].append({'category':category, 'count':count})
return jsonify(json) return jsonify(json_data)
elif graph_type == "solves": elif graph_type == "solves":
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all() solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
json = {} json_data = {}
for chal, count in solves: for chal, count in solves:
json[chal.chal.name] = count json_data[chal.chal.name] = count
return jsonify(json) return jsonify(json_data)
@admin.route('/admin/scoreboard') @admin.route('/admin/scoreboard')
@@ -436,10 +450,10 @@ def admin_scores():
quickest = db.func.max(Solves.date).label('quickest') 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) 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() db.session.close()
json = {'teams':[]} json_data = {'teams':[]}
for i, x in enumerate(teams): for i, x in enumerate(teams):
json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)}) json_data['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)})
return jsonify(json) return jsonify(json_data)
@admin.route('/admin/solves/<teamid>', methods=['GET']) @admin.route('/admin/solves/<teamid>', methods=['GET'])
@@ -450,10 +464,10 @@ def admin_solves(teamid="all"):
else: else:
solves = Solves.query.filter_by(teamid=teamid).all() solves = Solves.query.filter_by(teamid=teamid).all()
db.session.close() db.session.close()
json = {'solves':[]} json_data = {'solves':[]}
for x in 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)}) 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) return jsonify(json_data)
@admin.route('/admin/solves/<teamid>/<chalid>/delete', methods=['POST']) @admin.route('/admin/solves/<teamid>/<chalid>/delete', methods=['POST'])
@@ -531,14 +545,14 @@ def admin_fails(teamid='all'):
fails = WrongKeys.query.count() fails = WrongKeys.query.count()
solves = Solves.query.count() solves = Solves.query.count()
db.session.close() db.session.close()
json = {'fails':str(fails), 'solves': str(solves)} json_data = {'fails':str(fails), 'solves': str(solves)}
return jsonify(json) return jsonify(json_data)
else: else:
fails = WrongKeys.query.filter_by(team=teamid).count() fails = WrongKeys.query.filter_by(team=teamid).count()
solves = Solves.query.filter_by(teamid=teamid).count() solves = Solves.query.filter_by(teamid=teamid).count()
db.session.close() db.session.close()
json = {'fails':str(fails), 'solves': str(solves)} json_data = {'fails':str(fails), 'solves': str(solves)}
return jsonify(json) return jsonify(json_data)
@admin.route('/admin/chal/new', methods=['POST']) @admin.route('/admin/chal/new', methods=['POST'])
@@ -546,14 +560,12 @@ def admin_fails(teamid='all'):
def admin_create_chal(): def admin_create_chal():
files = request.files.getlist('files[]') files = request.files.getlist('files[]')
# Create challenge ## TODO: Expand to support multiple flags
chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category']) flags = [{'flag':request.form['key'], 'type':int(request.form['key_type[0]'])}]
db.session.add(chal)
db.session.commit()
# Add keys # Create challenge
key = Keys(chal.id, request.form['key'], request.form['key_type[0]']) chal = Challenges(request.form['name'], request.form['desc'], request.form['value'], request.form['category'], flags)
db.session.add(key) db.session.add(chal)
db.session.commit() db.session.commit()
for f in files: for f in files:
@@ -562,7 +574,7 @@ def admin_create_chal():
if len(filename) <= 0: if len(filename) <= 0:
continue 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)): 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)) 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(): def admin_delete_chal():
challenge = Challenges.query.filter_by(id=request.form['id']).first() challenge = Challenges.query.filter_by(id=request.form['id']).first()
if challenge: 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() Solves.query.filter_by(chalid=challenge.id).delete()
Keys.query.filter_by(chal=challenge.id).delete() Keys.query.filter_by(chal=challenge.id).delete()
files = Files.query.filter_by(chal=challenge.id).all() files = Files.query.filter_by(chal=challenge.id).all()

View File

@@ -6,6 +6,7 @@ from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys
import time import time
import re import re
import logging import logging
import json
challenges = Blueprint('challenges', __name__) challenges = Blueprint('challenges', __name__)
@@ -21,7 +22,7 @@ def challenges_view():
if can_view_challenges(): if can_view_challenges():
return render_template('chals.html', ctftime=ctftime()) return render_template('chals.html', ctftime=ctftime())
else: else:
return redirect(url_for('login', next="challenges")) return redirect('/login')
@challenges.route('/chals', methods=['GET']) @challenges.route('/chals', methods=['GET'])
@@ -55,7 +56,7 @@ def chals_per_solves():
for chal, count in solves: for chal, count in solves:
json[chal.chal.name] = count json[chal.chal.name] = count
return jsonify(json) return jsonify(json)
return redirect(url_for('login', next="/chals/solves")) return redirect('/login')
@challenges.route('/solves') @challenges.route('/solves')
@@ -80,7 +81,7 @@ def attempts():
chals = Challenges.query.add_columns('id').all() chals = Challenges.query.add_columns('id').all()
json = {'maxattempts':[]} json = {'maxattempts':[]}
for chal, chalid in chals: 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: if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
json['maxattempts'].append({'chalid':chalid}) json['maxattempts'].append({'chalid':chalid})
return jsonify(json) return jsonify(json)
@@ -97,7 +98,7 @@ def fails(teamid):
@challenges.route('/chal/<chalid>/solves', methods=['GET']) @challenges.route('/chal/<chalid>/solves', methods=['GET'])
def who_solved(chalid): 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':[]} json = {'teams':[]}
for solve in solves: for solve in solves:
json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date}) json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date})
@@ -109,12 +110,16 @@ def chal(chalid):
if not ctftime(): if not ctftime():
return redirect('/challenges') return redirect('/challenges')
if authed(): 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') 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'])) 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: if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
return "4" #too many tries on this challenge return "4" #too many tries on this challenge
# Anti-bruteforce / submitting keys too quickly
if get_kpm(session['id']) > 10: if get_kpm(session['id']) > 10:
wrong = WrongKeys(session['id'], chalid, request.form['key']) wrong = WrongKeys(session['id'], chalid, request.form['key'])
db.session.add(wrong) db.session.add(wrong)
@@ -122,21 +127,26 @@ def chal(chalid):
db.session.close() db.session.close()
logger.warn("[{0}] {1} submitted {2} with kpm {3} [TOO FAST]".format(*data)) logger.warn("[{0}] {1} submitted {2} with kpm {3} [TOO FAST]".format(*data))
return "3" # Submitting too fast return "3" # Submitting too fast
solves = Solves.query.filter_by(teamid=session['id'], chalid=chalid).first() solves = Solves.query.filter_by(teamid=session['id'], chalid=chalid).first()
# Challange not solved yet
if not solves: if not solves:
keys = Keys.query.filter_by(chal=chalid).all() chal = Challenges.query.filter_by(id=chalid).first()
key = request.form['key'].strip().lower() key = str(request.form['key'].strip().lower())
keys = json.loads(chal.flags)
for x in keys: for x in keys:
if x.key_type == 0: #static key if x['type'] == 0: #static key
if x.flag.strip().lower() == 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) solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key)
db.session.add(solve) db.session.add(solve)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data)) logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data))
return "1" # key was correct return "1" # key was correct
elif x.key_type == 1: #regex elif x['type'] == 1: #regex
res = re.match(str(x), key, re.IGNORECASE) res = re.match(str(x['flag']), key, re.IGNORECASE)
if res and res.group() == key: if res and res.group() == key:
solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key) solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key)
db.session.add(solve) db.session.add(solve)
@@ -151,6 +161,8 @@ def chal(chalid):
db.session.close() db.session.close()
logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data)) logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data))
return '0' # key was wrong return '0' # key was wrong
# Challenge already solved
else: else:
logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data)) logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data))
return "2" # challenge was already solved return "2" # challenge was already solved

View File

@@ -1,6 +1,7 @@
import os import os
##### SERVER SETTINGS ##### ##### SERVER SETTINGS #####
SECRET_KEY = os.urandom(64) SECRET_KEY = os.urandom(64)
#SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost/ctfd'
SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db' SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db'
SESSION_TYPE = "filesystem" SESSION_TYPE = "filesystem"
SESSION_FILE_DIR = "/tmp/flask_session" SESSION_FILE_DIR = "/tmp/flask_session"

View File

@@ -6,6 +6,7 @@ from passlib.hash import bcrypt_sha256
import datetime import datetime
import hashlib import hashlib
import json
def sha512(string): def sha512(string):
return hashlib.sha512(string).hexdigest() return hashlib.sha512(string).hexdigest()
@@ -37,13 +38,15 @@ class Challenges(db.Model):
description = db.Column(db.Text) description = db.Column(db.Text)
value = db.Column(db.Integer) value = db.Column(db.Integer)
category = db.Column(db.String(80)) 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.name = name
self.description = description self.description = description
self.value = value self.value = value
self.category = category self.category = category
self.flags = json.dumps(flags)
def __repr__(self): def __repr__(self):
return '<chal %r>' % self.name return '<chal %r>' % self.name
@@ -102,7 +105,7 @@ class Teams(db.Model):
def __init__(self, name, email, password): def __init__(self, name, email, password):
self.name = name self.name = name
self.email = email self.email = email
self.password = bcrypt_sha256.encrypt(password) self.password = bcrypt_sha256.encrypt(str(password))
def __repr__(self): def __repr__(self):
return '<team %r>' % self.name return '<team %r>' % self.name
@@ -152,14 +155,15 @@ class Solves(db.Model):
class WrongKeys(db.Model): class WrongKeys(db.Model):
id = db.Column(db.Integer, primary_key=True) 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')) team = db.Column(db.Integer, db.ForeignKey('teams.id'))
date = db.Column(db.DateTime, default=datetime.datetime.utcnow) date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
flag = db.Column(db.Text) 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.team = team
self.chal = chal self.chalid = chalid
self.flag = flag self.flag = flag
def __repr__(self): def __repr__(self):

View File

@@ -69,6 +69,7 @@ table{
#score-graph{ #score-graph{
max-height: 400px; max-height: 400px;
clear:both;
} }
#keys-pie-graph{ #keys-pie-graph{
@@ -81,4 +82,26 @@ table{
width: 600px; width: 600px;
float: left; float: left;
max-height: 330px; max-height: 330px;
} }
.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;
}

View File

@@ -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 //http://stackoverflow.com/a/7616484
String.prototype.hashCode = function() { String.prototype.hashCode = function() {
var hash = 0, i, chr, len; var hash = 0, i, chr, len;
@@ -16,8 +27,9 @@ function loadchal(id) {
obj = $.grep(challenges['game'], function (e) { obj = $.grep(challenges['game'], function (e) {
return e.id == id; return e.id == id;
})[0] })[0]
$('.chal-title').text(obj.name);
$('.chal-name').val(obj.name); $('.chal-name').val(obj.name);
$('.chal-desc').html(obj.description); $('.chal-desc').val(obj.description);
$('.chal-value').val(obj.value); $('.chal-value').val(obj.value);
$('.chal-category').val(obj.category); $('.chal-category').val(obj.category);
$('.chal-id').val(obj.id); $('.chal-id').val(obj.id);
@@ -44,9 +56,13 @@ function loadkeys(chal){
keys = keys['keys']; keys = keys['keys'];
$('#current-keys').empty(); $('#current-keys').empty();
for(x=0; x<keys.length; x++){ for(x=0; x<keys.length; x++){
$('#current-keys').append($("<input class='current-key' type='text'>").val(keys[x].key)); var elem = $('<div>');
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="0">Static'); elem.append($("<input class='current-key' type='text'>").val(keys[x].key));
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="1">Regex'); elem.append('<input type="radio" name="key_type['+x+']" value="0">Static');
elem.append('<input type="radio" name="key_type['+x+']" value="1">Regex');
elem.append('<a href="#" onclick="$(this).parent().remove()" class="remove-key">Remove</a>');
$('#current-keys').append(elem);
$('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true); $('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true);
} }
}); });
@@ -59,7 +75,7 @@ function updatekeys(){
$('.current-key').each(function(){ $('.current-key').each(function(){
keys.push($(this).val()); 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()); vals.push($(this).val());
}) })
$.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').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('<option value="' + categories[i] + '">' + categories[i] + '</option>');
$('#update-challenge select').append('<option value="' + categories[i] + '">' + categories[i] + '</option>');
};
for (var i = 0; i <= challenges['game'].length - 1; i++) { for (var i = 0; i <= challenges['game'].length - 1; i++) {
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append($('<button class="radius" value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>')); var chal = challenges['game'][i]
var chal_button = $('<button class="chal-button" value="{0}"><p>{1}</p><span>{2}</span></button>'.format(chal.id, chal.name, chal.value))
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button);
}; };
$('#challenges button').click(function (e) { $('#challenges button').click(function (e) {
@@ -162,8 +175,6 @@ function loadchals(){
loadfiles(this.value); loadfiles(this.value);
}); });
$('tr').append('<button class="radius create-challenge"><i class="fa fa-plus"></i></button>');
$('.create-challenge').click(function (e) { $('.create-challenge').click(function (e) {
$('#new-chal-category').val($($(this).siblings()[0]).text().trim()) $('#new-chal-category').val($($(this).siblings()[0]).text().trim())
$('#new-chal-title').text($($(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) { $('#submit-keys').click(function (e) {
if (confirm('Updating keys. Are you sure?')){ updatekeys()
updatekeys()
}
}); });
$('#submit-tags').click(function (e) { $('#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; $('#new-desc-edit').on('toggled', function (event, tab) {
$('#create-key').click(function (e) { if (tab[0].id == 'new-desc-preview'){
$('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>"); $(tab[0]).html(marked($('#new-desc-editor').val(), {'gfm':true, 'breaks':true}))
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="0">Static'); }
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="1">Regex'); });
count++;
// 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("<input class='current-key' type='text' placeholder='Blank Key'>");
// $('#current-keys').append('<input type="radio" name="key_type[{0}]" value="0">Static'.format(amt));
// $('#current-keys').append('<input type="radio" name="key_type[{0}]" value="1">Regex'.format(amt));
var elem = $('<div>');
elem.append("<input class='current-key' type='text' placeholder='Blank Key'>");
elem.append('<input type="radio" name="key_type[{0}]" value="0">Static'.format(amt));
elem.append('<input type="radio" name="key_type[{0}]" value="1">Regex'.format(amt));
elem.append('<a href="#" onclick="$(this).parent().remove()" class="remove-key">Remove</a>');
$('#current-keys').append(elem);
}); });
$(function(){ $(function(){

View File

@@ -56,6 +56,7 @@ table{
#score-graph{ #score-graph{
max-height: 400px; max-height: 400px;
clear:both;
} }
.dropdown{ .dropdown{
@@ -91,6 +92,24 @@ table{
display: block; 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){ @media only screen and (min-width: 40.063em){
.top-bar .dropdown{ .top-bar .dropdown{
display: block; display: block;
@@ -103,3 +122,8 @@ table{
padding: 10px; padding: 10px;
} }
.scroll-wrap{
height: 400px;
overflow-y: auto;
overflow-x: hidden;
}

View File

@@ -206,7 +206,9 @@ function loadchals() {
}; };
for (var i = 0; i <= challenges['game'].length - 1; i++) { for (var i = 0; i <= challenges['game'].length - 1; i++) {
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append($('<button value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>')); var chal = challenges['game'][i]
var chal_button = $('<button class="chal-button" value="{0}"><p>{1}</p><span>{2}</span></button>'.format(chal.id, chal.name, chal.value))
$('#' + challenges['game'][i].category.replace(/ /g,"-").hashCode()).append(chal_button);
}; };
updatesolves() updatesolves()
marktoomanyattempts() marktoomanyattempts()

View File

@@ -9,6 +9,16 @@ String.prototype.format = String.prototype.f = function() {
return s; 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) { function htmlentities(string) {
return $('<div/>').text(string).html(); return $('<div/>').text(string).html();
} }
@@ -37,7 +47,6 @@ function UTCtoDate(utc){
d.setUTCSeconds(utc) d.setUTCSeconds(utc)
return d; return d;
} }
function scoregraph () { function scoregraph () {
var times = [] var times = []
var scores = [] var scores = []
@@ -52,12 +61,15 @@ function scoregraph () {
xs_data = {} xs_data = {}
column_data = [] column_data = []
colors = {}
for (var i = 0; i < teams.length; i++) { for (var i = 0; i < teams.length; i++) {
times = [] times = []
team_scores = [] team_scores = []
for (var x = 0; x < scores[teams[i]].length; x++) { for (var x = 0; x < scores[teams[i]].length; x++) {
times.push(scores[teams[i]][x].time) times.push(scores[teams[i]][x].time)
team_scores.push(scores[teams[i]][x].value) 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) team_scores = cumulativesum(team_scores)
@@ -79,7 +91,8 @@ function scoregraph () {
data: { data: {
xs: xs_data, xs: xs_data,
columns: column_data, columns: column_data,
type: "step" type: "step",
colors: colors
// labels: true // labels: true
}, },
axis : { axis : {

View File

@@ -1,6 +1,6 @@
function teamid (){ function teamid (){
loc = window.location.pathname loc = window.location.pathname
return loc.substring(loc.lastIndexOf('/')+1, loc.length); return parseInt(loc.substring(loc.lastIndexOf('/')+1, loc.length));
} }
function colorhash (x) { function colorhash (x) {
@@ -165,4 +165,4 @@ function category_breakdown_graph(){
category_breakdown_graph() category_breakdown_graph()
keys_percentage_graph() keys_percentage_graph()
scoregraph() scoregraph()

View File

@@ -46,7 +46,7 @@
{% block content %} {% endblock %} {% block content %} {% endblock %}
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/vendor/jquery.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/vendor/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/vendor/modernizr.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/vendor/modernizr.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/pagedown/1.0/Markdown.Converter.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/foundation.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/foundation.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/foundation/foundation.topbar.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/js/foundation/foundation.topbar.min.js"></script>
<script> <script>
@@ -55,4 +55,4 @@
{% block scripts %} {% endblock %} {% block scripts %} {% endblock %}
</body> </body>
</html> </html>

View File

@@ -2,47 +2,31 @@
{% block content %} {% block content %}
<input type="hidden" value="{{ nonce }}" id="nonce"> <input type="hidden" value="{{ nonce }}" id="nonce">
<div id="new-category" class="reveal-modal" data-reveal>
<form method="POST" action="/admin/chal/new" enctype="multipart/form-data">
<h3>New Category</h3>
<input name='nonce' type='hidden' value="{{ nonce }}">
<input type='text' name='category' placeholder='Category'><br/>
<a href="#" data-reveal-id="create-challenge" class="button">Create</a>
<a class="close-reveal-modal">&#215;</a>
</div>
<div id="create-challenge" class="reveal-modal" data-reveal> <div id="create-challenge" class="reveal-modal" data-reveal>
<h3>New Challenge</h3> <h3>New Challenge</h3>
<input type='text' name='name' placeholder='Name'><br/>
<textarea name='desc' placeholder='Description'></textarea><br/>
<input type='number' name='value' placeholder='Value'><br/>
<input type='text' name='key' placeholder='Key'><br/>
<input type="radio" name="key_type[0]" value="0">Static
<input type="radio" name="key_type[0]" value="1">Regex
<br/>
<input type="file" name="files[]" multiple="multiple">
<button type='submit'>Create</button>
<a class="close-reveal-modal">&#215;</a>
</form>
</div>
<div id="new-challenge" class="reveal-modal" data-reveal>
<form method="POST" action="/admin/chal/new" enctype="multipart/form-data"> <form method="POST" action="/admin/chal/new" enctype="multipart/form-data">
<h3><span id="new-chal-title"></span> challenge</h3>
<input name='nonce' type='hidden' value="{{ nonce }}">
<input type='text' name='name' placeholder='Name'><br/> <input type='text' name='name' placeholder='Name'><br/>
<textarea class="textbox" name='desc' placeholder='Description'></textarea><br/> <input type='text' name='category' placeholder='Category'><br/>
<input type='number' name='value' placeholder='Value'><br/>
<input id="new-chal-category" type="hidden" name="category">
<input type='text' name='key' placeholder='Key'><br/>
<input type="file" name="files[]" multiple="multiple"> <ul id="new-desc-edit" class="tabs" data-tab>
<li class="tab-title active"><a href="#new-desc-write" style="padding: 0 2rem;">Write</a></li>
<br/> <li class="tab-title"><a href="#new-desc-preview" style="padding: 0 2rem;">Preview</a></li>
</ul>
<div class="tabs-content">
<div class="content active" id="new-desc-write">
<textarea id="new-desc-editor" class="new-chal-desc" name='desc' placeholder='Description' rows="10"></textarea><br>
</div>
<div class="content" id="new-desc-preview">
</div>
</div>
<input type='number' name='value' placeholder='Value'><br/>
<input type='text' name='key' placeholder='Key'><br/>
<input type="radio" name="key_type[0]" value="0">Static <input type="radio" name="key_type[0]" value="0">Static
<input type="radio" name="key_type[0]" value="1">Regex <input type="radio" name="key_type[0]" value="1">Regex
<br/> <br/>
<input type="file" name="files[]" multiple="multiple">
<button type='submit'>Create</button> <button type='submit'>Create</button>
<a class="close-reveal-modal">&#215;</a> <a class="close-reveal-modal">&#215;</a>
@@ -111,15 +95,26 @@
<div id="update-challenge" class="reveal-modal" data-reveal> <div id="update-challenge" class="reveal-modal" data-reveal>
<form method="POST" action="/admin/chal/update"> <form method="POST" action="/admin/chal/update">
<h3>Update Challenge</h3> <h3 class="chal-title text-center"></h3>
<input name='nonce' type='hidden' value="{{ nonce }}"> <input name='nonce' type='hidden' value="{{ nonce }}">
<input class="chal-name" type='text' name='name' placeholder='Name'><br/> Name: <input class="chal-name" type='text' name='name' placeholder='Name'><br>
<textarea class="chal-desc textbox" name='desc' placeholder='Description'></textarea><br/> Category: <input class="chal-category" type="text" name="category" placeholder="Category"><br>
<input class="chal-value" type='number' name='value' placeholder='Value'><br/> Description:
<select class="chal-category" name="category">
<option>-</option> <ul id="desc-edit" class="tabs" data-tab>
</select> <li class="tab-title active"><a href="#desc-write" style="padding: 0 2rem;">Write</a></li>
<input class="chal-id" type='hidden' name='id' placeholder='ID'><br/> <li class="tab-title"><a href="#desc-preview" style="padding: 0 2rem;">Preview</a></li>
</ul>
<div class="tabs-content">
<div class="content active" id="desc-write">
<textarea id="desc-editor" class="chal-desc" name='desc' placeholder='Description' rows="10"></textarea><br>
</div>
<div class="content" id="desc-preview">
</div>
</div>
Value: <input class="chal-value" type='number' name='value' placeholder='Value'><br>
<input class="chal-id" type='hidden' name='id' placeholder='ID'>
<a href="#" data-reveal-id="update-tags" class="secondary button">Tags</a> <a href="#" data-reveal-id="update-tags" class="secondary button">Tags</a>
<a href="#" data-reveal-id="update-files" class="secondary button">Files</a> <a href="#" data-reveal-id="update-files" class="secondary button">Files</a>
@@ -130,14 +125,13 @@
</form> </form>
</div> </div>
<div>
<div class="row"> <h1 class="text-center">Challenges</h1>
<h1>CTF</h1>
<div> <div>
<table id='challenges'> <table id='challenges'>
</table> </table>
<button style="width:100%;" class="radius create-category">New Category</button> <button style="width:100%;" class="radius create-challenge">New Challenge</button>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -6,7 +6,7 @@
<table id="scoreboard"> <table id="scoreboard">
<thead> <thead>
<tr> <tr>
<td><b>Place</b></td> <td width="10px"><b>Place</b></td>
<td><b>Team</b></td> <td><b>Team</b></td>
<td><b>Score</b></td> <td><b>Score</b></td>
<td><b>Status</b></td> <td><b>Status</b></td>

View File

@@ -5,6 +5,7 @@
<div class="row"> <div class="row">
<h1 id="team-id">{{ team.name }}</h1> <h1 id="team-id">{{ team.name }}</h1>
<h2 id="team-email" class="text-center">{{ team.email }}</h2>
<h2 id="team-place" class="text-center"> <h2 id="team-place" class="text-center">
{%if place %} {%if place %}
{{ place }} <small>place</small> {{ place }} <small>place</small>
@@ -19,11 +20,14 @@
<div id="keys-pie-graph"></div> <div id="keys-pie-graph"></div>
<div id="categories-pie-graph"></div> <div id="categories-pie-graph"></div>
<div id="score-graph"></div>
<table> <table>
<h3>Solves</h3>
<thead> <thead>
<tr> <tr>
<td><b>Challenge</b></td> <td><b>Challenge</b></td>
<td><b>Submitted</b></td>
<td><b>Category</b></td> <td><b>Category</b></td>
<td><b>Value</b></td> <td><b>Value</b></td>
<td><b>Time</b></td> <td><b>Time</b></td>
@@ -31,12 +35,19 @@
</thead> </thead>
<tbody> <tbody>
{% for solve in solves %} {% for solve in solves %}
<tr><td>{{ solve.chal.name }}</td><td>{{ solve.chal.category }}</td><td>{{ solve.chal.value }}</td><td class="solve-time">{{ solve.date|unix_time_millis }}</td></tr> <tr>
<td>{{ solve.chal.name }}</td>
<td>{{ solve.flag }}</td>
<td>{{ solve.chal.category }}</td>
<td>{{ solve.chal.value }}</td>
<td class="solve-time">{{ solve.date|unix_time_millis }}</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<table> <table>
<h3>IP Addresses</h3>
<thead> <thead>
<tr> <tr>
<td><b>IP Address</b></td> <td><b>IP Address</b></td>
@@ -50,7 +61,25 @@
</tbody> </tbody>
</table> </table>
<div id="score-graph"></div> <table>
<h3>Wrong Keys</h3>
<thead>
<tr>
<td><b>Challenge</b></td>
<td><b>Submitted</b></td>
<td><b>Time</b></td>
</tr>
</thead>
<tbody>
{% for wrong_key in wrong_keys %}
<tr>
<td>{{ wrong_key.chal.name }}</td>
<td>{{ wrong_key.flag }}</td>
<td class="solve-time">{{ wrong_key.date|unix_time_millis }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>

View File

@@ -76,7 +76,7 @@
<table id="teamsboard"> <table id="teamsboard">
<thead> <thead>
<tr> <tr>
<td class="text-center"><b>ID</b> <td width="10px" class="text-center"><b>ID</b>
</td> </td>
<td class="text-center"><b>Team</b> <td class="text-center"><b>Team</b>
</td> </td>
@@ -98,12 +98,12 @@
{% for team in teams %} {% for team in teams %}
<tr name="{{ team.id }}"> <tr name="{{ team.id }}">
<td class="team-id">{{ team.id }}</td> <td class="team-id">{{ team.id }}</td>
<td class="team-name"><a href="/admin/team/{{ team.id }}">{{ team.name }}</a> <td class="team-name"><a href="/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
</td> </td>
<td class="team-email">{{ team.email }}</td> <td class="team-email">{{ team.email | truncate(32) }}</td>
<td class="team-website">{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %} <td class="team-website">{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website | truncate(32) }}</a>{% endif %}
</td> </td>
<td class="team-affiliation"><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span> <td class="team-affiliation"><span>{% if team.affiliation %}{{ team.affiliation | truncate(20) }}{% endif %}</span>
</td> </td>
<td class="team-country"><span>{% if team.country %}{{ team.country }}{% endif %}</span> <td class="team-country"><span>{% if team.country %}{{ team.country }}{% endif %}</span>
</td> </td>
@@ -122,6 +122,15 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if pages > 1 %}
<div class="text-center">Page
<br>
{% for page in range(1, pages + 1) %}
<a href="/admin/teams/{{ page }}">{{ page }}</a>
{% endfor %}
<a href="">
</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}
@@ -248,4 +257,4 @@ $('.fa-envelope').click(function(){
load_email_modal(id); load_email_modal(id);
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -5,18 +5,20 @@
<div id="chal-solves-window" class="reveal-modal" data-reveal> <div id="chal-solves-window" class="reveal-modal" data-reveal>
<h3>Solved By</h3> <h3>Solved By</h3>
<table> <div class="scroll-wrap">
<thead> <table>
<tr> <thead>
<td><b>Name</b> <tr>
</td> <td><b>Name</b>
<td><b>Date</b> </td>
</td> <td><b>Date</b>
</tr> </td>
</thead> </tr>
<tbody id="chal-solves-names"> </thead>
</tbody> <tbody id="chal-solves-names">
</table> </tbody>
</table>
</div>
<a class="close-reveal-modal" data-reveal-id="chal-window">&#215;</a> <a class="close-reveal-modal" data-reveal-id="chal-window">&#215;</a>
</div> </div>
@@ -36,8 +38,9 @@
<a class="close-reveal-modal">&#215;</a> <a class="close-reveal-modal">&#215;</a>
</div> </div>
<div class="row"> <div>
<h2 class="text-center">Challenges</h2> <h1 class="text-center">Challenges</h1>
<br>
<div class="large-12 columns"> <div class="large-12 columns">
<table id='challenges'> <table id='challenges'>

View File

@@ -4,6 +4,7 @@
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css"> <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
<div class="row"> <div class="row">
<h1>Scoreboard</h1>
<br> <br>
<div id="score-graph"></div> <div id="score-graph"></div>
<br> <br>
@@ -11,7 +12,7 @@
<table id="scoreboard"> <table id="scoreboard">
<thead> <thead>
<tr> <tr>
<td><b>Place</b> <td width="10px"><b>Place</b>
</td> </td>
<td><b>Team</b> <td><b>Team</b>
</td> </td>
@@ -33,4 +34,4 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.13/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.js"></script>
<script src="/static/js/scoreboard.js"></script> <script src="/static/js/scoreboard.js"></script>
{% endblock %} {% endblock %}

View File

@@ -20,7 +20,8 @@
<div id="keys-pie-graph"></div> <div id="keys-pie-graph"></div>
<div id="categories-pie-graph"></div> <div id="categories-pie-graph"></div>
<div id="score-graph"></div>
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -36,7 +37,6 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div id="score-graph"></div>
</div> </div>

View File

@@ -4,10 +4,12 @@
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css"> <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
<div class="row"> <div class="row">
<h1>Teams</h1>
<br> <br>
<table id="teamsboard"> <table id="teamsboard">
<thead> <thead>
<tr> <tr>
<td width="10px"><b>ID</b></td>
<td><b>Team</b></td> <td><b>Team</b></td>
<td><b>Website</b></td> <td><b>Website</b></td>
<td><b>Affiliation</b></td> <td><b>Affiliation</b></td>
@@ -17,7 +19,8 @@
<tbody> <tbody>
{% for team in teams %} {% for team in teams %}
<tr> <tr>
<td><a href="/team/{{ team.id }}">{{ team.name }}</a></td> <td class="team-id">{{ team.id }}</td>
<td><a href="/team/{{ team.id }}">{{ team.name | truncate(32) }}</a></td>
<td>{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %}</td> <td>{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %}</td>
<td><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span></td> <td><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span></td>
<td><span>{% if team.country %}{{ team.country }}{% endif %}</span></td> <td><span>{% if team.country %}{{ team.country }}{% endif %}</span></td>
@@ -25,8 +28,17 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if team_pages > 1 %}
<div class="text-center">Page
<br>
{% for page in range(1, team_pages + 1) %}
<a href="/teams/{{ page }}">{{ page }}</a>
{% endfor %}
<a href="">
</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{% endblock %} {% endblock %}

View File

@@ -1,7 +1,7 @@
from CTFd.models import db, WrongKeys, Pages, Config from CTFd.models import db, WrongKeys, Pages, Config
from CTFd import mail from CTFd import mail
from urlparse import urlparse, urljoin from six.moves.urllib.parse import urlparse, urljoin
from functools import wraps from functools import wraps
from flask import current_app as app, g, request, redirect, url_for, session, render_template from flask import current_app as app, g, request, redirect, url_for, session, render_template
from flask.ext.mail import Message from flask.ext.mail import Message
@@ -140,7 +140,7 @@ def admins_only(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if session.get('admin', None) is None: if session.get('admin', None) is None:
return redirect(url_for('login', next=request.url)) return redirect('/login')
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated_function return decorated_function
@@ -159,23 +159,27 @@ def ctftime():
if start: if start:
start = int(start) start = int(start)
else:
start = 0
if end: if end:
end = int(end) end = int(end)
else:
end = 0
if start and end: if start and end:
if start < time.time() < end: if start < time.time() < end:
# Within the two time bounds # Within the two time bounds
return True 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 # CTF starts on a date but never ends
return True return True
if start is None and time.time() < end: if start == 0 and time.time() < end:
# CTF started but ends at a date # CTF started but ends at a date
return True return True
if start is None and end is None: if start == 0 and end == 0:
# CTF has no time requirements # CTF has no time requirements
return True return True

View File

@@ -29,6 +29,8 @@ def tracker():
@views.before_request @views.before_request
def csrf(): def csrf():
if request.method == "POST": if request.method == "POST":
print(session)
print(request.form.get('nonce'))
if session['nonce'] != request.form.get('nonce'): if session['nonce'] != request.form.get('nonce'):
abort(403) abort(403)
@@ -62,6 +64,7 @@ def setup():
password = request.form['password'] password = request.form['password']
admin = Teams(name, email, password) admin = Teams(name, email, password)
admin.admin = True admin.admin = True
admin.banned = True
## Index page ## Index page
html = request.form['html'] html = request.form['html']
@@ -96,6 +99,7 @@ def setup():
db.session.commit() db.session.commit()
app.setup = False app.setup = False
return redirect('/') return redirect('/')
print(session.get('nonce'))
return render_template('setup.html', nonce=session.get('nonce')) return render_template('setup.html', nonce=session.get('nonce'))
return redirect('/') return redirect('/')
@@ -120,11 +124,19 @@ def static_html(template):
abort(404) abort(404)
@views.route('/teams') @views.route('/teams', defaults={'page':'1'})
def teams(): @views.route('/teams/<page>')
teams = Teams.query.all() def teams(page):
return render_template('teams.html', teams=teams) 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/<teamid>', methods=['GET', 'POST']) @views.route('/team/<teamid>', methods=['GET', 'POST'])
def team(teamid): def team(teamid):

View File

@@ -1,11 +1,12 @@
APScheduler==3.0.1 APScheduler
Flask==0.10.1 Flask
Flask-Mail==0.9.1 Flask-Mail
Flask-SQLAlchemy==2.0 Flask-SQLAlchemy
Flask-Session==0.1.1 Flask-Session
SQLAlchemy==0.9.8 SQLAlchemy
passlib==1.6.2 passlib
bcrypt bcrypt
six==1.8.0 six
itsdangerous==0.24 itsdangerous
requests==2.3.0 requests
PyMySQL