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
mail = Mail(app)
Session(app)
#Session(app)
from CTFd.views import views
from CTFd.challenges import challenges

View File

@@ -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/<page>')
@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/<teamid>', 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/<teamid>', 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/<teamid>/<chalid>/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()

View File

@@ -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/<chalid>/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

View File

@@ -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"

View File

@@ -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 '<chal %r>' % 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 '<team %r>' % 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):

View File

@@ -69,6 +69,7 @@ table{
#score-graph{
max-height: 400px;
clear:both;
}
#keys-pie-graph{
@@ -82,3 +83,25 @@ table{
float: left;
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
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<keys.length; x++){
$('#current-keys').append($("<input class='current-key' type='text'>").val(keys[x].key));
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="0">Static');
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="1">Regex');
var elem = $('<div>');
elem.append($("<input class='current-key' type='text'>").val(keys[x].key));
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);
}
});
@@ -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('<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++) {
$('#' + 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) {
@@ -162,8 +175,6 @@ function loadchals(){
loadfiles(this.value);
});
$('tr').append('<button class="radius create-challenge"><i class="fa fa-plus"></i></button>');
$('.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()
}
});
$('#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) {
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){
$('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>");
$('#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++;
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(){

View File

@@ -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;
}

View File

@@ -206,7 +206,9 @@ function loadchals() {
};
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()
marktoomanyattempts()

View File

@@ -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 $('<div/>').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 : {

View File

@@ -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) {

View File

@@ -46,7 +46,7 @@
{% 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/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/foundation.topbar.min.js"></script>
<script>

View File

@@ -2,47 +2,31 @@
{% block content %}
<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>
<h3>New Challenge</h3>
<form method="POST" action="/admin/chal/new" enctype="multipart/form-data">
<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">
<input type='text' name='category' placeholder='Category'><br/>
<button type='submit'>Create</button>
<a class="close-reveal-modal">&#215;</a>
</form>
<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>
<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>
<div id="new-challenge" class="reveal-modal" data-reveal>
<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/>
<textarea class="textbox" name='desc' placeholder='Description'></textarea><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">
<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>
@@ -111,15 +95,26 @@
<div id="update-challenge" class="reveal-modal" data-reveal>
<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 class="chal-name" type='text' name='name' placeholder='Name'><br/>
<textarea class="chal-desc textbox" name='desc' placeholder='Description'></textarea><br/>
<input class="chal-value" type='number' name='value' placeholder='Value'><br/>
<select class="chal-category" name="category">
<option>-</option>
</select>
<input class="chal-id" type='hidden' name='id' placeholder='ID'><br/>
Name: <input class="chal-name" type='text' name='name' placeholder='Name'><br>
Category: <input class="chal-category" type="text" name="category" placeholder="Category"><br>
Description:
<ul id="desc-edit" class="tabs" data-tab>
<li class="tab-title active"><a href="#desc-write" style="padding: 0 2rem;">Write</a></li>
<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-files" class="secondary button">Files</a>
@@ -130,14 +125,13 @@
</form>
</div>
<div class="row">
<h1>CTF</h1>
<div>
<h1 class="text-center">Challenges</h1>
<div>
<table id='challenges'>
</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>
{% endblock %}

View File

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

View File

@@ -5,6 +5,7 @@
<div class="row">
<h1 id="team-id">{{ team.name }}</h1>
<h2 id="team-email" class="text-center">{{ team.email }}</h2>
<h2 id="team-place" class="text-center">
{%if place %}
{{ place }} <small>place</small>
@@ -19,11 +20,14 @@
<div id="keys-pie-graph"></div>
<div id="categories-pie-graph"></div>
<div id="score-graph"></div>
<table>
<h3>Solves</h3>
<thead>
<tr>
<td><b>Challenge</b></td>
<td><b>Submitted</b></td>
<td><b>Category</b></td>
<td><b>Value</b></td>
<td><b>Time</b></td>
@@ -31,12 +35,19 @@
</thead>
<tbody>
{% 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 %}
</tbody>
</table>
<table>
<h3>IP Addresses</h3>
<thead>
<tr>
<td><b>IP Address</b></td>
@@ -50,7 +61,25 @@
</tbody>
</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>

View File

@@ -76,7 +76,7 @@
<table id="teamsboard">
<thead>
<tr>
<td class="text-center"><b>ID</b>
<td width="10px" class="text-center"><b>ID</b>
</td>
<td class="text-center"><b>Team</b>
</td>
@@ -98,12 +98,12 @@
{% for team in teams %}
<tr name="{{ team.id }}">
<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 class="team-email">{{ team.email }}</td>
<td class="team-website">{% if team.website and team.website.startswith('http') %}<a href="{{ team.website }}">{{ team.website }}</a>{% endif %}
<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 | truncate(32) }}</a>{% endif %}
</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 class="team-country"><span>{% if team.country %}{{ team.country }}{% endif %}</span>
</td>
@@ -122,6 +122,15 @@
{% endfor %}
</tbody>
</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>
{% endblock %}

View File

@@ -5,6 +5,7 @@
<div id="chal-solves-window" class="reveal-modal" data-reveal>
<h3>Solved By</h3>
<div class="scroll-wrap">
<table>
<thead>
<tr>
@@ -17,6 +18,7 @@
<tbody id="chal-solves-names">
</tbody>
</table>
</div>
<a class="close-reveal-modal" data-reveal-id="chal-window">&#215;</a>
</div>
@@ -36,8 +38,9 @@
<a class="close-reveal-modal">&#215;</a>
</div>
<div class="row">
<h2 class="text-center">Challenges</h2>
<div>
<h1 class="text-center">Challenges</h1>
<br>
<div class="large-12 columns">
<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">
<div class="row">
<h1>Scoreboard</h1>
<br>
<div id="score-graph"></div>
<br>
@@ -11,7 +12,7 @@
<table id="scoreboard">
<thead>
<tr>
<td><b>Place</b>
<td width="10px"><b>Place</b>
</td>
<td><b>Team</b>
</td>

View File

@@ -20,6 +20,7 @@
<div id="keys-pie-graph"></div>
<div id="categories-pie-graph"></div>
<div id="score-graph"></div>
<table>
<thead>
@@ -36,7 +37,6 @@
{% endfor %}
</tbody>
</table>
<div id="score-graph"></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">
<div class="row">
<h1>Teams</h1>
<br>
<table id="teamsboard">
<thead>
<tr>
<td width="10px"><b>ID</b></td>
<td><b>Team</b></td>
<td><b>Website</b></td>
<td><b>Affiliation</b></td>
@@ -17,7 +19,8 @@
<tbody>
{% for team in teams %}
<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><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span></td>
<td><span>{% if team.country %}{{ team.country }}{% endif %}</span></td>
@@ -25,6 +28,15 @@
{% endfor %}
</tbody>
</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>
{% endblock %}

View File

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

View File

@@ -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/<page>')
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/<teamid>', methods=['GET', 'POST'])
def team(teamid):

View File

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