mirror of https://github.com/JohnHammond/CTFd.git
CTFd code push
parent
20183dd3c9
commit
376c90189b
|
@ -52,3 +52,6 @@ docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
|
|
||||||
|
*.db
|
||||||
|
*.log
|
|
@ -0,0 +1,76 @@
|
||||||
|
from flask import Flask, render_template, request, redirect, abort, session, jsonify, json as json_mod, url_for
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
from flask.ext.mail import Mail, Message
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from flask.ext.session import Session
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
def create_app(subdomain, username="", password=""):
|
||||||
|
app = Flask("CTFd", static_folder="../static", template_folder="../templates")
|
||||||
|
with app.app_context():
|
||||||
|
app.config.from_object('CTFd.config')
|
||||||
|
|
||||||
|
if subdomain:
|
||||||
|
app.config.update(
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'mysql://'+username+':'+password+'@localhost:3306/' + subdomain + '_ctfd',
|
||||||
|
HOST = subdomain + app.config["HOST"],
|
||||||
|
SESSION_FILE_DIR = app.config['SESSION_FILE_DIR'] + "/" + subdomain,
|
||||||
|
DEBUG = True
|
||||||
|
)
|
||||||
|
|
||||||
|
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
app.db = db
|
||||||
|
# app.setup = True
|
||||||
|
|
||||||
|
mail = Mail(app)
|
||||||
|
|
||||||
|
Session(app)
|
||||||
|
|
||||||
|
from CTFd.views import init_views
|
||||||
|
init_views(app)
|
||||||
|
from CTFd.errors import init_errors
|
||||||
|
init_errors(app)
|
||||||
|
from CTFd.challenges import init_challenges
|
||||||
|
init_challenges(app)
|
||||||
|
from CTFd.scoreboard import init_scoreboard
|
||||||
|
init_scoreboard(app)
|
||||||
|
from CTFd.auth import init_auth
|
||||||
|
init_auth(app)
|
||||||
|
from CTFd.admin import init_admin
|
||||||
|
init_admin(app)
|
||||||
|
from CTFd.utils import init_utils
|
||||||
|
init_utils(app)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
# logger_keys = logging.getLogger('keys')
|
||||||
|
# logger_logins = logging.getLogger('logins')
|
||||||
|
# logger_regs = logging.getLogger('regs')
|
||||||
|
|
||||||
|
# logger_keys.setLevel(logging.INFO)
|
||||||
|
# logger_logins.setLevel(logging.INFO)
|
||||||
|
# logger_regs.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# parent = os.path.dirname(__file__)
|
||||||
|
# except:
|
||||||
|
# parent = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||||
|
|
||||||
|
# key_log = RotatingFileHandler(os.path.join(parent, 'logs', 'keys.log'), maxBytes=10000)
|
||||||
|
# login_log = RotatingFileHandler(os.path.join(parent, 'logs', 'logins.log'), maxBytes=10000)
|
||||||
|
# register_log = RotatingFileHandler(os.path.join(parent, 'logs', 'registers.log'), maxBytes=10000)
|
||||||
|
|
||||||
|
# logger_keys.addHandler(key_log)
|
||||||
|
# logger_logins.addHandler(login_log)
|
||||||
|
# logger_regs.addHandler(register_log)
|
||||||
|
|
||||||
|
# logger_keys.propagate = 0
|
||||||
|
# logger_logins.propagate = 0
|
||||||
|
# logger_regs.propagate = 0
|
|
@ -0,0 +1,397 @@
|
||||||
|
from CTFd import render_template, request, redirect, abort, jsonify, url_for, session
|
||||||
|
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis
|
||||||
|
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
||||||
|
from itsdangerous import TimedSerializer, BadTimeSignature
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from socket import inet_aton, inet_ntoa
|
||||||
|
from passlib.hash import bcrypt_sha256
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
def init_admin(app):
|
||||||
|
@app.route('/admin', methods=['GET', 'POST'])
|
||||||
|
def admin():
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form.get('name')
|
||||||
|
password = request.form.get('password')
|
||||||
|
|
||||||
|
admin = Teams.query.filter_by(name=request.form['name'], admin=True).first()
|
||||||
|
if admin and bcrypt_sha256.verify(request.form['password'], admin.password):
|
||||||
|
session.regenerate() # NO SESSION FIXATION FOR YOU
|
||||||
|
session['username'] = admin.name
|
||||||
|
session['id'] = admin.id
|
||||||
|
session['admin'] = True
|
||||||
|
session['nonce'] = sha512(os.urandom(10))
|
||||||
|
db.session.close()
|
||||||
|
return redirect('/admin/graphs')
|
||||||
|
|
||||||
|
if is_admin():
|
||||||
|
return redirect('/admin/graphs')
|
||||||
|
|
||||||
|
return render_template('admin/login.html')
|
||||||
|
|
||||||
|
@app.route('/admin/graphs')
|
||||||
|
@admins_only
|
||||||
|
def admin_graphs():
|
||||||
|
return render_template('admin/graphs.html')
|
||||||
|
|
||||||
|
@app.route('/admin/config', methods=['GET', 'POST'])
|
||||||
|
@admins_only
|
||||||
|
def admin_config():
|
||||||
|
if request.method == "POST":
|
||||||
|
start = request.form['start']
|
||||||
|
end = request.form['end']
|
||||||
|
|
||||||
|
if not start:
|
||||||
|
start = None
|
||||||
|
else:
|
||||||
|
start = int(start)
|
||||||
|
if not end:
|
||||||
|
end = None
|
||||||
|
else:
|
||||||
|
end = int(end)
|
||||||
|
|
||||||
|
print repr(start), repr(end)
|
||||||
|
|
||||||
|
db_start = Config.query.filter_by(key='start').first()
|
||||||
|
db_start.value = start
|
||||||
|
|
||||||
|
db_end = Config.query.filter_by(key='end').first()
|
||||||
|
db_end.value = end
|
||||||
|
|
||||||
|
db.session.add(db_start)
|
||||||
|
db.session.add(db_end)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
return redirect('/admin/config')
|
||||||
|
start = Config.query.filter_by(key="start").first().value
|
||||||
|
end = Config.query.filter_by(key="end").first().value
|
||||||
|
return render_template('admin/config.html', start=start, end=end)
|
||||||
|
|
||||||
|
@app.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST'])
|
||||||
|
@app.route('/admin/pages/<route>', methods=['GET', 'POST'])
|
||||||
|
@admins_only
|
||||||
|
def admin_pages(route):
|
||||||
|
if route and request.method == 'GET':
|
||||||
|
page = Pages.query.filter_by(route=route).first()
|
||||||
|
return render_template('admin/editor.html', page=page)
|
||||||
|
if route and request.method == 'POST':
|
||||||
|
page = Pages.query.filter_by(route=route).first()
|
||||||
|
errors = []
|
||||||
|
html = request.form['html']
|
||||||
|
route = request.form['route']
|
||||||
|
if not route:
|
||||||
|
errors.append('Missing URL route')
|
||||||
|
if errors:
|
||||||
|
page = Pages(html, "")
|
||||||
|
return render_template('/admin/editor.html', page=page)
|
||||||
|
if page:
|
||||||
|
page.route = route
|
||||||
|
page.html = html
|
||||||
|
db.session.commit()
|
||||||
|
return redirect('/admin/pages')
|
||||||
|
page = Pages(route, html)
|
||||||
|
db.session.add(page)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect('/admin/pages')
|
||||||
|
if not route and request.method == 'POST':
|
||||||
|
return render_template('admin/editor.html')
|
||||||
|
pages = Pages.query.all()
|
||||||
|
return render_template('admin/pages.html', routes=pages)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/chals', methods=['POST', 'GET'])
|
||||||
|
@admins_only
|
||||||
|
def admin_chals():
|
||||||
|
# if authed():
|
||||||
|
if request.method == 'POST':
|
||||||
|
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
|
||||||
|
|
||||||
|
json = {'game':[]}
|
||||||
|
for x in chals:
|
||||||
|
json['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5]})
|
||||||
|
|
||||||
|
db.session.close()
|
||||||
|
return jsonify(json)
|
||||||
|
else:
|
||||||
|
return render_template('admin/chals.html')
|
||||||
|
|
||||||
|
@app.route('/admin/keys/<chalid>', methods=['POST', 'GET'])
|
||||||
|
@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)
|
||||||
|
elif request.method == 'POST':
|
||||||
|
keys = Keys.query.filter_by(chal=chalid).all()
|
||||||
|
for x in keys:
|
||||||
|
db.session.delete(x)
|
||||||
|
|
||||||
|
newkeys = request.form.getlist('keys[]')
|
||||||
|
newvals = request.form.getlist('vals[]')
|
||||||
|
for flag, val in zip(newkeys, newvals):
|
||||||
|
key = Keys(chalid, flag, val)
|
||||||
|
db.session.add(key)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return '1'
|
||||||
|
|
||||||
|
@app.route('/admin/tags/<chalid>', methods=['GET', 'POST'])
|
||||||
|
@admins_only
|
||||||
|
def admin_tags(chalid):
|
||||||
|
if request.method == 'GET':
|
||||||
|
tags = Tags.query.filter_by(chal=chalid).all()
|
||||||
|
json = {'tags':[]}
|
||||||
|
for x in tags:
|
||||||
|
json['tags'].append({'id':x.id, 'chal':x.chal, 'tag':x.tag})
|
||||||
|
return jsonify(json)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
newtags = request.form.getlist('tags[]')
|
||||||
|
for x in newtags:
|
||||||
|
tag = Tags(chalid, x)
|
||||||
|
db.session.add(tag)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return '1'
|
||||||
|
|
||||||
|
@app.route('/admin/tags/<tagid>/delete', methods=['POST'])
|
||||||
|
@admins_only
|
||||||
|
def admin_delete_tags(tagid):
|
||||||
|
if request.method == 'POST':
|
||||||
|
tag = Tags.query.filter_by(id=tagid).first_or_404()
|
||||||
|
db.session.delete(tag)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return "1"
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/files/<chalid>', methods=['GET', 'POST'])
|
||||||
|
@admins_only
|
||||||
|
def admin_files(chalid):
|
||||||
|
if request.method == 'GET':
|
||||||
|
files = Files.query.filter_by(chal=chalid).all()
|
||||||
|
json = {'files':[]}
|
||||||
|
for x in files:
|
||||||
|
json['files'].append({'id':x.id, 'file':x.location})
|
||||||
|
return jsonify(json)
|
||||||
|
if request.method == 'POST':
|
||||||
|
if request.form['method'] == "delete":
|
||||||
|
f = Files.query.filter_by(id=request.form['file']).first_or_404()
|
||||||
|
if os.path.isfile(f.location):
|
||||||
|
os.unlink(f.location)
|
||||||
|
db.session.delete(f)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return "1"
|
||||||
|
elif request.form['method'] == "upload":
|
||||||
|
files = request.files.getlist('files[]')
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
filename = secure_filename(f.filename)
|
||||||
|
|
||||||
|
if len(filename) <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
md5hash = hashlib.md5(os.urandom(64)).hexdigest()
|
||||||
|
|
||||||
|
# BUG NEEDS TO GO TO S3
|
||||||
|
if not os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], md5hash)):
|
||||||
|
os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], md5hash))
|
||||||
|
|
||||||
|
f.save(os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename))
|
||||||
|
db_f = Files(chalid, os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename))
|
||||||
|
db.session.add(db_f)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return redirect('/admin/chals')
|
||||||
|
|
||||||
|
@app.route('/admin/teams')
|
||||||
|
@admins_only
|
||||||
|
def admin_teams():
|
||||||
|
teams = Teams.query.all()
|
||||||
|
return render_template('admin/teams.html', teams=teams)
|
||||||
|
|
||||||
|
@app.route('/admin/team/<teamid>', methods=['GET', 'POST'])
|
||||||
|
@admins_only
|
||||||
|
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()
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs)
|
||||||
|
elif request.method == 'POST':
|
||||||
|
json = {'solves':[]}
|
||||||
|
for x in solves:
|
||||||
|
json['solves'].append({'id':x.id, 'chal':x.chalid, 'team':x.teamid})
|
||||||
|
return jsonify(json)
|
||||||
|
|
||||||
|
@app.route('/admin/team/<teamid>/ban', methods=['POST'])
|
||||||
|
@admins_only
|
||||||
|
def ban(teamid):
|
||||||
|
user = Teams.query.filter_by(id=teamid).first()
|
||||||
|
user.banned = 1;
|
||||||
|
db.session.commit()
|
||||||
|
return redirect('/scoreboard')
|
||||||
|
|
||||||
|
@app.route('/admin/team/<teamid>/unban', methods=['POST'])
|
||||||
|
@admins_only
|
||||||
|
def unban(teamid):
|
||||||
|
user = Teams.query.filter_by(id=teamid).first()
|
||||||
|
user.banned = None;
|
||||||
|
db.session.commit()
|
||||||
|
return redirect('/scoreboard')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/graphs/<graph_type>')
|
||||||
|
@admins_only
|
||||||
|
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':[]}
|
||||||
|
for category, count in categories:
|
||||||
|
json['categories'].append({'category':category, 'count':count})
|
||||||
|
return jsonify(json)
|
||||||
|
elif graph_type == "solves":
|
||||||
|
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
|
||||||
|
json = {}
|
||||||
|
for chal, count in solves:
|
||||||
|
json[chal.chal.name] = count
|
||||||
|
return jsonify(json)
|
||||||
|
|
||||||
|
@app.route('/admin/scoreboard')
|
||||||
|
@admins_only
|
||||||
|
def admin_scoreboard():
|
||||||
|
score = db.func.sum(Challenges.value).label('score')
|
||||||
|
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()
|
||||||
|
return render_template('admin/scoreboard.html', teams=teams)
|
||||||
|
|
||||||
|
@app.route('/admin/scores')
|
||||||
|
@admins_only
|
||||||
|
def admin_scores():
|
||||||
|
score = db.func.sum(Challenges.value).label('score')
|
||||||
|
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':[]}
|
||||||
|
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)
|
||||||
|
|
||||||
|
@app.route('/admin/solves/<teamid>', methods=['GET'])
|
||||||
|
@admins_only
|
||||||
|
def admin_solves(teamid="all"):
|
||||||
|
if teamid == "all":
|
||||||
|
solves = Solves.query.all()
|
||||||
|
else:
|
||||||
|
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||||
|
db.session.close()
|
||||||
|
json = {'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)
|
||||||
|
|
||||||
|
@app.route('/admin/statistics', methods=['GET'])
|
||||||
|
@admins_only
|
||||||
|
def admin_stats():
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
teams_registered = db.session.query(db.func.count(Teams.id)).first()[0]
|
||||||
|
site_hits = db.session.query(db.func.count(Tracking.id)).first()[0]
|
||||||
|
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
|
||||||
|
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
|
||||||
|
challenge_count = db.session.query(db.func.count(Challenges.id)).first()[0]
|
||||||
|
most_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves DESC').first()
|
||||||
|
least_solved_chal = Solves.query.add_columns(db.func.count(Solves.chalid).label('solves')).group_by(Solves.chalid).order_by('solves ASC').first()
|
||||||
|
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
|
return render_template('admin/statistics.html', team_count=teams_registered,
|
||||||
|
hit_count=site_hits,
|
||||||
|
wrong_count=wrong_count,
|
||||||
|
solve_count=solve_count,
|
||||||
|
challenge_count=challenge_count,
|
||||||
|
most_solved=most_solved_chal,
|
||||||
|
least_solved = least_solved_chal
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/fails/<teamid>', methods=['GET'])
|
||||||
|
@admins_only
|
||||||
|
def admin_fails(teamid='all'):
|
||||||
|
if teamid == "all":
|
||||||
|
fails = WrongKeys.query.count()
|
||||||
|
solves = Solves.query.count()
|
||||||
|
db.session.close()
|
||||||
|
json = {'fails':str(fails), 'solves': str(solves)}
|
||||||
|
return jsonify(json)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/chal/new', methods=['POST'])
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Add keys
|
||||||
|
key = Keys(chal.id, request.form['key'], request.form['key_type[0]'])
|
||||||
|
db.session.add(key)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
filename = secure_filename(f.filename)
|
||||||
|
|
||||||
|
if len(filename) <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
md5hash = hashlib.md5(filename).hexdigest()
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], md5hash)):
|
||||||
|
os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], md5hash))
|
||||||
|
|
||||||
|
f.save(os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename))
|
||||||
|
db_f = Files(chal.id, os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename))
|
||||||
|
db.session.add(db_f)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return redirect('/admin/chals')
|
||||||
|
|
||||||
|
@app.route('/admin/chal/update', methods=['POST'])
|
||||||
|
def admin_update_chal():
|
||||||
|
challenge=Challenges.query.filter_by(id=request.form['id']).first()
|
||||||
|
challenge.name = request.form['name']
|
||||||
|
challenge.description = request.form['desc']
|
||||||
|
challenge.value = request.form['value']
|
||||||
|
challenge.category = request.form['category']
|
||||||
|
db.session.add(challenge)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return redirect('/admin/chals')
|
|
@ -0,0 +1,129 @@
|
||||||
|
from CTFd import render_template, request, redirect, abort, jsonify, url_for, session
|
||||||
|
from CTFd.utils import sha512, is_safe_url, authed, mailserver
|
||||||
|
from CTFd.models import db, Teams
|
||||||
|
|
||||||
|
from itsdangerous import TimedSerializer, BadTimeSignature
|
||||||
|
from passlib.hash import bcrypt_sha256
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
def init_auth(app):
|
||||||
|
@app.context_processor
|
||||||
|
def inject_user():
|
||||||
|
if authed():
|
||||||
|
return dict(session)
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
@app.route('/reset_password', methods=['POST', 'GET'])
|
||||||
|
@app.route('/reset_password/<data>', methods=['POST', 'GET'])
|
||||||
|
def reset_password(data=None):
|
||||||
|
if data is not None and request.method == "GET":
|
||||||
|
return render_template('reset_password.html', mode='set')
|
||||||
|
if data is not None and request.method == "POST":
|
||||||
|
try:
|
||||||
|
s = TimedSerializer(app.config['SECRET_KEY'])
|
||||||
|
name = s.loads(data.decode('base64'), max_age=1800)
|
||||||
|
except BadTimeSignature:
|
||||||
|
return render_template('reset_password.html', errors=['Your link has expired'])
|
||||||
|
team = Teams.query.filter_by(name=name).first()
|
||||||
|
team.password = sha512(request.form['password'].strip())
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return redirect('/login')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
email = request.form['email'].strip()
|
||||||
|
team = Teams.query.filter_by(email=email).first()
|
||||||
|
if not team:
|
||||||
|
return render_template('reset_password.html', errors=['Check your email'])
|
||||||
|
s = TimedSerializer(app.config['SECRET_KEY'])
|
||||||
|
token = s.dumps(team.name)
|
||||||
|
text = """
|
||||||
|
Did you initiate a password reset?
|
||||||
|
|
||||||
|
{0}/reset_password/{1}
|
||||||
|
|
||||||
|
""".format(app.config['HOST'], token.encode('base64'))
|
||||||
|
|
||||||
|
sendmail(email, text)
|
||||||
|
|
||||||
|
return render_template('reset_password.html', errors=['Check your email'])
|
||||||
|
return render_template('reset_password.html')
|
||||||
|
|
||||||
|
@app.route('/register', methods=['POST', 'GET'])
|
||||||
|
def register():
|
||||||
|
if request.method == 'POST':
|
||||||
|
errors = []
|
||||||
|
name_len = len(request.form['name']) == 0
|
||||||
|
names = Teams.query.add_columns('name', 'id').filter_by(name=request.form['name']).first()
|
||||||
|
emails = Teams.query.add_columns('email', 'id').filter_by(email=request.form['email']).first()
|
||||||
|
pass_len = len(request.form['password']) == 0
|
||||||
|
valid_email = re.match("[^@]+@[^@]+\.[^@]+", request.form['email'])
|
||||||
|
|
||||||
|
if not valid_email:
|
||||||
|
errors.append("That email doesn't look right")
|
||||||
|
if names:
|
||||||
|
errors.append('That team name is already taken')
|
||||||
|
if emails:
|
||||||
|
errors.append('That email has already been used')
|
||||||
|
if pass_len:
|
||||||
|
errors.append('Pick a longer password')
|
||||||
|
if name_len:
|
||||||
|
errors.append('Pick a longer team name')
|
||||||
|
|
||||||
|
if not errors:
|
||||||
|
with app.app_context():
|
||||||
|
team = Teams(request.form['name'], request.form['email'], request.form['password'])
|
||||||
|
db.session.add(team)
|
||||||
|
db.session.commit()
|
||||||
|
if mailserver():
|
||||||
|
sendmail(request.form['email'], "You've successfully registered for the CTF")
|
||||||
|
|
||||||
|
db.session.close()
|
||||||
|
if len(errors) > 0:
|
||||||
|
return render_template('register.html', errors=errors, name=request.form['name'], email=request.form['email'], password=request.form['password'])
|
||||||
|
|
||||||
|
logger = logging.getLogger('regs')
|
||||||
|
logger.warn("[{0}] {1} registered with {2}".format(time.strftime("%m/%d/%Y %X"), request.form['name'], request.form['email']))
|
||||||
|
return redirect('/login')
|
||||||
|
else:
|
||||||
|
return render_template('register.html')
|
||||||
|
|
||||||
|
@app.route('/login', methods=['POST', 'GET'])
|
||||||
|
def login():
|
||||||
|
if request.method == 'POST':
|
||||||
|
errors = []
|
||||||
|
# team = Teams.query.filter_by(name=request.form['name'], password=sha512(request.form['password'])).first()
|
||||||
|
team = Teams.query.filter_by(name=request.form['name']).first()
|
||||||
|
if team and bcrypt_sha256.verify(request.form['password'], team.password):
|
||||||
|
# session.regenerate() # NO SESSION FIXATION FOR YOU
|
||||||
|
session['username'] = team.name
|
||||||
|
session['id'] = team.id
|
||||||
|
session['admin'] = team.admin
|
||||||
|
session['nonce'] = sha512(os.urandom(10))
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
|
logger = logging.getLogger('logins')
|
||||||
|
logger.warn("[{0}] {1} logged in".format(time.strftime("%m/%d/%Y %X"), session['username']))
|
||||||
|
|
||||||
|
# if request.args.get('next') and is_safe_url(request.args.get('next')):
|
||||||
|
# return redirect(request.args.get('next'))
|
||||||
|
return redirect('/team/{0}'.format(team.id))
|
||||||
|
else:
|
||||||
|
errors.append("That account doesn't seem to exist")
|
||||||
|
db.session.close()
|
||||||
|
return render_template('login.html', errors=errors)
|
||||||
|
else:
|
||||||
|
db.session.close()
|
||||||
|
return render_template('login.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
if authed():
|
||||||
|
session.clear()
|
||||||
|
return redirect('/')
|
|
@ -0,0 +1,127 @@
|
||||||
|
from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for
|
||||||
|
|
||||||
|
from CTFd import session, logging
|
||||||
|
from CTFd.utils import ctftime, authed, unix_time, get_kpm
|
||||||
|
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
def init_challenges(app):
|
||||||
|
@app.route('/challenges', methods=['GET'])
|
||||||
|
def challenges():
|
||||||
|
if not ctftime():
|
||||||
|
return redirect('/')
|
||||||
|
if authed():
|
||||||
|
return render_template('chals.html')
|
||||||
|
else:
|
||||||
|
return redirect(url_for('login', next="challenges"))
|
||||||
|
|
||||||
|
@app.route('/chals', methods=['GET'])
|
||||||
|
def chals():
|
||||||
|
if not ctftime():
|
||||||
|
return redirect('/')
|
||||||
|
if authed():
|
||||||
|
chals = Challenges.query.add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
|
||||||
|
|
||||||
|
json = {'game':[]}
|
||||||
|
for x in chals:
|
||||||
|
files = [ str(f.location) for f in Files.query.filter_by(chal=x.id).all() ]
|
||||||
|
json['game'].append({'id':x[1], 'name':x[2], 'value':x[3], 'description':x[4], 'category':x[5], 'files':files})
|
||||||
|
|
||||||
|
db.session.close()
|
||||||
|
return jsonify(json)
|
||||||
|
else:
|
||||||
|
db.session.close()
|
||||||
|
return redirect('/login')
|
||||||
|
|
||||||
|
@app.route('/chals/solves')
|
||||||
|
def chals_per_solves():
|
||||||
|
if authed():
|
||||||
|
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
|
||||||
|
json = {}
|
||||||
|
for chal, count in solves:
|
||||||
|
json[chal.chal.name] = count
|
||||||
|
return jsonify(json)
|
||||||
|
return redirect(url_for('login', next="/chals/solves"))
|
||||||
|
|
||||||
|
@app.route('/solves')
|
||||||
|
@app.route('/solves/<teamid>')
|
||||||
|
def solves(teamid=None):
|
||||||
|
if teamid is None:
|
||||||
|
if authed():
|
||||||
|
solves = Solves.query.filter_by(teamid=session['id']).all()
|
||||||
|
else:
|
||||||
|
return redirect('/login')
|
||||||
|
else:
|
||||||
|
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||||
|
db.session.close()
|
||||||
|
json = {'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)
|
||||||
|
|
||||||
|
@app.route('/fails/<teamid>', methods=['GET'])
|
||||||
|
def fails(teamid):
|
||||||
|
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)
|
||||||
|
|
||||||
|
@app.route('/chal/<chalid>/solves', methods=['GET'])
|
||||||
|
def who_solved(chalid):
|
||||||
|
solves = Solves.query.filter_by(chalid=chalid)
|
||||||
|
json = {'teams':[]}
|
||||||
|
for solve in solves:
|
||||||
|
json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date})
|
||||||
|
return jsonify(json)
|
||||||
|
|
||||||
|
@app.route('/chal/<chalid>', methods=['POST'])
|
||||||
|
def chal(chalid):
|
||||||
|
if not ctftime():
|
||||||
|
return redirect('/')
|
||||||
|
if authed():
|
||||||
|
logger = logging.getLogger('keys')
|
||||||
|
data = (time.strftime("%m/%d/%Y %X"), session['username'], request.form['key'], get_kpm(session['id']))
|
||||||
|
print "[{0}] {1} submitted {2} with kpm {3}".format(*data)
|
||||||
|
if get_kpm(session['id']) > 10:
|
||||||
|
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||||
|
db.session.add(wrong)
|
||||||
|
db.session.commit()
|
||||||
|
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()
|
||||||
|
if not solves:
|
||||||
|
keys = Keys.query.filter_by(chal=chalid).all()
|
||||||
|
key = request.form['key'].strip().lower()
|
||||||
|
for x in keys:
|
||||||
|
if x.key_type == 0: #static key
|
||||||
|
if x.flag.strip().lower() == key:
|
||||||
|
solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr)
|
||||||
|
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)
|
||||||
|
if res and res.group() == key:
|
||||||
|
solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr)
|
||||||
|
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
|
||||||
|
|
||||||
|
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||||
|
db.session.add(wrong)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
logger.info("[{0}] {1} submitted {2} with kpm {3} [WRONG]".format(*data))
|
||||||
|
return '0' # key was wrong
|
||||||
|
else:
|
||||||
|
logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data))
|
||||||
|
return "2" # challenge was already solved
|
||||||
|
else:
|
||||||
|
return "-1"
|
|
@ -0,0 +1,19 @@
|
||||||
|
import os
|
||||||
|
##### SERVER SETTINGS #####
|
||||||
|
SECRET_KEY = os.urandom(64)
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db'
|
||||||
|
SESSION_TYPE = "filesystem"
|
||||||
|
SESSION_FILE_DIR = "/tmp/flask_session"
|
||||||
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
|
HOST = ".ctfd.io"
|
||||||
|
UPLOAD_FOLDER = 'static/uploads'
|
||||||
|
|
||||||
|
##### EMAIL #####
|
||||||
|
CTF_NAME = ''
|
||||||
|
MAIL_SERVER = ''
|
||||||
|
MAIL_PORT = 0
|
||||||
|
MAIL_USE_TLS = False
|
||||||
|
MAIL_USE_SSL = False
|
||||||
|
MAIL_USERNAME = ''
|
||||||
|
MAIL_PASSWORD = ''
|
||||||
|
ADMINS = []
|
|
@ -0,0 +1,14 @@
|
||||||
|
from flask import current_app as app, render_template
|
||||||
|
|
||||||
|
def init_errors(app):
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(error):
|
||||||
|
return render_template('errors/404.html'), 404
|
||||||
|
|
||||||
|
@app.errorhandler(403)
|
||||||
|
def forbidden(error):
|
||||||
|
return render_template('errors/403.html'), 403
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def forbidden(error):
|
||||||
|
return render_template('errors/500.html'), 500
|
|
@ -0,0 +1,169 @@
|
||||||
|
#from CTFd import db
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
from socket import inet_aton, inet_ntoa
|
||||||
|
from struct import unpack, pack
|
||||||
|
from passlib.hash import bcrypt_sha256
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def sha512(string):
|
||||||
|
return hashlib.sha512(string).hexdigest()
|
||||||
|
|
||||||
|
def ip2long(ip):
|
||||||
|
return unpack('!I', inet_aton(ip))[0]
|
||||||
|
|
||||||
|
def long2ip(ip_int):
|
||||||
|
return inet_ntoa(pack('!I', ip_int))
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
|
||||||
|
class Pages(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
route = db.Column(db.String(80), unique=True)
|
||||||
|
html = db.Column(db.Text)
|
||||||
|
|
||||||
|
def __init__(self, route, html):
|
||||||
|
self.route = route
|
||||||
|
self.html = html
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Tag {0} for challenge {1}>".format(self.tag, self.chal)
|
||||||
|
|
||||||
|
class Challenges(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(80))
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
value = db.Column(db.Integer)
|
||||||
|
category = db.Column(db.String(80))
|
||||||
|
# add open category
|
||||||
|
|
||||||
|
def __init__(self, name, description, value, category):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.value = value
|
||||||
|
self.category = category
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<chal %r>' % self.name
|
||||||
|
|
||||||
|
class Tags(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
chal = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||||
|
tag = db.Column(db.String(80))
|
||||||
|
|
||||||
|
def __init__(self, chal, tag):
|
||||||
|
self.chal = chal
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Tag {0} for challenge {1}>".format(self.tag, self.chal)
|
||||||
|
|
||||||
|
class Files(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
chal = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||||
|
location = db.Column(db.Text)
|
||||||
|
|
||||||
|
def __init__(self, chal, location):
|
||||||
|
self.chal = chal
|
||||||
|
self.location = location
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<File {0} for challenge {1}>".format(self.location, self.chal)
|
||||||
|
|
||||||
|
class Keys(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
chal = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||||
|
key_type = db.Column(db.Integer)
|
||||||
|
flag = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, chal, flag, key_type):
|
||||||
|
self.chal = chal
|
||||||
|
self.flag = flag
|
||||||
|
self.key_type = key_type
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.flag
|
||||||
|
|
||||||
|
|
||||||
|
class Teams(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(128), unique=True)
|
||||||
|
email = db.Column(db.String(128), unique=True)
|
||||||
|
password = db.Column(db.String(128))
|
||||||
|
website = db.Column(db.String(128))
|
||||||
|
affiliation = db.Column(db.String(128))
|
||||||
|
country = db.Column(db.String(32))
|
||||||
|
bracket = db.Column(db.String(32))
|
||||||
|
banned = db.Column(db.Boolean)
|
||||||
|
admin = db.Column(db.Boolean)
|
||||||
|
|
||||||
|
def __init__(self, name, email, password):
|
||||||
|
self.name = name
|
||||||
|
self.email = email
|
||||||
|
self.password = bcrypt_sha256.encrypt(password)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<team %r>' % self.name
|
||||||
|
|
||||||
|
class Solves(db.Model):
|
||||||
|
__table_args__ = (db.UniqueConstraint('chalid', 'teamid'), {})
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
chalid = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||||
|
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
||||||
|
ip = db.Column(db.Integer)
|
||||||
|
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||||
|
team = db.relationship('Teams', foreign_keys="Solves.teamid", lazy='joined')
|
||||||
|
chal = db.relationship('Challenges', foreign_keys="Solves.chalid", lazy='joined')
|
||||||
|
# value = db.Column(db.Integer)
|
||||||
|
|
||||||
|
def __init__(self, chalid, teamid, ip):
|
||||||
|
self.ip = ip2long(ip)
|
||||||
|
self.chalid = chalid
|
||||||
|
self.teamid = teamid
|
||||||
|
# self.value = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<solves %r>' % self.chal
|
||||||
|
|
||||||
|
|
||||||
|
class WrongKeys(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
chal = 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)
|
||||||
|
|
||||||
|
def __init__(self, team, chal, flag):
|
||||||
|
self.team = team
|
||||||
|
self.chal = chal
|
||||||
|
self.flag = flag
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<wrong %r>' % self.flag
|
||||||
|
|
||||||
|
|
||||||
|
class Tracking(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
ip = db.Column(db.BigInteger)
|
||||||
|
team = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
||||||
|
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, ip, team):
|
||||||
|
self.ip = ip2long(ip)
|
||||||
|
self.team = team
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<ip %r>' % self.team
|
||||||
|
|
||||||
|
class Config(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
key = db.Column(db.Text)
|
||||||
|
value = db.Column(db.Text)
|
||||||
|
|
||||||
|
def __init__(self, key, value):
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
|
@ -0,0 +1,50 @@
|
||||||
|
from flask import current_app as app
|
||||||
|
from CTFd import session, render_template, jsonify
|
||||||
|
from CTFd.utils import unix_time
|
||||||
|
from CTFd.models import db, Teams, Solves, Challenges
|
||||||
|
|
||||||
|
def init_scoreboard(app):
|
||||||
|
@app.route('/scoreboard')
|
||||||
|
def scoreboard():
|
||||||
|
score = db.func.sum(Challenges.value).label('score')
|
||||||
|
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.engine.execute("SELECT solves.teamid, teams.id, teams.name, SUM(value) as score, MAX(solves.date) as quickest FROM solves JOIN teams ON solves.teamid=teams.id INNER JOIN challenges ON solves.chalid=challenges.id WHERE teams.banned IS NULL GROUP BY solves.teamid ORDER BY score DESC, quickest ASC;")
|
||||||
|
db.session.close()
|
||||||
|
return render_template('scoreboard.html', teams=teams)
|
||||||
|
|
||||||
|
@app.route('/scores')
|
||||||
|
def scores():
|
||||||
|
#teams = db.engine.execute("SELECT solves.teamid, teams.id, teams.name, SUM(value) as score, MAX(solves.date) as quickest FROM solves JOIN teams ON solves.teamid=teams.id INNER JOIN challenges ON solves.chalid=challenges.id WHERE teams.banned IS NULL GROUP BY solves.teamid ORDER BY score DESC, quickest ASC;")
|
||||||
|
score = db.func.sum(Challenges.value).label('score')
|
||||||
|
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':[]}
|
||||||
|
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)
|
||||||
|
|
||||||
|
@app.route('/top/<count>')
|
||||||
|
def topteams(count):
|
||||||
|
try:
|
||||||
|
count = int(count)
|
||||||
|
except:
|
||||||
|
count = 10
|
||||||
|
if count > 20 or count < 0:
|
||||||
|
count = 10
|
||||||
|
|
||||||
|
json = {'scores':{}}
|
||||||
|
|
||||||
|
#teams = db.engine.execute("SELECT solves.teamid, teams.id, teams.name, SUM(value) as score, MAX(solves.date) as quickest FROM solves JOIN teams ON solves.teamid=teams.id INNER JOIN challenges ON solves.chalid=challenges.id WHERE teams.banned IS NULL GROUP BY solves.teamid ORDER BY score DESC, quickest ASC LIMIT {0};".format(count))
|
||||||
|
score = db.func.sum(Challenges.value).label('score')
|
||||||
|
teams = db.session.query(Solves.teamid, Teams.name, score, db.func.max(Solves.date).label('quickest')).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), Solves.date).limit(count)
|
||||||
|
|
||||||
|
|
||||||
|
for team in teams:
|
||||||
|
solves = Solves.query.filter_by(teamid=team.teamid).all()
|
||||||
|
json['scores'][team.name] = []
|
||||||
|
for x in solves:
|
||||||
|
json['scores'][team.name].append({'id':x.teamid, 'chal':x.chalid, 'team':x.teamid, 'value': x.chal.value, 'time':unix_time(x.date)})
|
||||||
|
|
||||||
|
return jsonify(json)
|
|
@ -0,0 +1,124 @@
|
||||||
|
from CTFd import session
|
||||||
|
from CTFd.models import db, WrongKeys, Pages, Config
|
||||||
|
|
||||||
|
from urlparse import urlparse, urljoin
|
||||||
|
from functools import wraps
|
||||||
|
from flask import current_app as app, g, request, redirect, url_for
|
||||||
|
from socket import inet_aton, inet_ntoa
|
||||||
|
from struct import unpack, pack
|
||||||
|
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
|
||||||
|
def init_utils(app):
|
||||||
|
app.jinja_env.filters['unix_time'] = unix_time
|
||||||
|
app.jinja_env.filters['unix_time_millis'] = unix_time_millis
|
||||||
|
app.jinja_env.filters['long2ip'] = long2ip
|
||||||
|
app.jinja_env.globals.update(pages=pages)
|
||||||
|
|
||||||
|
def pages():
|
||||||
|
pages = Pages.query.filter(Pages.route!="index").all()
|
||||||
|
return pages
|
||||||
|
|
||||||
|
def authed():
|
||||||
|
try:
|
||||||
|
if session['id']:
|
||||||
|
return True
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_setup():
|
||||||
|
setup = Config.query.filter_by(key='setup').first()
|
||||||
|
if setup:
|
||||||
|
return setup.value
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_admin():
|
||||||
|
if authed():
|
||||||
|
return session['admin']
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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 f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
|
def ctftime():
|
||||||
|
""" Checks whether it's CTF time or not. """
|
||||||
|
|
||||||
|
start = Config.query.filter_by(key="start").first().value
|
||||||
|
end = Config.query.filter_by(key="end").first().value
|
||||||
|
|
||||||
|
if start:
|
||||||
|
start = int(start)
|
||||||
|
if end:
|
||||||
|
end = int(end)
|
||||||
|
|
||||||
|
if start and end:
|
||||||
|
if start < time.time() and time.time() < end:
|
||||||
|
# Within the two time bounds
|
||||||
|
return True
|
||||||
|
|
||||||
|
if start < time.time() and end is None:
|
||||||
|
# CTF starts on a date but never ends
|
||||||
|
return True
|
||||||
|
|
||||||
|
if start is None and time.time() < end:
|
||||||
|
# CTF started but ends at a date
|
||||||
|
return True
|
||||||
|
|
||||||
|
if start is None and end is None:
|
||||||
|
# CTF has no time requirements
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def unix_time(dt):
|
||||||
|
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||||
|
delta = dt - epoch
|
||||||
|
return int(delta.total_seconds())
|
||||||
|
|
||||||
|
def unix_time_millis(dt):
|
||||||
|
return unix_time(dt) * 1000
|
||||||
|
|
||||||
|
def long2ip(ip_int):
|
||||||
|
return inet_ntoa(pack('!I', ip_int))
|
||||||
|
|
||||||
|
def ip2long(ip):
|
||||||
|
return unpack('!I', inet_aton(ip))[0]
|
||||||
|
|
||||||
|
def get_kpm(teamid): # keys per minute
|
||||||
|
one_min_ago = datetime.datetime.utcnow() + datetime.timedelta(minutes=-1)
|
||||||
|
return len(db.session.query(WrongKeys).filter(WrongKeys.team == teamid, WrongKeys.date >= one_min_ago).all())
|
||||||
|
|
||||||
|
|
||||||
|
def mailserver():
|
||||||
|
if app.config['MAIL_SERVER'] and app.config['MAIL_PORT'] and app.config['ADMINS']:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def sendmail(addr, text):
|
||||||
|
try:
|
||||||
|
msg = Message("Message from {0}".format(app.config['CTF_NAME']), sender = app.config['ADMINS'][0], recipients = [addr])
|
||||||
|
msg.body = text
|
||||||
|
mail.send(msg)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_safe_url(target):
|
||||||
|
ref_url = urlparse(request.host_url)
|
||||||
|
test_url = urlparse(urljoin(request.host_url, target))
|
||||||
|
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
|
||||||
|
|
||||||
|
def sha512(string):
|
||||||
|
return hashlib.sha512(string).hexdigest()
|
|
@ -0,0 +1,167 @@
|
||||||
|
from flask import current_app as app, render_template, render_template_string, request, redirect, abort, jsonify, json as json_mod, url_for
|
||||||
|
from CTFd import session
|
||||||
|
from CTFd.utils import authed, ip2long, long2ip, is_setup
|
||||||
|
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
||||||
|
|
||||||
|
from jinja2.exceptions import TemplateNotFound
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
def init_views(app):
|
||||||
|
@app.before_request
|
||||||
|
def tracker():
|
||||||
|
if authed():
|
||||||
|
if not Tracking.query.filter_by(ip=ip2long(request.remote_addr)).first():
|
||||||
|
visit = Tracking(request.remote_addr, session['id'])
|
||||||
|
db.session.add(visit)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def csrf():
|
||||||
|
if authed() and request.method == "POST":
|
||||||
|
if session['nonce'] != request.form.get('nonce'):
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def redirect_setup():
|
||||||
|
if request.path == "/static/css/style.css":
|
||||||
|
return
|
||||||
|
if not is_setup() and request.path != "/setup":
|
||||||
|
return redirect('/setup')
|
||||||
|
|
||||||
|
@app.before_first_request
|
||||||
|
def needs_setup():
|
||||||
|
if not is_setup():
|
||||||
|
return redirect('/setup')
|
||||||
|
|
||||||
|
@app.route('/setup', methods=['GET', 'POST'])
|
||||||
|
def setup():
|
||||||
|
# with app.app_context():
|
||||||
|
# admin = Teams.query.filter_by(admin=True).first()
|
||||||
|
|
||||||
|
if not is_setup():
|
||||||
|
if request.method == 'POST':
|
||||||
|
## Admin user
|
||||||
|
name = request.form['name']
|
||||||
|
email = request.form['email']
|
||||||
|
password = request.form['password']
|
||||||
|
admin = Teams(name, email, password)
|
||||||
|
admin.admin = True
|
||||||
|
|
||||||
|
## Index page
|
||||||
|
html = request.form['html']
|
||||||
|
page = Pages('index', html)
|
||||||
|
|
||||||
|
## Start time
|
||||||
|
start = Config('start', None)
|
||||||
|
end = Config('end', None)
|
||||||
|
|
||||||
|
setup = Config('setup', True)
|
||||||
|
|
||||||
|
db.session.add(admin)
|
||||||
|
db.session.add(page)
|
||||||
|
db.session.add(start)
|
||||||
|
db.session.add(end)
|
||||||
|
db.session.add(setup)
|
||||||
|
db.session.commit()
|
||||||
|
app.setup = False
|
||||||
|
return redirect('/')
|
||||||
|
return render_template('setup.html')
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
# Static HTML files
|
||||||
|
@app.route("/", defaults={'template': 'index'})
|
||||||
|
@app.route("/<template>")
|
||||||
|
def static_html(template):
|
||||||
|
try:
|
||||||
|
return render_template('%s.html' % template)
|
||||||
|
except TemplateNotFound:
|
||||||
|
page = Pages.query.filter_by(route=template).first()
|
||||||
|
if page:
|
||||||
|
return render_template_string('{% extends "base.html" %}{% block content %}' + page.html + '{% endblock %}')
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
@app.route('/teams')
|
||||||
|
def teams():
|
||||||
|
teams = Teams.query.all()
|
||||||
|
return render_template('teams.html', teams=teams)
|
||||||
|
|
||||||
|
@app.route('/team/<teamid>', methods=['GET', 'POST'])
|
||||||
|
def team(teamid):
|
||||||
|
user = Teams.query.filter_by(id=teamid).first()
|
||||||
|
solves = Solves.query.filter_by(teamid=teamid).all()
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('team.html', solves=solves, team=user)
|
||||||
|
elif request.method == 'POST':
|
||||||
|
json = {'solves':[]}
|
||||||
|
for x in solves:
|
||||||
|
json['solves'].append({'id':x.id, 'chal':x.chalid, 'team':x.teamid})
|
||||||
|
return jsonify(json)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/profile', methods=['POST', 'GET'])
|
||||||
|
def profile():
|
||||||
|
if authed():
|
||||||
|
if request.method == "POST":
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
name = request.form.get('name')
|
||||||
|
email = request.form.get('email')
|
||||||
|
|
||||||
|
if 'password' in request.form.keys() and not len(request.form['password']) == 0:
|
||||||
|
password = request.form.get('password')
|
||||||
|
|
||||||
|
website = request.form.get('website')
|
||||||
|
affiliation = request.form.get('affiliation')
|
||||||
|
country = request.form.get('country')
|
||||||
|
|
||||||
|
names = Teams.query.filter_by(name=name).first()
|
||||||
|
emails = Teams.query.filter_by(email=email).first()
|
||||||
|
valid_email = re.match("[^@]+@[^@]+\.[^@]+", email)
|
||||||
|
|
||||||
|
name_len = len(request.form['name']) == 0
|
||||||
|
|
||||||
|
if not bcrypt_sha256.verify(request.form.get('confirm').strip(), names.password):
|
||||||
|
errors.append("Your old password doesn't match what we have.")
|
||||||
|
if not valid_email:
|
||||||
|
errors.append("That email doesn't look right")
|
||||||
|
if names and name!=session['username']:
|
||||||
|
errors.append('That team name is already taken')
|
||||||
|
if emails and emails.id != session['id']:
|
||||||
|
errors.append('That email has already been used')
|
||||||
|
if name_len:
|
||||||
|
errors.append('Pick a longer team name')
|
||||||
|
|
||||||
|
if len(errors) > 0:
|
||||||
|
return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation, country=country, errors=errors)
|
||||||
|
else:
|
||||||
|
team = Teams.query.filter_by(id=session['id']).first()
|
||||||
|
team.name = name
|
||||||
|
team.email = email
|
||||||
|
team.password = bcrypt_sha256.encrypt(password)
|
||||||
|
team.website = website
|
||||||
|
team.affiliation = affiliation
|
||||||
|
team.country = country
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
return redirect('/profile')
|
||||||
|
else:
|
||||||
|
user = Teams.query.filter_by(id=session['id']).first()
|
||||||
|
name = user.name
|
||||||
|
email = user.email
|
||||||
|
website = user.website
|
||||||
|
affiliation = user.affiliation
|
||||||
|
country = user.country
|
||||||
|
return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation, country=country)
|
||||||
|
else:
|
||||||
|
return redirect('/login')
|
10
README.md
10
README.md
|
@ -1,4 +1,10 @@
|
||||||
CTFd
|
![](https://github.com/isislab/CTFd/blob/master/static/img/logo.png)
|
||||||
====
|
====
|
||||||
|
|
||||||
CTFs as you need them
|
CTFd is a CTF in a can. Easily modifiable and has everything you need to run a jeopardy style CTF.
|
||||||
|
|
||||||
|
Install:
|
||||||
|
1. `./prepare.sh` to install dependencies using apt.
|
||||||
|
2. Modify [CTFd/config.py](https://github.com/isislab/CTFd/blob/master/CTFd/config.py) to your liking.
|
||||||
|
3. Use `python serve.py` in a terminal to drop into debug mode.
|
||||||
|
4. [Here](http://flask.pocoo.org/docs/0.10/deploying/) are some Flask deployment options
|
||||||
|
|
|
@ -0,0 +1,942 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from CTFd.models import Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking
|
||||||
|
from CTFd import create_app
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
app = create_app(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||||
|
|
||||||
|
USER_AMOUNT = 50
|
||||||
|
CHAL_AMOUNT = 20
|
||||||
|
|
||||||
|
categories = [
|
||||||
|
'Exploitation',
|
||||||
|
'Reversing',
|
||||||
|
'Web',
|
||||||
|
'Forensics',
|
||||||
|
'Scripting',
|
||||||
|
'Cryptography',
|
||||||
|
'Networking',
|
||||||
|
]
|
||||||
|
lorems = [
|
||||||
|
'Lorem',
|
||||||
|
'ipsum',
|
||||||
|
'dolor',
|
||||||
|
'sit',
|
||||||
|
'amet,',
|
||||||
|
'consectetur',
|
||||||
|
'adipiscing',
|
||||||
|
'elit.',
|
||||||
|
'Proin',
|
||||||
|
'fringilla',
|
||||||
|
'elit',
|
||||||
|
'velit,',
|
||||||
|
'sed',
|
||||||
|
'scelerisque',
|
||||||
|
'tellus',
|
||||||
|
'dapibus',
|
||||||
|
'vel.',
|
||||||
|
'Aenean',
|
||||||
|
'at',
|
||||||
|
'urna',
|
||||||
|
'porta,',
|
||||||
|
'fringilla',
|
||||||
|
'erat',
|
||||||
|
'eget,',
|
||||||
|
'lobortis',
|
||||||
|
'quam.',
|
||||||
|
'Praesent',
|
||||||
|
'luctus,',
|
||||||
|
'quam',
|
||||||
|
'at',
|
||||||
|
'consequat',
|
||||||
|
'luctus,',
|
||||||
|
'mauris',
|
||||||
|
'sem',
|
||||||
|
'pretium',
|
||||||
|
'metus,',
|
||||||
|
'eu',
|
||||||
|
'viverra',
|
||||||
|
'dui',
|
||||||
|
'leo',
|
||||||
|
'in',
|
||||||
|
'tortor.',
|
||||||
|
'Cras',
|
||||||
|
'iaculis',
|
||||||
|
'enim',
|
||||||
|
'erat,',
|
||||||
|
'sed',
|
||||||
|
'gravida',
|
||||||
|
'velit',
|
||||||
|
'consectetur',
|
||||||
|
'a.',
|
||||||
|
'Duis',
|
||||||
|
'eget',
|
||||||
|
'fermentum',
|
||||||
|
'elit.',
|
||||||
|
'Vivamus',
|
||||||
|
'laoreet',
|
||||||
|
'elementum',
|
||||||
|
'massa,',
|
||||||
|
'ut',
|
||||||
|
'sodales',
|
||||||
|
'mi',
|
||||||
|
'gravida',
|
||||||
|
'at.',
|
||||||
|
'Vivamus',
|
||||||
|
'dignissim',
|
||||||
|
'in',
|
||||||
|
'eros',
|
||||||
|
'non',
|
||||||
|
'iaculis.',
|
||||||
|
'Vivamus',
|
||||||
|
'nec',
|
||||||
|
'sem',
|
||||||
|
'fringilla,',
|
||||||
|
'semper',
|
||||||
|
'lectus',
|
||||||
|
'in,',
|
||||||
|
'malesuada',
|
||||||
|
'tellus.',
|
||||||
|
'Vestibulum',
|
||||||
|
'mattis',
|
||||||
|
'commodo',
|
||||||
|
'enim',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'scelerisque.',
|
||||||
|
'Proin',
|
||||||
|
'at',
|
||||||
|
'condimentum',
|
||||||
|
'nisi,',
|
||||||
|
'nec',
|
||||||
|
'fringilla',
|
||||||
|
'ante.',
|
||||||
|
'Vestibulum',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'neque',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'elit',
|
||||||
|
'placerat',
|
||||||
|
'interdum',
|
||||||
|
'egestas',
|
||||||
|
'ac',
|
||||||
|
'malesuada',
|
||||||
|
'quis',
|
||||||
|
'arcu',
|
||||||
|
'ac',
|
||||||
|
'blandit.',
|
||||||
|
'Vivamus',
|
||||||
|
'in',
|
||||||
|
'massa',
|
||||||
|
'a',
|
||||||
|
'purus',
|
||||||
|
'bibendum',
|
||||||
|
'sagittis.',
|
||||||
|
'Nunc',
|
||||||
|
'venenatis',
|
||||||
|
'lacus',
|
||||||
|
'sed',
|
||||||
|
'nulla',
|
||||||
|
'dapibus,',
|
||||||
|
'consequat',
|
||||||
|
'laoreet',
|
||||||
|
'nisi',
|
||||||
|
'faucibus.',
|
||||||
|
'Nam',
|
||||||
|
'consequat',
|
||||||
|
'viverra',
|
||||||
|
'nibh',
|
||||||
|
'a',
|
||||||
|
'cursus.',
|
||||||
|
'Phasellus',
|
||||||
|
'tristique',
|
||||||
|
'justo',
|
||||||
|
'vitae',
|
||||||
|
'rutrum',
|
||||||
|
'pharetra.',
|
||||||
|
'Sed',
|
||||||
|
'sed',
|
||||||
|
'porttitor',
|
||||||
|
'lacus.',
|
||||||
|
'Nam',
|
||||||
|
'ornare',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'nisi',
|
||||||
|
'imperdiet',
|
||||||
|
'vulputate.',
|
||||||
|
'Maecenas',
|
||||||
|
'hendrerit',
|
||||||
|
'ullamcorper',
|
||||||
|
'elit,',
|
||||||
|
'sed',
|
||||||
|
'pellentesque',
|
||||||
|
'lacus',
|
||||||
|
'bibendum',
|
||||||
|
'sit',
|
||||||
|
'amet.',
|
||||||
|
'Aliquam',
|
||||||
|
'consectetur',
|
||||||
|
'odio',
|
||||||
|
'quis',
|
||||||
|
'tellus',
|
||||||
|
'ornare,',
|
||||||
|
'id',
|
||||||
|
'malesuada',
|
||||||
|
'dui',
|
||||||
|
'rhoncus.',
|
||||||
|
'Quisque',
|
||||||
|
'fringilla',
|
||||||
|
'pellentesque',
|
||||||
|
'nulla',
|
||||||
|
'id',
|
||||||
|
'congue.',
|
||||||
|
'Nulla',
|
||||||
|
'ultricies',
|
||||||
|
'dolor',
|
||||||
|
'tristique',
|
||||||
|
'facilisis',
|
||||||
|
'at',
|
||||||
|
'accumsan',
|
||||||
|
'nisi.',
|
||||||
|
'Praesent',
|
||||||
|
'commodo,',
|
||||||
|
'mauris',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'placerat',
|
||||||
|
'condimentum,',
|
||||||
|
'nibh',
|
||||||
|
'leo',
|
||||||
|
'pulvinar',
|
||||||
|
'justo,',
|
||||||
|
'vel',
|
||||||
|
'dignissim',
|
||||||
|
'mi',
|
||||||
|
'dolor',
|
||||||
|
'et',
|
||||||
|
'est.',
|
||||||
|
'Nulla',
|
||||||
|
'facilisi.',
|
||||||
|
'Sed',
|
||||||
|
'nunc',
|
||||||
|
'est,',
|
||||||
|
'lobortis',
|
||||||
|
'id',
|
||||||
|
'diam',
|
||||||
|
'nec,',
|
||||||
|
'vulputate',
|
||||||
|
'varius',
|
||||||
|
'orci.',
|
||||||
|
'Maecenas',
|
||||||
|
'iaculis',
|
||||||
|
'vehicula',
|
||||||
|
'eros',
|
||||||
|
'eu',
|
||||||
|
'congue.',
|
||||||
|
'Nam',
|
||||||
|
'tempor',
|
||||||
|
'commodo',
|
||||||
|
'lobortis.',
|
||||||
|
'Donec',
|
||||||
|
'eget',
|
||||||
|
'posuere',
|
||||||
|
'dolor,',
|
||||||
|
'ut',
|
||||||
|
'rhoncus',
|
||||||
|
'tortor.',
|
||||||
|
'Donec',
|
||||||
|
'et',
|
||||||
|
'quam',
|
||||||
|
'quis',
|
||||||
|
'urna',
|
||||||
|
'rhoncus',
|
||||||
|
'fermentum',
|
||||||
|
'et',
|
||||||
|
'ut',
|
||||||
|
'tellus.',
|
||||||
|
'Aliquam',
|
||||||
|
'erat',
|
||||||
|
'volutpat.',
|
||||||
|
'Morbi',
|
||||||
|
'porttitor',
|
||||||
|
'ante',
|
||||||
|
'nec',
|
||||||
|
'porta',
|
||||||
|
'mollis.',
|
||||||
|
'Ut',
|
||||||
|
'sodales',
|
||||||
|
'pellentesque',
|
||||||
|
'rutrum.',
|
||||||
|
'Nullam',
|
||||||
|
'elit',
|
||||||
|
'eros,',
|
||||||
|
'sollicitudin',
|
||||||
|
'ac',
|
||||||
|
'rutrum',
|
||||||
|
'sit',
|
||||||
|
'amet,',
|
||||||
|
'eleifend',
|
||||||
|
'vel',
|
||||||
|
'nulla.',
|
||||||
|
'Morbi',
|
||||||
|
'quis',
|
||||||
|
'lacinia',
|
||||||
|
'nisi.',
|
||||||
|
'Integer',
|
||||||
|
'at',
|
||||||
|
'neque',
|
||||||
|
'vel',
|
||||||
|
'velit',
|
||||||
|
'tincidunt',
|
||||||
|
'elementum',
|
||||||
|
'lobortis',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'tellus.',
|
||||||
|
'Nunc',
|
||||||
|
'volutpat',
|
||||||
|
'diam',
|
||||||
|
'ac',
|
||||||
|
'diam',
|
||||||
|
'lacinia,',
|
||||||
|
'id',
|
||||||
|
'molestie',
|
||||||
|
'quam',
|
||||||
|
'eu',
|
||||||
|
'ultricies',
|
||||||
|
'ligula.',
|
||||||
|
'Duis',
|
||||||
|
'iaculis',
|
||||||
|
'massa',
|
||||||
|
'massa,',
|
||||||
|
'eget',
|
||||||
|
'venenatis',
|
||||||
|
'dolor',
|
||||||
|
'fermentum',
|
||||||
|
'laoreet.',
|
||||||
|
'Nam',
|
||||||
|
'posuere,',
|
||||||
|
'erat',
|
||||||
|
'quis',
|
||||||
|
'tempor',
|
||||||
|
'consequat,',
|
||||||
|
'purus',
|
||||||
|
'erat',
|
||||||
|
'hendrerit',
|
||||||
|
'arcu,',
|
||||||
|
'nec',
|
||||||
|
'aliquam',
|
||||||
|
'ligula',
|
||||||
|
'augue',
|
||||||
|
'vitae',
|
||||||
|
'felis.',
|
||||||
|
'Vestibulum',
|
||||||
|
'tincidunt',
|
||||||
|
'ipsum',
|
||||||
|
'vel',
|
||||||
|
'pharetra',
|
||||||
|
'lacinia.',
|
||||||
|
'Quisque',
|
||||||
|
'dignissim,',
|
||||||
|
'arcu',
|
||||||
|
'non',
|
||||||
|
'feugiat',
|
||||||
|
'semper,',
|
||||||
|
'felis',
|
||||||
|
'est',
|
||||||
|
'commodo',
|
||||||
|
'lorem,',
|
||||||
|
'malesuada',
|
||||||
|
'elementum',
|
||||||
|
'nibh',
|
||||||
|
'lectus',
|
||||||
|
'porttitor',
|
||||||
|
'nisi.',
|
||||||
|
'Duis',
|
||||||
|
'non',
|
||||||
|
'lacinia',
|
||||||
|
'nisl.',
|
||||||
|
'Etiam',
|
||||||
|
'ante',
|
||||||
|
'nisl,',
|
||||||
|
'mattis',
|
||||||
|
'eget',
|
||||||
|
'convallis',
|
||||||
|
'vel,',
|
||||||
|
'ullamcorper',
|
||||||
|
'ac',
|
||||||
|
'nisl.',
|
||||||
|
'Duis',
|
||||||
|
'eu',
|
||||||
|
'massa',
|
||||||
|
'at',
|
||||||
|
'urna',
|
||||||
|
'laoreet',
|
||||||
|
'convallis.',
|
||||||
|
'Donec',
|
||||||
|
'tincidunt',
|
||||||
|
'sapien',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'varius',
|
||||||
|
'eu',
|
||||||
|
'dignissim',
|
||||||
|
'tortor,',
|
||||||
|
'elementum',
|
||||||
|
'gravida',
|
||||||
|
'eros.',
|
||||||
|
'Cras',
|
||||||
|
'viverra',
|
||||||
|
'accumsan',
|
||||||
|
'erat,',
|
||||||
|
'et',
|
||||||
|
'euismod',
|
||||||
|
'dui',
|
||||||
|
'placerat',
|
||||||
|
'ac.',
|
||||||
|
'Ut',
|
||||||
|
'tortor',
|
||||||
|
'arcu,',
|
||||||
|
'euismod',
|
||||||
|
'vitae',
|
||||||
|
'aliquam',
|
||||||
|
'in,',
|
||||||
|
'interdum',
|
||||||
|
'vitae',
|
||||||
|
'magna.',
|
||||||
|
'Vestibulum',
|
||||||
|
'leo',
|
||||||
|
'ante,',
|
||||||
|
'posuere',
|
||||||
|
'eget',
|
||||||
|
'est',
|
||||||
|
'non,',
|
||||||
|
'adipiscing',
|
||||||
|
'ultrices',
|
||||||
|
'erat.',
|
||||||
|
'Donec',
|
||||||
|
'suscipit',
|
||||||
|
'felis',
|
||||||
|
'molestie,',
|
||||||
|
'ultricies',
|
||||||
|
'dui',
|
||||||
|
'a,',
|
||||||
|
'facilisis',
|
||||||
|
'magna.',
|
||||||
|
'Cum',
|
||||||
|
'sociis',
|
||||||
|
'natoque',
|
||||||
|
'penatibus',
|
||||||
|
'et',
|
||||||
|
'magnis',
|
||||||
|
'dis',
|
||||||
|
'parturient',
|
||||||
|
'montes,',
|
||||||
|
'nascetur',
|
||||||
|
'ridiculus',
|
||||||
|
'mus.',
|
||||||
|
'Nulla',
|
||||||
|
'quis',
|
||||||
|
'odio',
|
||||||
|
'sit',
|
||||||
|
'amet',
|
||||||
|
'ante',
|
||||||
|
'tristique',
|
||||||
|
'accumsan',
|
||||||
|
'ut',
|
||||||
|
'iaculis',
|
||||||
|
'neque.',
|
||||||
|
'Vivamus',
|
||||||
|
'in',
|
||||||
|
'venenatis',
|
||||||
|
'enim.',
|
||||||
|
'Nunc',
|
||||||
|
'dignissim',
|
||||||
|
'justo',
|
||||||
|
'neque,',
|
||||||
|
'sed',
|
||||||
|
'ultricies',
|
||||||
|
'justo',
|
||||||
|
'dictum',
|
||||||
|
'in.',
|
||||||
|
'Nulla',
|
||||||
|
'eget',
|
||||||
|
'nunc',
|
||||||
|
'ac',
|
||||||
|
'arcu',
|
||||||
|
'vestibulum',
|
||||||
|
'bibendum',
|
||||||
|
'vitae',
|
||||||
|
'quis',
|
||||||
|
'tellus.',
|
||||||
|
'Morbi',
|
||||||
|
'bibendum,',
|
||||||
|
'quam',
|
||||||
|
'ac',
|
||||||
|
'cursus',
|
||||||
|
'posuere,',
|
||||||
|
'purus',
|
||||||
|
'lectus',
|
||||||
|
'tempor',
|
||||||
|
'est,',
|
||||||
|
'eu',
|
||||||
|
'iaculis',
|
||||||
|
'quam',
|
||||||
|
'enim',
|
||||||
|
'a',
|
||||||
|
'nibh.',
|
||||||
|
'Etiam',
|
||||||
|
'consequat',
|
||||||
|
'libero',
|
||||||
|
'vitae',
|
||||||
|
'ullamcorper',
|
||||||
|
'tincidunt.',
|
||||||
|
]
|
||||||
|
hipsters = ['Ethnic', 'narwhal', 'pickled', 'Odd', 'Future', 'cliche', 'VHS', 'whatever', 'Etsy', 'American', 'Apparel', 'kitsch', 'wolf', 'mlkshk', 'fashion', 'axe', 'ethnic', 'banh', 'mi', 'cornhole', 'scenester', 'Echo', 'Park', 'Dreamcatcher', 'tofu', 'fap', 'selvage', 'authentic', 'cliche', 'High', 'Life', 'brunch', 'pork', 'belly', 'viral', 'XOXO', 'drinking', 'vinegar', 'bitters', 'Wayfarers', 'gastropub', 'dreamcatcher', 'chillwave', 'Shoreditch', 'kale', 'chips', 'swag', 'street', 'art', 'put', 'a', 'bird', 'on', 'it', 'Vice', 'synth', 'cliche', 'retro', 'Master', 'cleanse', 'ugh', 'Austin', 'slow-carb', 'small', 'batch', 'Hashtag', 'food', 'truck', 'deep', 'v', 'semiotics', 'chia', 'normcore', 'bicycle', 'rights', 'Austin', 'drinking', 'vinegar', 'hella', 'readymade', 'farm-to-table', 'Wes', 'Anderson', 'put', 'a', 'bird', 'on', 'it', 'freegan', 'Synth', 'lo-fi', 'food', 'truck', 'chambray', 'Shoreditch', 'cliche', 'kogiSynth', 'lo-fi', 'fap', 'single-origin', 'coffee', 'brunch', 'butcher', 'Pickled', 'Etsy', 'locavore', 'forage', 'pug', 'stumptown', 'occupy', 'PBR&B', 'actually', 'shabby', 'chic', 'church-key', 'disrupt', 'lomo', 'hoodie', 'Tumblr', 'biodiesel', 'Pinterest', 'butcher', 'Hella', 'Carles', 'pour-over', 'YOLO', 'VHS', 'literally', 'Selvage', 'narwhal', 'flexitarian', 'wayfarers', 'kitsch', 'bespoke', 'sriracha', 'Banh', 'mi', '8-bit', 'cornhole', 'viral', 'Tonx', 'keytar', 'gastropub', 'YOLO', 'hashtag', 'food', 'truck', '3', 'wolf', 'moonFingerstache', 'flexitarian', 'craft', 'beer', 'shabby', 'chic', '8-bit', 'try-hard', 'semiotics', 'Helvetica', 'keytar', 'PBR', 'four', 'loko', 'scenester', 'keytar', '3', 'wolf', 'moon', 'sriracha', 'gluten-free', 'literally', 'try-hard', 'put', 'a', 'bird', 'on', 'it', 'cornhole', 'blog', 'fanny', 'pack', 'Mumblecore', 'pickled', 'distillery', 'butcher', 'Ennui', 'tote', 'bag', 'letterpress', 'disrupt', 'keffiyeh', 'art', 'party', 'aesthetic', 'Helvetica', 'stumptown', 'Wes', 'Anderson', 'next', 'level', "McSweeney's", 'cornhole', 'Schlitz', 'skateboard', 'pop-up', 'Chillwave', 'biodiesel', 'semiotics', 'seitan', 'authentic', 'bicycle', 'rights', 'wolf', 'pork', 'belly', 'letterpress', 'locavore', 'whatever', 'fixie', 'viral', 'mustache', 'beard', 'Hashtag', 'sustainable', 'lomo', 'cardigan', 'lo-fiWilliamsburg', 'craft', 'beer', 'bitters', 'iPhone', 'gastropub', 'messenger', 'bag', 'Organic', 'post-ironic', 'fingerstache', 'ennui', 'banh', 'mi', 'Art', 'party', 'bitters', 'twee', 'bespoke', 'church-key', 'Intelligentsia', 'sriracha', 'Echo', 'Park', 'Tofu', 'locavore', 'street', 'art', 'freegan', 'farm-to-table', 'distillery', 'hoodie', 'swag', 'ugh', 'YOLO', 'VHS', 'Cred', 'hella', 'readymade', 'distillery', 'Banh', 'mi', 'Echo', 'Park', "McSweeney's,", 'mlkshk', 'photo', 'booth', 'swag', 'Odd', 'Future', 'squid', 'Tonx', 'craft', 'beer', 'High', 'Life', 'tousled', 'PBR', 'you', 'probably', "haven't", 'heard', 'of', 'them', 'locavore', 'PBR&B', 'street', 'art', 'pop-up', 'raw', 'denim']
|
||||||
|
names = [
|
||||||
|
'James',
|
||||||
|
'John',
|
||||||
|
'Robert',
|
||||||
|
'Michael',
|
||||||
|
'William',
|
||||||
|
'David',
|
||||||
|
'Richard',
|
||||||
|
'Joseph',
|
||||||
|
'Charles',
|
||||||
|
'Thomas',
|
||||||
|
'Christopher',
|
||||||
|
'Daniel',
|
||||||
|
'Matthew',
|
||||||
|
'Donald',
|
||||||
|
'Anthony',
|
||||||
|
'Paul',
|
||||||
|
'Mark',
|
||||||
|
'George',
|
||||||
|
'Steven',
|
||||||
|
'Kenneth',
|
||||||
|
'Andrew',
|
||||||
|
'Edward',
|
||||||
|
'Brian',
|
||||||
|
'Joshua',
|
||||||
|
'Kevin',
|
||||||
|
'Ronald',
|
||||||
|
'Timothy',
|
||||||
|
'Jason',
|
||||||
|
'Jeffrey',
|
||||||
|
'Gary',
|
||||||
|
'Ryan',
|
||||||
|
'Nicholas',
|
||||||
|
'Eric',
|
||||||
|
'Stephen',
|
||||||
|
'Jacob',
|
||||||
|
'Larry',
|
||||||
|
'Frank',
|
||||||
|
'Jonathan',
|
||||||
|
'Scott',
|
||||||
|
'Justin',
|
||||||
|
'Raymond',
|
||||||
|
'Brandon',
|
||||||
|
'Gregory',
|
||||||
|
'Samuel',
|
||||||
|
'Patrick',
|
||||||
|
'Benjamin',
|
||||||
|
'Jack',
|
||||||
|
'Dennis',
|
||||||
|
'Jerry',
|
||||||
|
'Alexander',
|
||||||
|
'Tyler',
|
||||||
|
'Douglas',
|
||||||
|
'Henry',
|
||||||
|
'Peter',
|
||||||
|
'Walter',
|
||||||
|
'Aaron',
|
||||||
|
'Jose',
|
||||||
|
'Adam',
|
||||||
|
'Harold',
|
||||||
|
'Zachary',
|
||||||
|
'Nathan',
|
||||||
|
'Carl',
|
||||||
|
'Kyle',
|
||||||
|
'Arthur',
|
||||||
|
'Gerald',
|
||||||
|
'Lawrence',
|
||||||
|
'Roger',
|
||||||
|
'Albert',
|
||||||
|
'Keith',
|
||||||
|
'Jeremy',
|
||||||
|
'Terry',
|
||||||
|
'Joe',
|
||||||
|
'Sean',
|
||||||
|
'Willie',
|
||||||
|
'Jesse',
|
||||||
|
'Ralph',
|
||||||
|
'Billy',
|
||||||
|
'Austin',
|
||||||
|
'Bruce',
|
||||||
|
'Christian',
|
||||||
|
'Roy',
|
||||||
|
'Bryan',
|
||||||
|
'Eugene',
|
||||||
|
'Louis',
|
||||||
|
'Harry',
|
||||||
|
'Wayne',
|
||||||
|
'Ethan',
|
||||||
|
'Jordan',
|
||||||
|
'Russell',
|
||||||
|
'Alan',
|
||||||
|
'Philip',
|
||||||
|
'Randy',
|
||||||
|
'Juan',
|
||||||
|
'Howard',
|
||||||
|
'Vincent',
|
||||||
|
'Bobby',
|
||||||
|
'Dylan',
|
||||||
|
'Johnny',
|
||||||
|
'Phillip',
|
||||||
|
'Craig',
|
||||||
|
'Mary',
|
||||||
|
'Patricia',
|
||||||
|
'Elizabeth',
|
||||||
|
'Jennifer',
|
||||||
|
'Linda',
|
||||||
|
'Barbara',
|
||||||
|
'Susan',
|
||||||
|
'Margaret',
|
||||||
|
'Jessica',
|
||||||
|
'Dorothy',
|
||||||
|
'Sarah',
|
||||||
|
'Karen',
|
||||||
|
'Nancy',
|
||||||
|
'Betty',
|
||||||
|
'Lisa',
|
||||||
|
'Sandra',
|
||||||
|
'Helen',
|
||||||
|
'Donna',
|
||||||
|
'Ashley',
|
||||||
|
'Kimberly',
|
||||||
|
'Carol',
|
||||||
|
'Michelle',
|
||||||
|
'Amanda',
|
||||||
|
'Emily',
|
||||||
|
'Melissa',
|
||||||
|
'Laura',
|
||||||
|
'Deborah',
|
||||||
|
'Stephanie',
|
||||||
|
'Rebecca',
|
||||||
|
'Sharon',
|
||||||
|
'Cynthia',
|
||||||
|
'Ruth',
|
||||||
|
'Kathleen',
|
||||||
|
'Anna',
|
||||||
|
'Shirley',
|
||||||
|
'Amy',
|
||||||
|
'Angela',
|
||||||
|
'Virginia',
|
||||||
|
'Brenda',
|
||||||
|
'Pamela',
|
||||||
|
'Catherine',
|
||||||
|
'Katherine',
|
||||||
|
'Nicole',
|
||||||
|
'Christine',
|
||||||
|
'Janet',
|
||||||
|
'Debra',
|
||||||
|
'Carolyn',
|
||||||
|
'Samantha',
|
||||||
|
'Rachel',
|
||||||
|
'Heather',
|
||||||
|
'Maria',
|
||||||
|
'Diane',
|
||||||
|
'Frances',
|
||||||
|
'Joyce',
|
||||||
|
'Julie',
|
||||||
|
'Martha',
|
||||||
|
'Joan',
|
||||||
|
'Evelyn',
|
||||||
|
'Kelly',
|
||||||
|
'Christina',
|
||||||
|
'Emma',
|
||||||
|
'Lauren',
|
||||||
|
'Alice',
|
||||||
|
'Judith',
|
||||||
|
'Marie',
|
||||||
|
'Doris',
|
||||||
|
'Ann',
|
||||||
|
'Jean',
|
||||||
|
'Victoria',
|
||||||
|
'Cheryl',
|
||||||
|
'Megan',
|
||||||
|
'Kathryn',
|
||||||
|
'Andrea',
|
||||||
|
'Jacqueline',
|
||||||
|
'Gloria',
|
||||||
|
'Teresa',
|
||||||
|
'Janice',
|
||||||
|
'Sara',
|
||||||
|
'Rose',
|
||||||
|
'Julia',
|
||||||
|
'Hannah',
|
||||||
|
'Theresa',
|
||||||
|
'Judy',
|
||||||
|
'Mildred',
|
||||||
|
'Grace',
|
||||||
|
'Beverly',
|
||||||
|
'Denise',
|
||||||
|
'Marilyn',
|
||||||
|
'Amber',
|
||||||
|
'Danielle',
|
||||||
|
'Brittany',
|
||||||
|
'Diana',
|
||||||
|
'Jane',
|
||||||
|
'Lori',
|
||||||
|
'Olivia',
|
||||||
|
'Tiffany',
|
||||||
|
'Kathy',
|
||||||
|
'Tammy',
|
||||||
|
'Crystal',
|
||||||
|
'Madison',
|
||||||
|
]
|
||||||
|
emails = [
|
||||||
|
'@gmail.com',
|
||||||
|
'@yahoo.com',
|
||||||
|
'@outlook.com',
|
||||||
|
'@hotmail.com',
|
||||||
|
'@mailinator.com',
|
||||||
|
'@poly.edu',
|
||||||
|
'@nyu.edu'
|
||||||
|
]
|
||||||
|
extensions = [
|
||||||
|
'.doc',
|
||||||
|
'.log',
|
||||||
|
'.msg',
|
||||||
|
'.rtf',
|
||||||
|
'.txt',
|
||||||
|
'.wpd',
|
||||||
|
'.wps',
|
||||||
|
'.123',
|
||||||
|
'.csv',
|
||||||
|
'.dat',
|
||||||
|
'.db ',
|
||||||
|
'.dll',
|
||||||
|
'.mdb',
|
||||||
|
'.pps',
|
||||||
|
'.ppt',
|
||||||
|
'.sql',
|
||||||
|
'.wks',
|
||||||
|
'.xls',
|
||||||
|
'.xml',
|
||||||
|
'.mng',
|
||||||
|
'.pct',
|
||||||
|
'.bmp',
|
||||||
|
'.gif',
|
||||||
|
'.jpe',
|
||||||
|
'.jpg',
|
||||||
|
'.png',
|
||||||
|
'.psd',
|
||||||
|
'.psp',
|
||||||
|
'.tif',
|
||||||
|
'.ai ',
|
||||||
|
'.drw',
|
||||||
|
'.dxf',
|
||||||
|
'.eps',
|
||||||
|
'.ps ',
|
||||||
|
'.svg',
|
||||||
|
'.3dm',
|
||||||
|
'.3dm',
|
||||||
|
'.ind',
|
||||||
|
'.pdf',
|
||||||
|
'.qxd',
|
||||||
|
'.qxp',
|
||||||
|
'.aac',
|
||||||
|
'.aif',
|
||||||
|
'.iff',
|
||||||
|
'.m3u',
|
||||||
|
'.mid',
|
||||||
|
'.mid',
|
||||||
|
'.mp3',
|
||||||
|
'.mpa',
|
||||||
|
'.ra ',
|
||||||
|
'.ram',
|
||||||
|
'.wav',
|
||||||
|
'.wma',
|
||||||
|
'.3gp',
|
||||||
|
'.asf',
|
||||||
|
'.asx',
|
||||||
|
'.avi',
|
||||||
|
'.mov',
|
||||||
|
'.mp4',
|
||||||
|
'.mpg',
|
||||||
|
'.qt ',
|
||||||
|
'.rm ',
|
||||||
|
'.swf',
|
||||||
|
'.wmv',
|
||||||
|
'.asp',
|
||||||
|
'.css',
|
||||||
|
'.htm',
|
||||||
|
'.htm',
|
||||||
|
'.js ',
|
||||||
|
'.jsp',
|
||||||
|
'.php',
|
||||||
|
'.xht',
|
||||||
|
'.fnt',
|
||||||
|
'.fon',
|
||||||
|
'.otf',
|
||||||
|
'.ttf',
|
||||||
|
'.8bi',
|
||||||
|
'.plu',
|
||||||
|
'.xll',
|
||||||
|
'.cab',
|
||||||
|
'.cpl',
|
||||||
|
'.cur',
|
||||||
|
'.dmp',
|
||||||
|
'.drv',
|
||||||
|
'.key',
|
||||||
|
'.lnk',
|
||||||
|
'.sys',
|
||||||
|
'.cfg',
|
||||||
|
'.ini',
|
||||||
|
'.reg',
|
||||||
|
'.app',
|
||||||
|
'.bat',
|
||||||
|
'.cgi',
|
||||||
|
'.com',
|
||||||
|
'.exe',
|
||||||
|
'.pif',
|
||||||
|
'.vb ',
|
||||||
|
'.ws ',
|
||||||
|
'.deb',
|
||||||
|
'.gz ',
|
||||||
|
'.pkg',
|
||||||
|
'.rar',
|
||||||
|
'.sea',
|
||||||
|
'.sit',
|
||||||
|
'.sit',
|
||||||
|
'.zip',
|
||||||
|
'.bin',
|
||||||
|
'.hqx',
|
||||||
|
'.0 E',
|
||||||
|
'.mim',
|
||||||
|
'.uue',
|
||||||
|
'.cpp',
|
||||||
|
'.jav',
|
||||||
|
'.pl ',
|
||||||
|
'.bak',
|
||||||
|
'.gho',
|
||||||
|
'.old',
|
||||||
|
'.ori',
|
||||||
|
'.tmp',
|
||||||
|
'.dmg',
|
||||||
|
'.iso',
|
||||||
|
'.toa',
|
||||||
|
'.vcd',
|
||||||
|
'.gam',
|
||||||
|
'.nes',
|
||||||
|
'.rom',
|
||||||
|
'.sav',
|
||||||
|
'.msi',
|
||||||
|
'.tor',
|
||||||
|
'.yps'
|
||||||
|
]
|
||||||
|
|
||||||
|
def gen_sentence():
|
||||||
|
return ' '.join(random.sample(lorems, 50))
|
||||||
|
|
||||||
|
def gen_name():
|
||||||
|
return random.choice(names)
|
||||||
|
|
||||||
|
def gen_email():
|
||||||
|
return random.choice(emails)
|
||||||
|
|
||||||
|
def gen_category():
|
||||||
|
return random.choice(categories)
|
||||||
|
|
||||||
|
def gen_value():
|
||||||
|
return random.choice(range(100, 500, 50))
|
||||||
|
|
||||||
|
def gen_word():
|
||||||
|
return random.choice(hipsters)
|
||||||
|
|
||||||
|
def gen_file():
|
||||||
|
return gen_word()+random.choice(extensions)
|
||||||
|
|
||||||
|
def random_date(start, end):
|
||||||
|
return start + datetime.timedelta(
|
||||||
|
seconds=randint(0, int((end - start).total_seconds())))
|
||||||
|
with app.app_context():
|
||||||
|
db = app.db
|
||||||
|
|
||||||
|
### Generating Challenges
|
||||||
|
print "GENERATING CHALLENGES"
|
||||||
|
for x in range(CHAL_AMOUNT):
|
||||||
|
word = gen_word()
|
||||||
|
db.session.add(Challenges(word, gen_sentence(), gen_value(), gen_category()))
|
||||||
|
db.session.commit()
|
||||||
|
db.session.add(Keys(x+1, word, 0))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
### Generating Files
|
||||||
|
print "GENERATING FILES"
|
||||||
|
AMT_CHALS_WITH_FILES = int(CHAL_AMOUNT * (3.0/4.0))
|
||||||
|
for x in range(AMT_CHALS_WITH_FILES):
|
||||||
|
chal = random.randint(1, CHAL_AMOUNT)
|
||||||
|
filename = gen_file()
|
||||||
|
md5hash = hashlib.md5(filename).hexdigest()
|
||||||
|
db.session.add( Files(chal, os.path.join('static/uploads', md5hash, filename)) )
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
### Generating Users
|
||||||
|
print "GENERATING USERS"
|
||||||
|
used = []
|
||||||
|
while len(used) < USER_AMOUNT:
|
||||||
|
name = gen_name()
|
||||||
|
if name not in used:
|
||||||
|
used.append(name)
|
||||||
|
db.session.add( Teams(name , name.lower() + gen_email(), 'password') )
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
### Generating Solves
|
||||||
|
print "GENERATING SOLVES"
|
||||||
|
base_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=-2880)
|
||||||
|
for x in range(USER_AMOUNT):
|
||||||
|
used = []
|
||||||
|
for y in range(random.randint(1,CHAL_AMOUNT)):
|
||||||
|
chalid = random.randint(1,CHAL_AMOUNT)
|
||||||
|
if chalid not in used:
|
||||||
|
used.append(chalid)
|
||||||
|
solve = Solves(chalid, x+1, '127.0.0.1')
|
||||||
|
|
||||||
|
new_base = random_date(base_time, base_time + datetime.timedelta(minutes=60))
|
||||||
|
solve.date = new_base
|
||||||
|
base_time = new_base
|
||||||
|
|
||||||
|
db.session.add(solve)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
### Generating Wrong Keys
|
||||||
|
print "GENERATING WRONG KEYS"
|
||||||
|
base_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=-3880)
|
||||||
|
for x in range(USER_AMOUNT):
|
||||||
|
used = []
|
||||||
|
for y in range(random.randint(1,CHAL_AMOUNT * 20)):
|
||||||
|
chalid = random.randint(1,CHAL_AMOUNT)
|
||||||
|
if chalid not in used:
|
||||||
|
used.append(chalid)
|
||||||
|
wrong = WrongKeys(x+1, chalid, gen_word())
|
||||||
|
|
||||||
|
new_base = random_date(base_time, base_time + datetime.timedelta(minutes=60))
|
||||||
|
wrong.date = new_base
|
||||||
|
base_time = new_base
|
||||||
|
|
||||||
|
db.session.add(wrong)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
|
@ -0,0 +1,2 @@
|
||||||
|
sudo apt-get install build-essential python-dev python-pip
|
||||||
|
sudo pip install Flask Flask-Session Flask-SQLAlchemy Flask-Mail apscheduler passlib
|
|
@ -0,0 +1,3 @@
|
||||||
|
from CTFd import create_app
|
||||||
|
app = create_app('')
|
||||||
|
app.run(debug=True, host="0.0.0.0", port=4000)
|
|
@ -0,0 +1,84 @@
|
||||||
|
#submit-key{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > h1{
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > form{
|
||||||
|
width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > form > h3,h4{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > form > input{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Not sure why foundation needs these two...*/
|
||||||
|
.top-bar input{
|
||||||
|
height: auto;
|
||||||
|
padding-top: 0.35rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar .button{
|
||||||
|
padding-top: 0.45rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown{
|
||||||
|
background-color: #333 !important;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown button{
|
||||||
|
padding-top: 0.45rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#challenges button{
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row h1{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.textbox{
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chal-tag{
|
||||||
|
margin: 0 5px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#score-graph{
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keys-pie-graph{
|
||||||
|
width: 400px;
|
||||||
|
max-height: 330px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#categories-pie-graph{
|
||||||
|
width: 600px;
|
||||||
|
float: left;
|
||||||
|
max-height: 330px;
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
function loadchal(id) {
|
||||||
|
// $('#chal *').show()
|
||||||
|
// $('#chal > h1').hide()
|
||||||
|
obj = $.grep(challenges['game'], function (e) {
|
||||||
|
return e.id == id;
|
||||||
|
})[0]
|
||||||
|
$('#update-challenge .chal-name').val(obj.name)
|
||||||
|
$('#update-challenge .chal-desc').html(obj.description)
|
||||||
|
$('#update-challenge .chal-value').val(obj.value)
|
||||||
|
$('#update-challenge .chal-category').val(obj.category)
|
||||||
|
$('#update-challenge .chal-id').val(obj.id)
|
||||||
|
$('#update-challenge .chal-delete').attr({
|
||||||
|
'href': '/admin/chal/close/' + (id + 1)
|
||||||
|
})
|
||||||
|
$('#update-challenge').foundation('reveal', 'open');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitkey(chal, key) {
|
||||||
|
$.post("/admin/chal/" + chal, {
|
||||||
|
key: key,
|
||||||
|
nonce: $('#nonce').val()
|
||||||
|
}, function (data) {
|
||||||
|
alert(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadkeys(chal){
|
||||||
|
$.get('/admin/keys/' + chal, function(data){
|
||||||
|
$('#keys-chal').val(chal)
|
||||||
|
keys = $.parseJSON(JSON.stringify(data));
|
||||||
|
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')
|
||||||
|
$('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatekeys(){
|
||||||
|
keys = [];
|
||||||
|
vals = [];
|
||||||
|
chal = $('#keys-chal').val()
|
||||||
|
$('.current-key').each(function(){
|
||||||
|
keys.push($(this).val());
|
||||||
|
})
|
||||||
|
$('#current-keys > input[name*="key_type"]:checked').each(function(){
|
||||||
|
vals.push($(this).val());
|
||||||
|
})
|
||||||
|
$.post('/admin/keys/'+chal, {'keys':keys, 'vals':vals, 'nonce': $('#nonce').val()})
|
||||||
|
loadchal(chal)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadtags(chal){
|
||||||
|
$('#tags-chal').val(chal)
|
||||||
|
$('#current-tags').empty()
|
||||||
|
$('#chal-tags').empty()
|
||||||
|
$.get('/admin/tags/'+chal, function(data){
|
||||||
|
tags = $.parseJSON(JSON.stringify(data))
|
||||||
|
tags = tags['tags']
|
||||||
|
for (var i = 0; i < tags.length; i++) {
|
||||||
|
tag = "<span class='secondary label chal-tag'><span>"+tags[i].tag+"</span><a name='"+tags[i].id+"'' class='delete-tag'>×</a></span>"
|
||||||
|
$('#current-tags').append(tag)
|
||||||
|
};
|
||||||
|
$('.delete-tag').click(function(e){
|
||||||
|
deletetag(e.target.name)
|
||||||
|
$(e.target).parent().remove()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletetag(tagid){
|
||||||
|
$.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatetags(){
|
||||||
|
tags = [];
|
||||||
|
chal = $('#tags-chal').val()
|
||||||
|
$('#chal-tags > span > span').each(function(i, e){
|
||||||
|
tags.push($(e).text())
|
||||||
|
});
|
||||||
|
$.post('/admin/tags/'+chal, {'tags':tags, 'nonce': $('#nonce').val()})
|
||||||
|
loadchal(chal)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadfiles(chal){
|
||||||
|
$('#update-files > form').attr('action', '/admin/files/'+chal)
|
||||||
|
$.get('/admin/files/' + chal, function(data){
|
||||||
|
$('#files-chal').val(chal)
|
||||||
|
files = $.parseJSON(JSON.stringify(data));
|
||||||
|
files = files['files']
|
||||||
|
$('#current-files').empty()
|
||||||
|
for(x=0; x<files.length; x++){
|
||||||
|
filename = files[x].file.split('/')
|
||||||
|
filename = filename[filename.length - 1]
|
||||||
|
$('#current-files').append('<div data-alert class="alert-box info radius">'+filename+'<a href="#" onclick="deletefile('+chal+','+files[x].id+', $(this))" value="'+files[x].id+'" style="float:right;">Delete</a></div>')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletefile(chal, file, elem){
|
||||||
|
$.post('/admin/files/' + chal,{
|
||||||
|
'nonce': $('#nonce').val(),
|
||||||
|
'method': 'delete',
|
||||||
|
'file': file
|
||||||
|
}, function (data){
|
||||||
|
if (data == "1") {
|
||||||
|
elem.parent().remove()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadchals(){
|
||||||
|
$.post("/admin/chals", {
|
||||||
|
'nonce': $('#nonce').val()
|
||||||
|
}, function (data) {
|
||||||
|
categories = [];
|
||||||
|
challenges = $.parseJSON(JSON.stringify(data));
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = challenges['game'].length - 1; i >= 0; i--) {
|
||||||
|
if ($.inArray(challenges['game'][i].category, categories) == -1) {
|
||||||
|
categories.push(challenges['game'][i].category)
|
||||||
|
$('#challenges').append($('<tr id="' + challenges['game'][i].category.replace(/ /g,"-") + '"><td class="large-2"><h3>' + challenges['game'][i].category + '</h3></td></tr>'))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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,"-")).append($('<button value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>'));
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#challenges button').click(function (e) {
|
||||||
|
loadchal(this.value);
|
||||||
|
loadkeys(this.value);
|
||||||
|
loadtags(this.value);
|
||||||
|
loadfiles(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('tr').append('<button class="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())
|
||||||
|
$('#new-challenge').foundation('reveal', 'open');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$('#submit-key').click(function (e) {
|
||||||
|
submitkey($('#chalid').val(), $('#answer').val())
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#submit-keys').click(function (e) {
|
||||||
|
if (confirm('Updating keys. Are you sure?')){
|
||||||
|
updatekeys()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#submit-tags').click(function (e) {
|
||||||
|
updatetags()
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".tag-insert").keyup(function (e) {
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
tag = $('.tag-insert').val()
|
||||||
|
tag = tag.replace(/'/g, '');
|
||||||
|
if (tag.length > 0){
|
||||||
|
tag = "<span class='secondary label chal-tag'><span>"+tag+"</span><a onclick='$(this).parent().remove()'>×</a></span>"
|
||||||
|
$('#chal-tags').append(tag)
|
||||||
|
}
|
||||||
|
$('.tag-insert').val("")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.create-category').click(function (e) {
|
||||||
|
$('#new-category').foundation('reveal', 'open');
|
||||||
|
});
|
||||||
|
count = 1
|
||||||
|
$('#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++
|
||||||
|
});
|
||||||
|
|
||||||
|
$(function(){
|
||||||
|
loadchals()
|
||||||
|
})
|
|
@ -0,0 +1,159 @@
|
||||||
|
function teamid (){
|
||||||
|
loc = window.location.pathname
|
||||||
|
return loc.substring(loc.lastIndexOf('/')+1, loc.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cumulativesum (arr) {
|
||||||
|
var result = arr.concat();
|
||||||
|
for (var i = 0; i < arr.length; i++){
|
||||||
|
result[i] = arr.slice(0, i + 1).reduce(function(p, i){ return p + i; });
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoregraph () {
|
||||||
|
var times = []
|
||||||
|
var scores = []
|
||||||
|
var teamname = $('#team-id').text()
|
||||||
|
$.get('/admin/solves/'+teamid(), function( data ) {
|
||||||
|
solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
solves = solves['solves']
|
||||||
|
|
||||||
|
if (solves.length == 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
for (var i = 0; i < solves.length; i++) {
|
||||||
|
times.push(solves[i].time * 1000)
|
||||||
|
scores.push(solves[i].value)
|
||||||
|
};
|
||||||
|
scores = cumulativesum(scores)
|
||||||
|
|
||||||
|
times.unshift('x1')
|
||||||
|
times.push( Math.round(new Date().getTime()) )
|
||||||
|
|
||||||
|
scores.unshift('data1')
|
||||||
|
scores.push( scores[scores.length-1] )
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: "#score-graph",
|
||||||
|
data: {
|
||||||
|
xs: {
|
||||||
|
"data1": 'x1',
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
times,
|
||||||
|
scores,
|
||||||
|
],
|
||||||
|
type: "area",
|
||||||
|
labels: true,
|
||||||
|
names : {
|
||||||
|
data1: teamname
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axis : {
|
||||||
|
x : {
|
||||||
|
tick: {
|
||||||
|
format: function (x) {
|
||||||
|
return moment(x).local().format('LLL');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
y:{
|
||||||
|
label: {
|
||||||
|
text: 'Score'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjust_times () {
|
||||||
|
$.each($(".solve-time"), function(i, e){
|
||||||
|
$(e).text( moment(parseInt(e.innerText)).local().format('LLL') )
|
||||||
|
})
|
||||||
|
$(".solve-time").css('color', "#222")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function keys_percentage_graph(){
|
||||||
|
// Solves and Fails pie chart
|
||||||
|
$.get('/admin/fails/'+teamid(), function(data){
|
||||||
|
res = $.parseJSON(JSON.stringify(data));
|
||||||
|
solves = res['solves']
|
||||||
|
fails = res['fails']
|
||||||
|
total = solves+fails
|
||||||
|
|
||||||
|
if (total == 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: '#keys-pie-graph',
|
||||||
|
data: {
|
||||||
|
columns: [
|
||||||
|
['Solves', solves],
|
||||||
|
['Fails', fails],
|
||||||
|
],
|
||||||
|
type : 'donut'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
pattern: ["#00D140", "#CF2600"]
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
title: "Solves vs Fails",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function category_breakdown_graph(){
|
||||||
|
$.get('/admin/solves/'+teamid(), function(data){
|
||||||
|
solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
solves = solves['solves']
|
||||||
|
|
||||||
|
if (solves.length == 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
for (var i = 0; i < solves.length; i++) {
|
||||||
|
categories.push(solves[i].category)
|
||||||
|
};
|
||||||
|
|
||||||
|
keys = categories.filter(function(elem, pos) {
|
||||||
|
return categories.indexOf(elem) == pos;
|
||||||
|
})
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
temp = []
|
||||||
|
count = 0
|
||||||
|
for (var x = 0; x < categories.length; x++) {
|
||||||
|
if (categories[x] == keys[i]){
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
};
|
||||||
|
temp.push(keys[i])
|
||||||
|
temp.push(count)
|
||||||
|
data.push(temp)
|
||||||
|
};
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: '#categories-pie-graph',
|
||||||
|
data: {
|
||||||
|
columns: data,
|
||||||
|
type : 'donut',
|
||||||
|
labels: true
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
title: "Category Breakdown"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
category_breakdown_graph()
|
||||||
|
keys_percentage_graph()
|
||||||
|
adjust_times()
|
||||||
|
scoregraph()
|
|
@ -0,0 +1,105 @@
|
||||||
|
#chal > form{
|
||||||
|
width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal-modal{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chal-desc{
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Not sure why foundation needs these two...*/
|
||||||
|
.top-bar input{
|
||||||
|
height: auto;
|
||||||
|
padding-top: 0.35rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar .button{
|
||||||
|
padding-top: 0.45rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown{
|
||||||
|
background-color: #333 !important;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown button{
|
||||||
|
padding-top: 0.45rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#challenges button{
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row > h1{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#challenges{
|
||||||
|
line-height: 66px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#score-graph{
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown{
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown input{
|
||||||
|
margin: 5px auto;
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown button{
|
||||||
|
margin: 10px auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keys-pie-graph{
|
||||||
|
width: 50%;
|
||||||
|
max-height: 330px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#categories-pie-graph{
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
max-height: 330px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo{
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 500px;
|
||||||
|
padding: 50px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 40.063em){
|
||||||
|
.top-bar .dropdown{
|
||||||
|
display: block;
|
||||||
|
padding: 0 15px 5px;
|
||||||
|
width: 200% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar input{
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,244 @@
|
||||||
|
//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;
|
||||||
|
};
|
||||||
|
|
||||||
|
var challenges;
|
||||||
|
|
||||||
|
function loadchal(id) {
|
||||||
|
obj = $.grep(challenges['game'], function (e) {
|
||||||
|
return e.id == id;
|
||||||
|
})[0]
|
||||||
|
window.location.hash = obj.name
|
||||||
|
$('#chal-window .chal-name').text(obj.name)
|
||||||
|
$('#chal-window .chal-desc').html(marked(obj.description, {'gfm':true, 'breaks':true}))
|
||||||
|
|
||||||
|
for (var i = 0; i < obj.files.length; i++) {
|
||||||
|
filename = obj.files[i].split('/')
|
||||||
|
filename = filename[filename.length - 1]
|
||||||
|
$('#chal-window .chal-desc').append("<a href='"+obj.files[i]+"'>"+filename+"</a><br/>")
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#chal-window .chal-value').text(obj.value)
|
||||||
|
$('#chal-window .chal-category').text(obj.category)
|
||||||
|
$('#chal-window #chal-id').val(obj.id)
|
||||||
|
$('#chal-window .chal-solves').text(obj.solves + " solves")
|
||||||
|
$('#answer').val("")
|
||||||
|
|
||||||
|
$('pre code').each(function(i, block) {
|
||||||
|
hljs.highlightBlock(block);
|
||||||
|
});
|
||||||
|
$('#chal-window').foundation('reveal', 'open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadchalbyname(chalname) {
|
||||||
|
obj = $.grep(challenges['game'], function (e) {
|
||||||
|
return e.name == chalname;
|
||||||
|
})[0]
|
||||||
|
window.location.hash = obj.name
|
||||||
|
$('#chal-window .chal-name').text(obj.name)
|
||||||
|
$('#chal-window .chal-desc').html(marked(obj.description, {'gfm':true, 'breaks':true}))
|
||||||
|
|
||||||
|
for (var i = 0; i < obj.files.length; i++) {
|
||||||
|
filename = obj.files[i].split('/')
|
||||||
|
filename = filename[filename.length - 1]
|
||||||
|
$('#chal-window .chal-desc').append("<a href='"+obj.files[i]+"'>"+filename+"</a><br/>")
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#chal-window .chal-value').text(obj.value)
|
||||||
|
$('#chal-window .chal-category').text(obj.category)
|
||||||
|
$('#chal-window #chal-id').val(obj.id)
|
||||||
|
$('#chal-window .chal-solves').text(obj.solves + " solves")
|
||||||
|
$('#answer').val("")
|
||||||
|
|
||||||
|
$('pre code').each(function(i, block) {
|
||||||
|
hljs.highlightBlock(block);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chal-window').foundation('reveal', 'open');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$("#answer").keyup(function(event){
|
||||||
|
if(event.keyCode == 13){
|
||||||
|
$("#submit-key").click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function submitkey(chal, key, nonce) {
|
||||||
|
$.post("/chal/" + chal, {
|
||||||
|
key: key,
|
||||||
|
nonce: nonce
|
||||||
|
}, function (data) {
|
||||||
|
if (data == -1){
|
||||||
|
window.location="/login"
|
||||||
|
}
|
||||||
|
else if (data == 0){ // Incorrect key
|
||||||
|
$('#submit-key').text('Incorrect, sorry')
|
||||||
|
$('#submit-key').css('background-color', 'red')
|
||||||
|
$('#submit-key').prop('disabled', true)
|
||||||
|
}
|
||||||
|
else if (data == 1){ // Challenge Solved
|
||||||
|
$('#submit-key').text('Correct!')
|
||||||
|
$('#submit-key').css('background-color', 'green')
|
||||||
|
$('#submit-key').prop('disabled', true)
|
||||||
|
$('#chal-window .chal-solves').text( (parseInt($('#chal-window .chal-solves').text().split(" ")[0]) + 1 + " solves") )
|
||||||
|
}
|
||||||
|
else if (data == 2){ // Challenge already solved
|
||||||
|
$('#submit-key').text('You already solved this')
|
||||||
|
$('#submit-key').prop('disabled', true)
|
||||||
|
}
|
||||||
|
else if (data == 3){ // Keys per minute too high
|
||||||
|
$('#submit-key').text("You're submitting keys too fast. Slow down.")
|
||||||
|
$('#submit-key').css('background-color', '#e18728')
|
||||||
|
$('#submit-key').prop('disabled', true)
|
||||||
|
}
|
||||||
|
marksolves()
|
||||||
|
updatesolves()
|
||||||
|
setTimeout(function(){
|
||||||
|
$('#submit-key').text('Submit')
|
||||||
|
$('#submit-key').prop('disabled', false)
|
||||||
|
$('#submit-key').css('background-color', '#007095')
|
||||||
|
}, 3000);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function marksolves() {
|
||||||
|
$.get('/solves', function (data) {
|
||||||
|
solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
for (var i = solves['solves'].length - 1; i >= 0; i--) {
|
||||||
|
id = solves['solves'][i].chalid
|
||||||
|
$('#challenges button[value="' + id + '"]').addClass('secondary')
|
||||||
|
$('#challenges button[value="' + id + '"]').css('opacity', '0.3')
|
||||||
|
};
|
||||||
|
if (window.location.hash.length > 0){
|
||||||
|
loadchalbyname(window.location.hash.substring(1))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatesolves(){
|
||||||
|
$.get('/chals/solves', function (data) {
|
||||||
|
solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
chals = Object.keys(solves);
|
||||||
|
|
||||||
|
for (var i = 0; i < chals.length; i++) {
|
||||||
|
obj = $.grep(challenges['game'], function (e) {
|
||||||
|
return e.name == chals[i];
|
||||||
|
})[0]
|
||||||
|
obj.solves = solves[chals[i]]
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getsolves(id){
|
||||||
|
$.get('/chal/'+id+'/solves', function (data) {
|
||||||
|
var teams = data['teams'];
|
||||||
|
var box = $('#chal-solves-names');
|
||||||
|
box.empty();
|
||||||
|
for (var i = 0; i < teams.length; i++) {
|
||||||
|
var id = teams[i].id;
|
||||||
|
var name = teams[i].name;
|
||||||
|
var date = moment(teams[i].date).local().format('LLL');
|
||||||
|
box.append('<tr><td><a href="/team/{0}">{1}</td><td>{2}</td></tr>'.format(id, name, date));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadchals() {
|
||||||
|
|
||||||
|
$.get("/chals", function (data) {
|
||||||
|
categories = [];
|
||||||
|
challenges = $.parseJSON(JSON.stringify(data));
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = challenges['game'].length - 1; i >= 0; i--) {
|
||||||
|
challenges['game'][i].solves = 0
|
||||||
|
if ($.inArray(challenges['game'][i].category, categories) == -1) {
|
||||||
|
categories.push(challenges['game'][i].category)
|
||||||
|
$('#challenges').append($('<tr id="' + challenges['game'][i].category.replace(/ /g,"-") + '"><td class="large-2"><h4>' + challenges['game'][i].category + '</h4></td></tr>'))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i <= challenges['game'].length - 1; i++) {
|
||||||
|
$('#' + challenges['game'][i].category.replace(/ /g,"-")).append($('<button value="' + challenges['game'][i].id + '">' + challenges['game'][i].value + '</button>'));
|
||||||
|
};
|
||||||
|
updatesolves()
|
||||||
|
marksolves()
|
||||||
|
|
||||||
|
$('#challenges button').click(function (e) {
|
||||||
|
loadchal(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#submit-key').click(function (e) {
|
||||||
|
submitkey($('#chal-id').val(), $('#answer').val(), $('#nonce').val())
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.chal-solves').click(function (e) {
|
||||||
|
getsolves($('#chal-id').val())
|
||||||
|
});
|
||||||
|
|
||||||
|
// $.distint(array)
|
||||||
|
// Unique elements in array
|
||||||
|
$.extend({
|
||||||
|
distinct : function(anArray) {
|
||||||
|
var result = [];
|
||||||
|
$.each(anArray, function(i,v){
|
||||||
|
if ($.inArray(v, result) == -1) result.push(v);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('close', '[data-reveal]', function () {
|
||||||
|
window.location.hash = ""
|
||||||
|
});
|
||||||
|
|
||||||
|
// function solves_graph() {
|
||||||
|
// $.get('/graphs/solves', function(data){
|
||||||
|
// solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
// chals = []
|
||||||
|
// counts = []
|
||||||
|
// colors = []
|
||||||
|
// i = 1
|
||||||
|
// $.each(solves, function(key, value){
|
||||||
|
// chals.push(key)
|
||||||
|
// counts.push(value)
|
||||||
|
// colors.push(colorhash(i++))
|
||||||
|
// });
|
||||||
|
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
function update(){
|
||||||
|
$('#challenges').empty()
|
||||||
|
loadchals()
|
||||||
|
solves_graph()
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
loadchals()
|
||||||
|
// solves_graph()
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(update, 300000);
|
|
@ -0,0 +1,109 @@
|
||||||
|
//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;
|
||||||
|
};
|
||||||
|
|
||||||
|
function updatescores () {
|
||||||
|
$.get('/scores', function( data ) {
|
||||||
|
teams = $.parseJSON(JSON.stringify(data));
|
||||||
|
$('#scoreboard > tbody').empty()
|
||||||
|
for (var i = 0; i < teams['teams'].length; i++) {
|
||||||
|
row = "<tr><td>{0}</td><td><a href='/team/{1}'>{2}</a></td><td>{3}</td></tr>".format(i+1, teams['teams'][i].id, teams['teams'][i].name, teams['teams'][i].score)
|
||||||
|
$('#scoreboard > tbody').append(row)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cumulativesum (arr) {
|
||||||
|
var result = arr.concat();
|
||||||
|
for (var i = 0; i < arr.length; i++){
|
||||||
|
result[i] = arr.slice(0, i + 1).reduce(function(p, i){ return p + i; });
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function UTCtoDate(utc){
|
||||||
|
var d = new Date(0)
|
||||||
|
d.setUTCSeconds(utc)
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoregraph () {
|
||||||
|
var times = []
|
||||||
|
var scores = []
|
||||||
|
$.get('/top/10', function( data ) {
|
||||||
|
scores = $.parseJSON(JSON.stringify(data));
|
||||||
|
scores = scores['scores']
|
||||||
|
if (Object.keys(scores).length == 0 ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#score-graph').show()
|
||||||
|
teams = Object.keys(scores)
|
||||||
|
|
||||||
|
xs_data = {}
|
||||||
|
column_data = []
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
team_scores = cumulativesum(team_scores)
|
||||||
|
|
||||||
|
times.unshift("x"+i)
|
||||||
|
times.push( Math.round(new Date().getTime()/1000) )
|
||||||
|
|
||||||
|
team_scores.unshift(teams[i])
|
||||||
|
team_scores.push( team_scores[team_scores.length-1] )
|
||||||
|
|
||||||
|
|
||||||
|
xs_data[teams[i]] = "x"+i
|
||||||
|
column_data.push(times)
|
||||||
|
column_data.push(team_scores)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: "#score-graph",
|
||||||
|
data: {
|
||||||
|
xs: xs_data,
|
||||||
|
columns: column_data,
|
||||||
|
type: "step",
|
||||||
|
labels: true
|
||||||
|
},
|
||||||
|
axis : {
|
||||||
|
x : {
|
||||||
|
tick: {
|
||||||
|
format: function (x) {
|
||||||
|
return moment(x*1000).local().format('LLL');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
y:{
|
||||||
|
label: {
|
||||||
|
text: 'Score'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// zoom : {
|
||||||
|
// enabled: true
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(){
|
||||||
|
updatescores()
|
||||||
|
scoregraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(update, 300000); // Update scores every 5 minutes
|
||||||
|
scoregraph()
|
|
@ -0,0 +1,168 @@
|
||||||
|
function teamid (){
|
||||||
|
loc = window.location.pathname
|
||||||
|
return loc.substring(loc.lastIndexOf('/')+1, loc.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 cumulativesum (arr) {
|
||||||
|
var result = arr.concat();
|
||||||
|
for (var i = 0; i < arr.length; i++){
|
||||||
|
result[i] = arr.slice(0, i + 1).reduce(function(p, i){ return p + i; });
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoregraph () {
|
||||||
|
var times = []
|
||||||
|
var scores = []
|
||||||
|
var teamname = $('#team-id').text()
|
||||||
|
$.get('/solves/'+teamid(), function( data ) {
|
||||||
|
solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
solves = solves['solves']
|
||||||
|
|
||||||
|
console.log(solves)
|
||||||
|
|
||||||
|
if (solves.length == 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
for (var i = 0; i < solves.length; i++) {
|
||||||
|
times.push(solves[i].time * 1000)
|
||||||
|
scores.push(solves[i].value)
|
||||||
|
};
|
||||||
|
scores = cumulativesum(scores)
|
||||||
|
|
||||||
|
times.unshift('x1')
|
||||||
|
// times.push( Math.round(new Date().getTime()) )
|
||||||
|
|
||||||
|
scores.unshift('data1')
|
||||||
|
// scores.push( scores[scores.length-1] )
|
||||||
|
|
||||||
|
console.log(scores)
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: "#score-graph",
|
||||||
|
data: {
|
||||||
|
xs: {
|
||||||
|
"data1": 'x1',
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
times,
|
||||||
|
scores,
|
||||||
|
],
|
||||||
|
type: "area-step",
|
||||||
|
colors: {
|
||||||
|
data1: colorhash(teamid()),
|
||||||
|
},
|
||||||
|
labels: true,
|
||||||
|
names : {
|
||||||
|
data1: teamname
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axis : {
|
||||||
|
x : {
|
||||||
|
tick: {
|
||||||
|
format: function (x) {
|
||||||
|
return moment(x).local().format('M/D h:mm:ss');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
y:{
|
||||||
|
label: {
|
||||||
|
text: 'Score'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zoom : {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function keys_percentage_graph(){
|
||||||
|
// Solves and Fails pie chart
|
||||||
|
$.get('/fails/'+teamid(), function(data){
|
||||||
|
res = $.parseJSON(JSON.stringify(data));
|
||||||
|
solves = res['solves']
|
||||||
|
fails = res['fails']
|
||||||
|
total = solves+fails
|
||||||
|
|
||||||
|
if (total == 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: '#keys-pie-graph',
|
||||||
|
data: {
|
||||||
|
columns: [
|
||||||
|
['Solves', solves],
|
||||||
|
['Fails', fails],
|
||||||
|
],
|
||||||
|
type : 'donut'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
pattern: ["#00D140", "#CF2600"]
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
title: "Solves vs Fails",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function category_breakdown_graph(){
|
||||||
|
$.get('/solves/'+teamid(), function(data){
|
||||||
|
solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
solves = solves['solves']
|
||||||
|
if (solves.length == 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
for (var i = 0; i < solves.length; i++) {
|
||||||
|
categories.push(solves[i].category)
|
||||||
|
};
|
||||||
|
|
||||||
|
keys = categories.filter(function(elem, pos) {
|
||||||
|
return categories.indexOf(elem) == pos;
|
||||||
|
})
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
temp = []
|
||||||
|
count = 0
|
||||||
|
for (var x = 0; x < categories.length; x++) {
|
||||||
|
if (categories[x] == keys[i]){
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
};
|
||||||
|
temp.push(keys[i])
|
||||||
|
temp.push(count)
|
||||||
|
data.push(temp)
|
||||||
|
};
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: '#categories-pie-graph',
|
||||||
|
data: {
|
||||||
|
columns: data,
|
||||||
|
type : 'donut',
|
||||||
|
labels: true
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
title: "Category Breakdown",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
category_breakdown_graph()
|
||||||
|
keys_percentage_graph()
|
||||||
|
scoregraph()
|
|
@ -0,0 +1,54 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/normalize.min.css" />
|
||||||
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/foundation.min.css" />
|
||||||
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/admin/css/style.css">
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-bar" data-topbar>
|
||||||
|
<ul class="title-area">
|
||||||
|
<li class="name">
|
||||||
|
<h1><a href="/">CTF</a></h1>
|
||||||
|
</li>
|
||||||
|
<li class="toggle-topbar menu-icon"><a href="#">Menu</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<section class="top-bar-section">
|
||||||
|
<!-- Right Nav Section -->
|
||||||
|
<ul class="right">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Left Nav Section -->
|
||||||
|
<ul class="left">
|
||||||
|
<li><a href="/admin/graphs">Graphs</a></li>
|
||||||
|
<li><a href="/admin/pages">Pages</a></li>
|
||||||
|
<li><a href="/admin/teams">Teams</a></li>
|
||||||
|
<li><a href="/admin/scoreboard">Scoreboard</a></li>
|
||||||
|
<li><a href="/admin/chals">Challenges</a></li>
|
||||||
|
<li><a href="/admin/statistics">Statistics</a></li>
|
||||||
|
<li><a href="/admin/config">Config</a></li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% 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/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>
|
||||||
|
$(document).foundation();
|
||||||
|
</script>
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,131 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% 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">×</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="create-challenge" class="reveal-modal" data-reveal>
|
||||||
|
<h3>New Challenge</h3>
|
||||||
|
<input type='text' name='name' placeholder='Name'><br/>
|
||||||
|
<textarea name='desc' placeholder='Description'></textarea><br/>
|
||||||
|
<input type='text' 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">×</a>
|
||||||
|
</form>
|
||||||
|
</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='text' 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/>
|
||||||
|
|
||||||
|
<button type='submit'>Create</button>
|
||||||
|
<a class="close-reveal-modal">×</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="update-keys" class="reveal-modal" data-reveal>
|
||||||
|
<form method="POST" action="/admin/keys">
|
||||||
|
<h3>Keys</h3>
|
||||||
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
|
<input id="keys-chal" name='chal' type='hidden'>
|
||||||
|
<div id="current-keys"></div>
|
||||||
|
<a href="#" id="create-key" class="secondary button">New Key</a>
|
||||||
|
<a href="#" id="submit-keys" class="button">Update</a>
|
||||||
|
<a class="close-reveal-modal">×</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="update-files" class="reveal-modal" data-reveal>
|
||||||
|
<form method="POST" action="/admin/files" enctype="multipart/form-data">
|
||||||
|
<h3>Files</h3>
|
||||||
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
|
<input id="files-chal" name='chal' type='hidden'>
|
||||||
|
|
||||||
|
<div id="current-files"></div>
|
||||||
|
<input type="hidden" name="method" value="upload">
|
||||||
|
<input type="file" name="files[]" multiple="multiple">
|
||||||
|
<button type='submit'>Upload</button>
|
||||||
|
<a class="close-reveal-modal">×</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="update-tags" class="reveal-modal" data-reveal>
|
||||||
|
<h3>Tags</h3>
|
||||||
|
<input type="text" class="tag-insert" maxlength="80" placeholder="Type tag and press Enter">
|
||||||
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
|
<input id="tags-chal" name='chal' type='hidden'>
|
||||||
|
|
||||||
|
<div id="current-tags">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div id="chal-tags">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="#" id="submit-tags" class="button">Update</a>
|
||||||
|
<a class="close-reveal-modal">×</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="update-challenge" class="reveal-modal" data-reveal>
|
||||||
|
<form method="POST" action="/admin/chal/update">
|
||||||
|
<h3>Update Challenge</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='text' 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/>
|
||||||
|
|
||||||
|
<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-keys" class="secondary button">Keys</a>
|
||||||
|
<button type='submit'>Update</button>
|
||||||
|
<a class="close-reveal-modal">×</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h1>CTF</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<table id='challenges'>
|
||||||
|
</table>
|
||||||
|
<button style="width:100%;" class="create-category">New Category</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="/static/admin/js/chalboard.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<br>
|
||||||
|
<h1>Config</h1>
|
||||||
|
<form method="POST">
|
||||||
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
|
<strong>Start Date:</strong>
|
||||||
|
<input name='start' type='text' placeholder="Start Date (UTC timestamp)" {% if start is defined and start != None %}value="{{ start }}"{% endif %}>
|
||||||
|
|
||||||
|
<strong>End Date:</strong>
|
||||||
|
<input name='end' type='text' placeholder="End Date (UTC timestamp)" {% if end is defined and end != None %}value="{{ end }}"{% endif %}>
|
||||||
|
|
||||||
|
<button class="radius" type='submit'>Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.css">
|
||||||
|
<div class="row">
|
||||||
|
<div class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="large-8 large-centered columns">
|
||||||
|
<div data-alert class="alert-box alert radius centered text-center">
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
<a href="#" class="close">×</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="page-edit" method="POST">
|
||||||
|
<br>
|
||||||
|
<strong>Route: </strong><input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
|
<input class="radius" id="route" type="text" name="route" value="{% if page is defined %}{{ page.route }}{% endif %}" placeholder="Route">
|
||||||
|
<strong>HTML: </strong><textarea id="admin-pages-editor" name="html">{% if page is defined %}{{ page.html }}{% endif %}</textarea><br>
|
||||||
|
<button class="radius" type='submit'>Create</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var editor = CodeMirror.fromTextArea(document.getElementById("admin-pages-editor"), {
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
mode: "text/html"
|
||||||
|
});
|
||||||
|
$('#page-edit').submit(function (e){
|
||||||
|
$(this).attr('action', '/admin/pages/'+$('#route').val());
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,168 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div id="solves-graph"></div>
|
||||||
|
<div id="keys-pie-graph"></div>
|
||||||
|
<div id="categories-pie-graph"></div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<style>
|
||||||
|
#solves-graph{
|
||||||
|
margin-left: -50px;
|
||||||
|
}
|
||||||
|
#solves-graph > svg{
|
||||||
|
overflow: visible;
|
||||||
|
padding: 10px 50px 0 50px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
text {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
#solves-graph rect {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<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 type="text/javascript">
|
||||||
|
// $.distint(array)
|
||||||
|
// Unique elements in array
|
||||||
|
$.extend({
|
||||||
|
distinct : function(anArray) {
|
||||||
|
var result = [];
|
||||||
|
$.each(anArray, function(i,v){
|
||||||
|
if ($.inArray(v, result) == -1) result.push(v);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Praise Resig: http://ejohn.org/blog/fast-javascript-maxmin/
|
||||||
|
Array.max = function( array ){
|
||||||
|
return Math.max.apply( Math, array );
|
||||||
|
};
|
||||||
|
|
||||||
|
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 solves_graph() {
|
||||||
|
$.get('/admin/graphs/solves', function(data){
|
||||||
|
solves = $.parseJSON(JSON.stringify(data));
|
||||||
|
chals = []
|
||||||
|
counts = ['Challenges']
|
||||||
|
colors = []
|
||||||
|
i = 1
|
||||||
|
$.each(solves, function(key, value){
|
||||||
|
chals.push(key)
|
||||||
|
counts.push(value)
|
||||||
|
colors.push(colorhash(i++))
|
||||||
|
});
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: '#solves-graph',
|
||||||
|
size: {
|
||||||
|
width: 1000,
|
||||||
|
height: 500
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
columns: [
|
||||||
|
counts
|
||||||
|
],
|
||||||
|
type: 'bar',
|
||||||
|
labels: true
|
||||||
|
},
|
||||||
|
axis: {
|
||||||
|
y: {
|
||||||
|
max: null,
|
||||||
|
min: 0,
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
type: 'categorized',
|
||||||
|
categories: chals,
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
rotated: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function keys_percentage_graph(){
|
||||||
|
// Solves and Fails pie chart
|
||||||
|
$.get('/admin/fails/all', function(data){
|
||||||
|
res = $.parseJSON(JSON.stringify(data));
|
||||||
|
solves = res['solves']
|
||||||
|
fails = res['fails']
|
||||||
|
total = solves+fails
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: '#keys-pie-graph',
|
||||||
|
data: {
|
||||||
|
columns: [
|
||||||
|
['Solves', solves],
|
||||||
|
['Fails', fails],
|
||||||
|
],
|
||||||
|
type : 'donut'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
pattern: ["#00D140", "#CF2600"]
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
title: "Solves vs Fails"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function category_breakdown_graph(){
|
||||||
|
$.get('/admin/graphs/categories', function(data){
|
||||||
|
res = $.parseJSON(JSON.stringify(data));
|
||||||
|
res = res['categories']
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for (var i = 0; i < res.length; i++) {
|
||||||
|
temp = []
|
||||||
|
temp.push(res[i].category)
|
||||||
|
temp.push(res[i].count)
|
||||||
|
data.push(temp)
|
||||||
|
};
|
||||||
|
|
||||||
|
var chart = c3.generate({
|
||||||
|
bindto: '#categories-pie-graph',
|
||||||
|
data: {
|
||||||
|
columns: data,
|
||||||
|
type : 'donut',
|
||||||
|
labels: true
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
title: "Category Breakdown"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(){
|
||||||
|
solves_graph()
|
||||||
|
keys_percentage_graph()
|
||||||
|
category_breakdown_graph()
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
update()
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(update, 300000);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<h1>Login</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="large-8 large-centered columns">
|
||||||
|
<div data-alert class="alert-box alert radius centered text-center">
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
<a href="#" class="close">×</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input class="radius" type='text' name='name' placeholder='Name'><br/>
|
||||||
|
<input class="radius" type='password' name='password' placeholder='Password'><br/>
|
||||||
|
<p><a href="/reset_password">Forgot your password?</a></p>
|
||||||
|
<button class="radius" type='submit'>Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<br>
|
||||||
|
<table id="pages">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Route</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for route in routes %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="/admin/pages/{{ route.route }}">{{ route.route }}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<form method="POST">
|
||||||
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
|
<button class="radius" type='submit'>New Page</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,42 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<p></p>
|
||||||
|
<table id="scoreboard">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Place</b></td>
|
||||||
|
<td><b>Team</b></td>
|
||||||
|
<td><b>Score</b></td>
|
||||||
|
<td><b>Status</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for team in teams %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ loop.index }}</td>
|
||||||
|
<td><a href="/admin/team/{{ team.teamid }}">{{ team.name }}</a></td>
|
||||||
|
<td>{{ team.score }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not team.banned %}
|
||||||
|
<form method="POST" style="margin:0;" action="/admin/team/{{ team.teamid }}/ban">
|
||||||
|
<a onclick="$(this).parent().submit()">Ban</a>
|
||||||
|
<input type="hidden" value="{{ nonce }}" name="nonce">
|
||||||
|
</form>
|
||||||
|
{%else %}
|
||||||
|
<form method="POST" style="margin:0;" action="/admin/team/{{ team.teamid }}/unban">
|
||||||
|
<a onclick="$(this).parent().submit()">Unban</a>
|
||||||
|
<input type="hidden" value="{{ nonce }}" name="nonce">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,88 @@
|
||||||
|
#submit-key{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > h1{
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > form{
|
||||||
|
width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > form > h3,h4{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chal > form > input{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Not sure why foundation needs these two...*/
|
||||||
|
.top-bar input{
|
||||||
|
height: auto;
|
||||||
|
padding-top: 0.35rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar .button{
|
||||||
|
padding-top: 0.45rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown{
|
||||||
|
background-color: #333 !important;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown button{
|
||||||
|
padding-top: 0.45rem;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#challenges button{
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row h1{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.textbox{
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chal-tag{
|
||||||
|
margin: 0 5px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#score-graph{
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keys-pie-graph{
|
||||||
|
width: 400px;
|
||||||
|
max-height: 330px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#categories-pie-graph{
|
||||||
|
width: 600px;
|
||||||
|
float: left;
|
||||||
|
max-height: 330px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-pages-editor{
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h1>Statistics</h1>
|
||||||
|
|
||||||
|
<h3><b>{{ team_count }}</b> teams registered</h3>
|
||||||
|
<h3><b>{{ hit_count}}</b> hits</h3>
|
||||||
|
<h3><b>{{ wrong_count }}</b> wrong keys submitted</h3>
|
||||||
|
<h3><b>{{ solve_count }}</b> right keys submitted</h3>
|
||||||
|
<h3><b>{{ challenge_count }}</b> challenges</h3>
|
||||||
|
{% if most_solved %}
|
||||||
|
<h3>Most solved: <b>{{ most_solved[0].chal.name }}</b> with {{ most_solved[1] }}</b> solves</h3>
|
||||||
|
{% endif %}
|
||||||
|
{% if least_solved %}
|
||||||
|
<h3>Least solved: <b>{{ least_solved[0].chal.name }}</b> with {{ least_solved[1] }}</b> solves</h3>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,53 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h1 id="team-id">{{ team.name }}</h1>
|
||||||
|
|
||||||
|
<div id="keys-pie-graph"></div>
|
||||||
|
<div id="categories-pie-graph"></div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Challenge</b></td>
|
||||||
|
<td><b>Category</b></td>
|
||||||
|
<td><b>Value</b></td>
|
||||||
|
<td><b>Time</b></td>
|
||||||
|
</tr>
|
||||||
|
</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>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>IP Address</b></td>
|
||||||
|
<td><b>Last Seen</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for addr in addrs %}
|
||||||
|
<tr><td>{{ addr.ip|long2ip }}</td><td class="solve-time">{{ addr.date|unix_time_millis }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id="score-graph"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.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="/static/admin/js/team.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<p></p>
|
||||||
|
<table id="teamsboard">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Team</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for team in teams %}
|
||||||
|
<tr><td><a href="/admin/team/{{ team.id }}">{{ team.name }}</a></td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,93 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/normalize.min.css" />
|
||||||
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/foundation.min.css" />
|
||||||
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" />
|
||||||
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/railscasts.min.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="top-bar" data-topbar>
|
||||||
|
<ul class="title-area">
|
||||||
|
<li class="name">
|
||||||
|
<h1><a href="/">CTF</a></h1>
|
||||||
|
</li>
|
||||||
|
<li class="toggle-topbar menu-icon"><a href="#">Menu</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<section class="top-bar-section">
|
||||||
|
<!-- Right Nav Section -->
|
||||||
|
<ul class="right">
|
||||||
|
{% if username is defined %}
|
||||||
|
<li class="has-dropdown">
|
||||||
|
<a href="/team/{{ id }}">{{ username }} {% if admin %} (ADMIN) {% endif %}</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
{% if admin %}
|
||||||
|
<li><a href="/admin">Admin</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><a href="/team/{{ id }}">Team</a></li>
|
||||||
|
<li><a href="/profile">Account</a></li>
|
||||||
|
<li><a href="/logout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{%else %}
|
||||||
|
<li class="has-form">
|
||||||
|
<li class="has-dropdown">
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
<form method="POST" action="/register">
|
||||||
|
<li><input type='text' name='name' placeholder='Name'></li>
|
||||||
|
<li><input type='text' name='email' placeholder='Email'></li>
|
||||||
|
<li><input type='password' name='password' placeholder='Password'></li>
|
||||||
|
<li><button type='submit'>Register</button></li>
|
||||||
|
</form>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="has-dropdown">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
<form method="POST" action="/login">
|
||||||
|
<li><input type='text' name='name' placeholder='Name'></li>
|
||||||
|
<li><input type='password' name='password' placeholder='Password'></li>
|
||||||
|
<li><button type='submit'>Login</button></li>
|
||||||
|
<li><a href="/reset_password" class="text-center">Forgot?</a></li>
|
||||||
|
</form>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Left Nav Section -->
|
||||||
|
<ul class="left">
|
||||||
|
{% for page in pages() %}
|
||||||
|
<li><a href="/{{ page.route }}">{{ page.route|title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
<li><a href="/teams">Teams</a></li>
|
||||||
|
<li><a href="/scoreboard">Scoreboard</a></li>
|
||||||
|
<li><a href="/challenges">Challenges</a></li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% 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/marked/0.3.2/marked.min.js"></script>
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/highlight.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>
|
||||||
|
$(document).foundation();
|
||||||
|
</script>
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,54 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||||
|
|
||||||
|
<div id="chal-solves-window" class="reveal-modal" data-reveal>
|
||||||
|
<h3>Solved By</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Name</b>
|
||||||
|
</td>
|
||||||
|
<td><b>Date</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="chal-solves-names">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<a class="close-reveal-modal" data-reveal-id="chal-window">×</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="chal-window" class="reveal-modal" data-reveal>
|
||||||
|
<div class="row">
|
||||||
|
<h3 class='chal-name'></h3>
|
||||||
|
<h4 class="chal-value"></h4>
|
||||||
|
<p><i><a data-reveal-id="chal-solves-window" class="chal-solves"></a></i></p>
|
||||||
|
<p class="chal-desc"></p>
|
||||||
|
|
||||||
|
<input id="answer" type="text" placeholder="Key">
|
||||||
|
<input type="hidden" id="nonce" name="nonce" value={{ nonce }}>
|
||||||
|
<button id="submit-key">Submit</button>
|
||||||
|
<input id="chal-id" type="hidden">
|
||||||
|
</div>
|
||||||
|
<a class="close-reveal-modal">×</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h2 class="text-center">Challenges</h2>
|
||||||
|
|
||||||
|
<div class="large-12 columns">
|
||||||
|
<table id='challenges'>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="solves-graph" style="width: 90%; height: 400px; margin: 0 auto"></div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.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="/static/js/chalboard.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
CTF
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h2 style="text-align:center;">I don't think you're allowed here</h2>
|
||||||
|
<h2 style="text-align:center;">Not so sorry about that</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h2 style="text-align:center;">Whoops, looks like we can't find that.</h2>
|
||||||
|
<h2 style="text-align:center;">Sorry about that</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h2 style="text-align:center;">Uhh what did you just do?</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<h1>Login</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="large-8 large-centered columns">
|
||||||
|
<div data-alert class="alert-box alert radius centered text-center">
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
<a href="#" class="close">×</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input class="radius" type='text' name='name' placeholder='Name'><br/>
|
||||||
|
<input class="radius" type='password' name='password' placeholder='Password'><br/>
|
||||||
|
<p><a href="/reset_password">Forgot your password?</a></p>
|
||||||
|
<button class="radius" type='submit'>Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,46 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<br/>
|
||||||
|
{% for error in errors %}
|
||||||
|
<h1>{{ error }}</h1>
|
||||||
|
{% endfor %}
|
||||||
|
<form method="POST">
|
||||||
|
<span>Team Name</span>
|
||||||
|
<input class="radius" type="text" name="name" placeholder="Team Name" value="{{name}}">
|
||||||
|
|
||||||
|
<span>Email Address</span>
|
||||||
|
<input class="radius" type="text" name="email" placeholder="Email Address" value="{{email}}">
|
||||||
|
<br/>
|
||||||
|
<hr/>
|
||||||
|
<br/>
|
||||||
|
<span>Old Password</span>
|
||||||
|
<input class="radius" type="password" name="confirm" placeholder="Old Password">
|
||||||
|
|
||||||
|
<span>Password</span>
|
||||||
|
<input class="radius" type="password" name="password" placeholder="Password">
|
||||||
|
<br/>
|
||||||
|
<hr/>
|
||||||
|
<br/>
|
||||||
|
<span>Website</span>
|
||||||
|
<input class="radius" type="text" name="website" placeholder="Website" value="{% if website %}{{website}}{% endif %}">
|
||||||
|
<span>Affiliation</span>
|
||||||
|
<input class="radius" type="text" name="affiliation" placeholder="Affiliation" value="{% if affiliation %}{{affiliation}}{% endif %}">
|
||||||
|
<span>Country</span>
|
||||||
|
<input class="radius" type="text" name="country" placeholder="Country" value="{% if country %}{{country}}{% endif %}">
|
||||||
|
<input type="hidden" name="nonce" value="{{nonce}}">
|
||||||
|
<button class="radius">Update</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<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="/static/js/team.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<h1>Register</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="large-8 large-centered columns">
|
||||||
|
<div data-alert class="alert-box alert radius centered text-center">
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
<a href="#" class="close">×</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input type='text' class="radius" name='name' placeholder='Team Name' {% if name %}value="{{ name }}"{% endif %}><br/>
|
||||||
|
<input type='text' class="radius" name='email' placeholder='Email' {% if email %}value="{{ email }}"{% endif %}><br/>
|
||||||
|
<input type='password' class="radius" name='password' placeholder='Password' {% if password %}value="{{ password }}"{% endif %}><br/>
|
||||||
|
<button class="radius" type='submit'>Register</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
if (window.location.hash == "#frame"){
|
||||||
|
$('.top-bar').hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<h1>Reset Password</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="large-8 large-centered columns">
|
||||||
|
<div data-alert class="alert-box alert radius centered text-center">
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
<a href="#" class="close">×</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if mode %}
|
||||||
|
<form method="POST">
|
||||||
|
<input class="radius" type='password' name='password' placeholder='Password'><br/>
|
||||||
|
<button class="radius" type='submit'>Reset Password</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form method="POST">
|
||||||
|
<input class="radius" type='text' name='email' placeholder='Email'><br/>
|
||||||
|
<button class="radius" type='submit'>Reset Password</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,36 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<p></p>
|
||||||
|
<div id="score-graph"></div>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<table id="scoreboard">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Place</b>
|
||||||
|
</td>
|
||||||
|
<td><b>Team</b>
|
||||||
|
</td>
|
||||||
|
<td><b>Score</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for team in teams %}
|
||||||
|
<tr><td>{{ loop.index }}</td><td><a href="/team/{{ team.teamid }}">{{ team.name }}</a></td><td>{{ team.score }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.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="/static/js/scoreboard.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,52 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.css">
|
||||||
|
<div class="row">
|
||||||
|
<h1>Setup</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="large-8 large-centered columns">
|
||||||
|
<div data-alert class="alert-box alert radius centered text-center">
|
||||||
|
<span>{{ error }}</span>
|
||||||
|
<a href="#" class="close">×</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<strong>Username</strong>
|
||||||
|
<input class="radius" type='text' name='name' placeholder='Name'><br/>
|
||||||
|
|
||||||
|
<strong>Email</strong>
|
||||||
|
<input class="radius" type='text' name='email' placeholder='Email'><br/>
|
||||||
|
|
||||||
|
<strong>Password</strong>
|
||||||
|
<input class="radius" type='password' name='password' placeholder='Password'><br/>
|
||||||
|
<strong>Index Page</strong>
|
||||||
|
<textarea id="pages-editor" name="html">
|
||||||
|
<div class="row">
|
||||||
|
<img class="logo" src="/static/img/logo.png">
|
||||||
|
<h3 class="text-center">Welcome to a cool CTF framwork written by <a href="https://github.com/ColdHeat">Kevin Chung</a> of <a href="https://github.com/isislab">@isislab</a></h3>
|
||||||
|
|
||||||
|
<h4 class="text-center"><a href="/admin">Click here</a> to login and setup your CTF</h4>
|
||||||
|
</div>
|
||||||
|
</textarea><br>
|
||||||
|
<button class="radius" type='submit'>Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/codemirror/4.8.0/codemirror.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var editor = CodeMirror.fromTextArea(document.getElementById("pages-editor"), {
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
mode: "text/html"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h1 id="team-id">{{ team.name }}</h1>
|
||||||
|
|
||||||
|
<div id="keys-pie-graph"></div>
|
||||||
|
<div id="categories-pie-graph"></div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Challenge</b></td>
|
||||||
|
<td><b>Category</b></td>
|
||||||
|
<td><b>Value</b></td>
|
||||||
|
<td><b>Time</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for solve in solves %}
|
||||||
|
<tr><td><a href="/challenges#{{ solve.chal.name }}">{{ solve.chal.name }}</a></td><td>{{ solve.chal.category }}</td><td>{{ solve.chal.value }}</td><td class="solve-time"><script>document.write( moment({{ solve.date|unix_time_millis }}).local().format('MMMM Do, h:mm:ss A'))</script></td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div id="score-graph"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<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="/static/js/team.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/c3/0.4.0/c3.min.css">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<p></p>
|
||||||
|
<table id="teamsboard">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td><b>Team</b></td>
|
||||||
|
<td><b>Website</b></td>
|
||||||
|
<td><b>Affiliation</b></td>
|
||||||
|
<td><b>Country</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for team in teams %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="/team/{{ team.id }}">{{ team.name }}</a></td>
|
||||||
|
<td><a href="{{ team.website }}">{% if team.website %}{{ team.website }}{% endif %}</a></td>
|
||||||
|
<td><span>{% if team.affiliation %}{{ team.affiliation }}{% endif %}</span></td>
|
||||||
|
<td><span>{% if team.country %}{{ team.country }}{% endif %}</span></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue