diff --git a/CTFd/__init__.py b/CTFd/__init__.py index 13edbdd2..52a481aa 100644 --- a/CTFd/__init__.py +++ b/CTFd/__init__.py @@ -3,22 +3,14 @@ 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") + +def create_app(config='CTFd.config'): + app = Flask("CTFd") 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 - ) + app.config.from_object(config) from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking @@ -26,52 +18,27 @@ def create_app(subdomain="", username="", password=""): db.create_all() app.db = db - # app.setup = True global mail 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 + from CTFd.views import views + from CTFd.challenges import challenges + from CTFd.scoreboard import scoreboard + from CTFd.auth import auth + from CTFd.admin import admin + from CTFd.utils import init_utils, init_errors, init_logs + init_utils(app) + init_errors(app) + init_logs(app) + + app.register_blueprint(views) + app.register_blueprint(challenges) + app.register_blueprint(scoreboard) + app.register_blueprint(auth) + app.register_blueprint(admin) 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 diff --git a/CTFd/admin.py b/CTFd/admin.py index 83149366..da42b86a 100644 --- a/CTFd/admin.py +++ b/CTFd/admin.py @@ -1,5 +1,5 @@ -from flask 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, get_config, set_config, get_digitalocean, sendmail, rmdir +from flask import render_template, request, redirect, abort, jsonify, url_for, session, Blueprint +from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, sendmail, rmdir 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 @@ -14,586 +14,599 @@ 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 = Blueprint('admin', __name__) - admin = Teams.query.filter_by(name=request.form['name'], admin=True).first() - if admin and bcrypt_sha256.verify(request.form['password'], admin.password): - try: - session.regenerate() # NO SESSION FIXATION FOR YOU - except: - pass # TODO: Some session objects dont implement regenerate :( - 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(): +@admin.route('/admin', methods=['GET', 'POST']) +def admin_view(): + if request.method == 'POST': + username = request.form.get('name') + password = request.form.get('password') + + admin_user= Teams.query.filter_by(name=request.form['name'], admin=True).first() + if admin_user and bcrypt_sha256.verify(request.form['password'], admin.password): + try: + session.regenerate() # NO SESSION FIXATION FOR YOU + except: + pass # TODO: Some session objects dont implement regenerate :( + session['username'] = admin.name + session['id'] = admin.id + session['admin'] = True + session['nonce'] = sha512(os.urandom(10)) + db.session.close() return redirect('/admin/graphs') - return render_template('admin/login.html') + if is_admin(): + return redirect('/admin/graphs') - @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": - try: - start = int(request.form['start']) - end = int(request.form['end']) - except (ValueError, TypeError): - start = None - end = None - - try: - view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None)) - prevent_registration = bool(request.form.get('prevent_registration', None)) - prevent_name_change = bool(request.form.get('prevent_name_change', None)) - view_after_ctf = bool(request.form.get('view_after_ctf', None)) - except (ValueError, TypeError): - view_challenges_unregistered = None - prevent_registration = None - prevent_name_change = None - view_after_ctf = None - finally: - view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered) - prevent_registration = set_config('prevent_registration', prevent_registration) - prevent_name_change = set_config('prevent_name_change', prevent_name_change) - view_after_ctf = set_config('view_after_ctf', view_after_ctf) - - ctf_name = set_config("ctf_name", request.form.get('ctf_name', None)) - mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None)) - do_api_key = set_config("do_api_key", request.form.get('do_api_key', None)) - max_tries = set_config("max_tries", request.form.get('max_tries', None)) + return render_template('admin/login.html') - db_start = Config.query.filter_by(key='start').first() - db_start.value = start +@admin.route('/admin/graphs') +@admins_only +def admin_graphs(): + return render_template('admin/graphs.html') - db_end = Config.query.filter_by(key='end').first() - db_end.value = end - db.session.add(db_start) - db.session.add(db_end) +@admin.route('/admin/config', methods=['GET', 'POST']) +@admins_only +def admin_config(): + if request.method == "POST": + try: + start = int(request.form['start']) + end = int(request.form['end']) + except (ValueError, TypeError): + start = None + end = None + try: + view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None)) + prevent_registration = bool(request.form.get('prevent_registration', None)) + prevent_name_change = bool(request.form.get('prevent_name_change', None)) + view_after_ctf = bool(request.form.get('view_after_ctf', None)) + except (ValueError, TypeError): + view_challenges_unregistered = None + prevent_registration = None + prevent_name_change = None + view_after_ctf = None + finally: + view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered) + prevent_registration = set_config('prevent_registration', prevent_registration) + prevent_name_change = set_config('prevent_name_change', prevent_name_change) + view_after_ctf = set_config('view_after_ctf', view_after_ctf) + + ctf_name = set_config("ctf_name", request.form.get('ctf_name', None)) + mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None)) + max_tries = set_config("max_tries", request.form.get('max_tries', None)) + + + 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') + + ctf_name = get_config('ctf_name') + if not ctf_name: + set_config('ctf_name', None) + + mg_api_key = get_config('mg_api_key') + if not mg_api_key: + set_config('mg_api_key', None) + + max_tries = get_config('max_tries') + if not max_tries: + set_config('max_tries', 0) + max_tries = 0 + + view_after_ctf = get_config('view_after_ctf') == '1' + if not view_after_ctf: + set_config('view_after_ctf', 0) + view_after_ctf = 0 + + start = get_config('start') + if not start: + set_config('start', None) + + end = get_config('end') + if not end: + set_config('end', None) + + view_challenges_unregistered = get_config('view_challenges_unregistered') == '1' + if not view_challenges_unregistered: + set_config('view_challenges_unregistered', None) + + prevent_registration = get_config('prevent_registration') == '1' + if not prevent_registration: + set_config('prevent_registration', None) + + prevent_name_change = get_config('prevent_name_change') == '1' + if not prevent_name_change: + set_config('prevent_name_change', None) + + db.session.commit() + db.session.close() + + return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end, + max_tries=max_tries, + view_challenges_unregistered=view_challenges_unregistered, + prevent_registration=prevent_registration, mg_api_key=mg_api_key, + prevent_name_change=prevent_name_change, + view_after_ctf=view_after_ctf) + + +@admin.route('/admin/css', methods=['GET', 'POST']) +@admins_only +def admin_css(): + if request.method == 'POST': + css = request.form['css'] + css = set_config('css', css) + print css + return "1" + return "0" + +@admin.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST']) +@admin.route('/admin/pages/', methods=['GET', 'POST']) +@admins_only +def admin_pages(route): + if request.method == 'GET' and request.args.get('mode') == 'create': + return render_template('admin/editor.html') + 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/config') + return redirect('/admin/pages') + page = Pages(route, html) + db.session.add(page) + db.session.commit() + return redirect('/admin/pages') + pages = Pages.query.all() + return render_template('admin/pages.html', routes=pages, css=get_config('css')) - ctf_name = get_config('ctf_name') - if not ctf_name: - set_config('do_api_key', None) - mg_api_key = get_config('do_api_key') - if not mg_api_key: - set_config('do_api_key', None) +@admin.route('/admin/page//delete', methods=['POST']) +@admins_only +def delete_page(pageroute): + page = Pages.query.filter_by(route=pageroute).first() + db.session.delete(page) + db.session.commit() + return '1' - do_api_key = get_config('do_api_key') - if not do_api_key: - set_config('do_api_key', None) - max_tries = get_config('max_tries') - if not max_tries: - set_config('max_tries', 0) - max_tries = 0 +@admin.route('/admin/chals', methods=['POST', 'GET']) +@admins_only +def admin_chals(): + 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]}) - view_after_ctf = get_config('view_after_ctf') == '1' - if not view_after_ctf: - set_config('view_after_ctf', 0) - view_after_ctf = 0 + db.session.close() + return jsonify(json) + else: + return render_template('admin/chals.html') - start = get_config('start') - if not start: - set_config('start', None) - end = get_config('end') - if not end: - set_config('end', None) +@admin.route('/admin/keys/', 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) - view_challenges_unregistered = get_config('view_challenges_unregistered') == '1' - if not view_challenges_unregistered: - set_config('view_challenges_unregistered', None) - - prevent_registration = get_config('prevent_registration') == '1' - if not prevent_registration: - set_config('prevent_registration', None) - - prevent_name_change = get_config('prevent_name_change') == '1' - if not prevent_name_change: - set_config('prevent_name_change', None) + 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 render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end, - max_tries=max_tries, - view_challenges_unregistered=view_challenges_unregistered, - prevent_registration=prevent_registration, do_api_key=do_api_key, mg_api_key=mg_api_key, - prevent_name_change=prevent_name_change, - view_after_ctf=view_after_ctf) - - @app.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST']) - @app.route('/admin/pages/', 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/page//delete', methods=['POST']) - @admins_only - def delete_page(pageroute): - page = Pages.query.filter_by(route=pageroute).first() - db.session.delete(page) - db.session.commit() return '1' - @app.route('/admin/hosts', methods=['GET']) - @admins_only - def admin_hosts(): - m = get_digitalocean() - errors = [] - if not m: - errors.append("Your Digital Ocean API key is not set") - return render_template('admin/hosts.html', errors=errors) - hosts = m.get_all_droplets() - slugs = m.get_all_sizes() - images = m.get_all_images() - regions = m.get_all_regions() +@admin.route('/admin/tags/', 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) - return render_template('admin/hosts.html', hosts=hosts, slugs=slugs, images=images, regions=regions) + 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/chals', methods=['POST', 'GET']) - @admins_only - def admin_chals(): - 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') +@admin.route('/admin/tags//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/keys/', 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/', 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//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) +@admin.route('/admin/files/', 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) - @app.route('/admin/files/', 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[]') + if len(filename) <= 0: + continue + + md5hash = hashlib.md5(os.urandom(64)).hexdigest() - for f in files: - filename = secure_filename(f.filename) + # BUG NEEDS TO GO TO S3 + base = os.path.dirname(os.path.dirname(__file__)) + ## mod_wsgi does some sad things with cwd so the upload directory needs to be shifted a bit + if not os.path.exists(os.path.join(base, app.config['UPLOAD_FOLDER'], md5hash)): + os.makedirs(os.path.join(base, app.config['UPLOAD_FOLDER'], md5hash)) - if len(filename) <= 0: - continue - - md5hash = hashlib.md5(os.urandom(64)).hexdigest() + f.save(os.path.join(base, app.config['UPLOAD_FOLDER'], md5hash, filename)) - # BUG NEEDS TO GO TO S3 - base = os.path.dirname(os.path.dirname(__file__)) - ## mod_wsgi does some sad things with cwd so the upload directory needs to be shifted a bit - if not os.path.exists(os.path.join(base, app.config['UPLOAD_FOLDER'], md5hash)): - os.makedirs(os.path.join(base, app.config['UPLOAD_FOLDER'], md5hash)) + ## This needs to be relative to CTFd so doesn't nee base. + db_f = Files(chalid, os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename)) + db.session.add(db_f) - f.save(os.path.join(base, app.config['UPLOAD_FOLDER'], md5hash, filename)) - - ## This needs to be relative to CTFd so doesn't nee base. - 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/', 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() - score = user.score() - place = user.place() - - if request.method == 'GET': - return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place) - elif request.method == 'POST': - admin = request.form.get('admin', "false") - admin = 1 if admin == "true" else 0 - if admin: - user.admin = 1 - db.session.commit() - return jsonify({'data': ['success']}) - - name = request.form.get('name', None) - password = request.form.get('password', None) - email = request.form.get('email', None) - website = request.form.get('website', None) - affiliation = request.form.get('affiliation', None) - country = request.form.get('country', None) - - errors = [] - - name_used = Teams.query.filter(Teams.name == name).first() - if name_used and int(name_used.id) != int(teamid): - errors.append('That name is taken') - - email_used = Teams.query.filter(Teams.email == email).first() - if email_used and int(email_used.id) != int(teamid): - errors.append('That email is taken') - - if errors: - db.session.close() - return jsonify({'data':errors}) - else: - user.name = name - user.email = email - if password: - user.password = bcrypt_sha256.encrypt(password) - user.website = website - user.affiliation = affiliation - user.country = country - db.session.commit() - db.session.close() - return jsonify({'data':['success']}) - - @app.route('/admin/team//mail', methods=['POST']) - @admins_only - def email_user(teamid): - message = request.form.get('msg', None) - team = Teams.query.filter(Teams.id == teamid).first() - if message and team: - if sendmail(team.email, message): - return "1" - return "0" - - @app.route('/admin/team//ban', methods=['POST']) - @admins_only - def ban(teamid): - user = Teams.query.filter_by(id=teamid).first() - user.banned = 1 - db.session.commit() - return redirect('/admin/scoreboard') - - @app.route('/admin/team//unban', methods=['POST']) - @admins_only - def unban(teamid): - user = Teams.query.filter_by(id=teamid).first() - user.banned = None - db.session.commit() - return redirect('/admin/scoreboard') - - @app.route('/admin/team//delete', methods=['POST']) - @admins_only - def delete_team(teamid): - user = Teams.query.filter_by(id=teamid).first() - db.session.delete(user) - db.session.commit() - return '1' - - - @app.route('/admin/graphs/') - @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, Teams.banned, score).join(Teams).join(Challenges).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/', 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/solves///delete', methods=['POST']) - @admins_only - def delete_solve(teamid, chalid): - solve = Solves.query.filter_by(teamid=teamid, chalid=chalid).first() - db.session.delete(solve) - db.session.commit() - return '1' - - @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] - 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 = Challenges.query.add_columns(db.func.count(Solves.chalid).label('solves')).outerjoin(Solves).group_by(Challenges.id).order_by('solves ASC').first() - - db.session.close() - - return render_template('admin/statistics.html', team_count=teams_registered, - 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/wrong_keys/', methods=['GET']) - @admins_only - def admin_wrong_key(page='1'): - page = abs(int(page)) - results_per_page = 50 - page_start = results_per_page * ( page - 1 ) - page_end = results_per_page * ( page - 1 ) + results_per_page - - wrong_keys = WrongKeys.query.add_columns(WrongKeys.flag, WrongKeys.team, WrongKeys.date,\ - Challenges.name.label('chal_name'), Teams.name.label('team_name')).\ - join(Challenges).join(Teams).order_by('team_name ASC').slice(page_start, page_end).all() - - wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0] - pages = int(wrong_count / results_per_page) + (wrong_count % results_per_page > 0) - - return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages) - - @app.route('/admin/correct_keys/', methods=['GET']) - @admins_only - def admin_correct_key(page='1'): - page = abs(int(page)) - results_per_page = 50 - page_start = results_per_page * (page - 1) - page_end = results_per_page * (page - 1) + results_per_page - - solves = Solves.query.add_columns(Solves.chalid, Solves.teamid, Solves.date, Solves.flag, \ - Challenges.name.label('chal_name'), Teams.name.label('team_name')).\ - join(Challenges).join(Teams).order_by('team_name ASC').slice(page_start, page_end).all() - - solve_count = db.session.query(db.func.count(Solves.id)).first()[0] - pages = int(solve_count / results_per_page) + (solve_count % results_per_page > 0) - - return render_template('admin/correct_keys.html', solves=solves, pages=pages) - - @app.route('/admin/fails/', 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(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash)): - os.makedirs(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash)) - - f.save(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename)) - db_f = Files(chal.id, os.path.join(os.path.normpath(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/delete', methods=['POST']) - def admin_delete_chal(): - challenge = Challenges.query.filter_by(id=request.form['id']).first() - if challenge: - WrongKeys.query.filter_by(chal=challenge.id).delete() - Solves.query.filter_by(chalid=challenge.id).delete() - Keys.query.filter_by(chal=challenge.id).delete() - files = Files.query.filter_by(chal=challenge.id).all() - Files.query.filter_by(chal=challenge.id).delete() - for file in files: - folder = os.path.dirname(file.location) - rmdir(folder) - Tags.query.filter_by(chal=challenge.id).delete() - Challenges.query.filter_by(id=challenge.id).delete() db.session.commit() db.session.close() - return '1' + 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) + +@admin.route('/admin/teams') +@admins_only +def admin_teams(): + teams = Teams.query.all() + return render_template('admin/teams.html', teams=teams) + + +@admin.route('/admin/team/', 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() + score = user.score() + place = user.place() + + if request.method == 'GET': + return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place) + elif request.method == 'POST': + admin_user = request.form.get('admin', "false") + admin_user = 1 if admin_user == "true" else 0 + if admin: + user.admin = 1 + db.session.commit() + return jsonify({'data': ['success']}) + + name = request.form.get('name', None) + password = request.form.get('password', None) + email = request.form.get('email', None) + website = request.form.get('website', None) + affiliation = request.form.get('affiliation', None) + country = request.form.get('country', None) + + errors = [] + + name_used = Teams.query.filter(Teams.name == name).first() + if name_used and int(name_used.id) != int(teamid): + errors.append('That name is taken') + + email_used = Teams.query.filter(Teams.email == email).first() + if email_used and int(email_used.id) != int(teamid): + errors.append('That email is taken') + + if errors: + db.session.close() + return jsonify({'data':errors}) + else: + user.name = name + user.email = email + if password: + user.password = bcrypt_sha256.encrypt(password) + user.website = website + user.affiliation = affiliation + user.country = country + db.session.commit() + db.session.close() + return jsonify({'data':['success']}) + + +@admin.route('/admin/team//mail', methods=['POST']) +@admins_only +def email_user(teamid): + message = request.form.get('msg', None) + team = Teams.query.filter(Teams.id == teamid).first() + if message and team: + if sendmail(team.email, message): + return "1" + return "0" + + +@admin.route('/admin/team//ban', methods=['POST']) +@admins_only +def ban(teamid): + user = Teams.query.filter_by(id=teamid).first() + user.banned = 1 + db.session.commit() + return redirect('/admin/scoreboard') + + +@admin.route('/admin/team//unban', methods=['POST']) +@admins_only +def unban(teamid): + user = Teams.query.filter_by(id=teamid).first() + user.banned = None + db.session.commit() + return redirect('/admin/scoreboard') + + +@admin.route('/admin/team//delete', methods=['POST']) +@admins_only +def delete_team(teamid): + user = Teams.query.filter_by(id=teamid).first() + db.session.delete(user) + db.session.commit() + return '1' + + +@admin.route('/admin/graphs/') +@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) + + +@admin.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, Teams.banned, score).join(Teams).join(Challenges).group_by(Solves.teamid).order_by(score.desc(), quickest) + db.session.close() + return render_template('admin/scoreboard.html', teams=teams) + + +@admin.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) + + +@admin.route('/admin/solves/', 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) + + +@admin.route('/admin/solves///delete', methods=['POST']) +@admins_only +def delete_solve(teamid, chalid): + solve = Solves.query.filter_by(teamid=teamid, chalid=chalid).first() + db.session.delete(solve) + db.session.commit() + return '1' + + +@admin.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] + 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 = Challenges.query.add_columns(db.func.count(Solves.chalid).label('solves')).outerjoin(Solves).group_by(Challenges.id).order_by('solves ASC').first() + + db.session.close() + + return render_template('admin/statistics.html', team_count=teams_registered, + wrong_count=wrong_count, + solve_count=solve_count, + challenge_count=challenge_count, + most_solved=most_solved_chal, + least_solved=least_solved_chal + ) + + +@admin.route('/admin/wrong_keys/', methods=['GET']) +@admins_only +def admin_wrong_key(page='1'): + page = abs(int(page)) + results_per_page = 50 + page_start = results_per_page * ( page - 1 ) + page_end = results_per_page * ( page - 1 ) + results_per_page + + wrong_keys = WrongKeys.query.add_columns(WrongKeys.flag, WrongKeys.team, WrongKeys.date,\ + Challenges.name.label('chal_name'), Teams.name.label('team_name')).\ + join(Challenges).join(Teams).order_by('team_name ASC').slice(page_start, page_end).all() + + wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0] + pages = int(wrong_count / results_per_page) + (wrong_count % results_per_page > 0) + + return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages) + + +@admin.route('/admin/correct_keys/', methods=['GET']) +@admins_only +def admin_correct_key(page='1'): + page = abs(int(page)) + results_per_page = 50 + page_start = results_per_page * (page - 1) + page_end = results_per_page * (page - 1) + results_per_page + + solves = Solves.query.add_columns(Solves.chalid, Solves.teamid, Solves.date, Solves.flag, \ + Challenges.name.label('chal_name'), Teams.name.label('team_name')).\ + join(Challenges).join(Teams).order_by('team_name ASC').slice(page_start, page_end).all() + + solve_count = db.session.query(db.func.count(Solves.id)).first()[0] + pages = int(solve_count / results_per_page) + (solve_count % results_per_page > 0) + + return render_template('admin/correct_keys.html', solves=solves, pages=pages) + + +@admin.route('/admin/fails/', 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) + + +@admin.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(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash)): + os.makedirs(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash)) + + f.save(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename)) + db_f = Files(chal.id, os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename)) + db.session.add(db_f) + + db.session.commit() + db.session.close() + return redirect('/admin/chals') + + +@admin.route('/admin/chal/delete', methods=['POST']) +def admin_delete_chal(): + challenge = Challenges.query.filter_by(id=request.form['id']).first() + if challenge: + WrongKeys.query.filter_by(chal=challenge.id).delete() + Solves.query.filter_by(chalid=challenge.id).delete() + Keys.query.filter_by(chal=challenge.id).delete() + files = Files.query.filter_by(chal=challenge.id).all() + Files.query.filter_by(chal=challenge.id).delete() + for file in files: + folder = os.path.dirname(file.location) + rmdir(folder) + Tags.query.filter_by(chal=challenge.id).delete() + Challenges.query.filter_by(id=challenge.id).delete() db.session.commit() db.session.close() - return redirect('/admin/chals') + return '1' + + +@admin.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') diff --git a/CTFd/auth.py b/CTFd/auth.py index 29cda280..f09afb0d 100644 --- a/CTFd/auth.py +++ b/CTFd/auth.py @@ -1,4 +1,4 @@ -from flask import render_template, request, redirect, abort, jsonify, url_for, session +from flask import render_template, request, redirect, abort, jsonify, url_for, session, Blueprint from CTFd.utils import sha512, is_safe_url, authed, mailserver, sendmail, can_register from CTFd.models import db, Teams @@ -11,131 +11,129 @@ import time import re import os -def init_auth(app): - @app.context_processor - def inject_user(): - if authed(): - return dict(session) - return dict() +auth = Blueprint('auth', __name__) - @app.route('/reset_password', methods=['POST', 'GET']) - @app.route('/reset_password/', 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 = bcrypt_sha256.encrypt(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']) +@auth.route('/reset_password', methods=['POST', 'GET']) +@auth.route('/reset_password/', 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']) - token = s.dumps(team.name) - text = """ + 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 = bcrypt_sha256.encrypt(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')) +""".format(app.config['HOST'], token.encode('base64')) - sendmail(email, text) + sendmail(email, text) - return render_template('reset_password.html', errors=['Check your email']) - return render_template('reset_password.html') + 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 not can_register(): - return redirect('/login') - if request.method == 'POST': - errors = [] - name = request.form['name'] - email = request.form['email'] - password = request.form['password'] - name_len = len(name) == 0 - names = Teams.query.add_columns('name', 'id').filter_by(name=name).first() - emails = Teams.query.add_columns('email', 'id').filter_by(email=email).first() - pass_short = len(password) == 0 - pass_long = len(password) > 128 - valid_email = re.match("[^@]+@[^@]+\.[^@]+", request.form['email']) +@auth.route('/register', methods=['POST', 'GET']) +def register(): + if not can_register(): + return redirect('/login') + if request.method == 'POST': + errors = [] + name = request.form['name'] + email = request.form['email'] + password = request.form['password'] - 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_short: - errors.append('Pick a longer password') - if pass_long: - errors.append('Pick a shorter password') - if name_len: - errors.append('Pick a longer team name') + name_len = len(name) == 0 + names = Teams.query.add_columns('name', 'id').filter_by(name=name).first() + emails = Teams.query.add_columns('email', 'id').filter_by(email=email).first() + pass_short = len(password) == 0 + pass_long = len(password) > 128 + valid_email = re.match("[^@]+@[^@]+\.[^@]+", request.form['email']) - if len(errors) > 0: - return render_template('register.html', errors=errors, name=request.form['name'], email=request.form['email'], password=request.form['password']) - else: - with app.app_context(): - team = Teams(name, email, password) - db.session.add(team) - db.session.commit() - if mailserver(): - sendmail(request.form['email'], "You've successfully registered for the CTF") - + 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_short: + errors.append('Pick a longer password') + if pass_long: + errors.append('Pick a shorter password') + if name_len: + errors.append('Pick a longer team name') + + if len(errors) > 0: + return render_template('register.html', errors=errors, name=request.form['name'], email=request.form['email'], password=request.form['password']) + else: + with app.app_context(): + team = Teams(name, email, password) + db.session.add(team) + db.session.commit() + if mailserver(): + sendmail(request.form['email'], "You've successfully registered for the CTF") + + db.session.close() + + logger = logging.getLogger('regs') + logger.warn("[{0}] {1} registered with {2}".format(time.strftime("%m/%d/%Y %X"), request.form['name'].encode('utf-8'), request.form['email'].encode('utf-8'))) + return redirect('/login') + else: + return render_template('register.html') + + +@auth.route('/login', methods=['POST', 'GET']) +def login(): + if request.method == 'POST': + errors = [] + name = request.form['name'] + team = Teams.query.filter_by(name=name).first() + if team and bcrypt_sha256.verify(request.form['password'], team.password): + try: + session.regenerate() # NO SESSION FIXATION FOR YOU + except: + pass # TODO: Some session objects don't implement regenerate :( + session['username'] = team.name + session['id'] = team.id + session['admin'] = team.admin + session['nonce'] = sha512(os.urandom(10)) db.session.close() - logger = logging.getLogger('regs') - logger.warn("[{0}] {1} registered with {2}".format(time.strftime("%m/%d/%Y %X"), request.form['name'].encode('utf-8'), request.form['email'].encode('utf-8'))) - return redirect('/login') - else: - return render_template('register.html') - - @app.route('/login', methods=['POST', 'GET']) - def login(): - if request.method == 'POST': - errors = [] - name = request.form['name'] - team = Teams.query.filter_by(name=name).first() - if team and bcrypt_sha256.verify(request.form['password'], team.password): - try: - session.regenerate() # NO SESSION FIXATION FOR YOU - except: - pass # TODO: Some session objects don't implement regenerate :( - 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'].encode('utf-8'))) - - # 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) + logger = logging.getLogger('logins') + logger.warn("[{0}] {1} logged in".format(time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'))) + + # 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') + 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('/') +@auth.route('/logout') +def logout(): + if authed(): + session.clear() + return redirect('/') diff --git a/CTFd/challenges.py b/CTFd/challenges.py index 54db8375..aeae42d3 100644 --- a/CTFd/challenges.py +++ b/CTFd/challenges.py @@ -1,4 +1,4 @@ -from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session +from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session, Blueprint from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, can_view_challenges, is_admin, get_config from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys @@ -7,143 +7,152 @@ import time import re import logging -def init_challenges(app): - @app.route('/challenges', methods=['GET']) - def challenges(): - if not is_admin(): - if not ctftime(): - if view_after_ctf(): - pass - else: - return redirect('/') - if can_view_challenges(): - return render_template('chals.html', ctftime=ctftime()) - else: - return redirect(url_for('login', next="challenges")) +challenges = Blueprint('challenges', __name__) - @app.route('/chals', methods=['GET']) - def chals(): - if not is_admin(): - if not ctftime(): - if view_after_ctf(): - pass - else: - return redirect('/') - if can_view_challenges(): - 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 can_view_challenges(): - 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/') - def solves(teamid=None): - if teamid is None: - if authed(): - solves = Solves.query.filter_by(teamid=session['id']).all() - else: - abort(401) - else: - solves = Solves.query.filter_by(teamid=teamid).all() - db.session.close() - json = {'solves':[]} - for x in solves: - json['solves'].append({ '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('/maxattempts') - def attempts(): - chals = Challenges.query.add_columns('id').all() - json = {'maxattempts':[]} - for chal, chalid in chals: - fails = WrongKeys.query.filter_by(team=session['id'], chal=chalid).count() - if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0: - json['maxattempts'].append({'chalid':chalid}) - return jsonify(json) - - @app.route('/fails/', 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//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/', methods=['POST']) - def chal(chalid): +@challenges.route('/challenges', methods=['GET']) +def challenges_view(): + if not is_admin(): if not ctftime(): - return redirect('/challenges') - if authed(): - fails = WrongKeys.query.filter_by(team=session['id'],chal=chalid).count() - logger = logging.getLogger('keys') - data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id'])) - print "[{0}] {1} submitted {2} with kpm {3}".format(*data) - if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0: - return "4" #too many tries on this challenge - 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, flag=key) - db.session.add(solve) - db.session.commit() - db.session.close() - logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data)) - return "1" # key was correct - elif x.key_type == 1: #regex - res = re.match(str(x), key, re.IGNORECASE) - if res and res.group() == key: - solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key) - db.session.add(solve) - db.session.commit() - db.session.close() - logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data)) - return "1" # key was correct - - 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 + if view_after_ctf(): + pass else: - logger.info("{0} submitted {1} with kpm {2} [ALREADY SOLVED]".format(*data)) - return "2" # challenge was already solved + return redirect('/') + if can_view_challenges(): + return render_template('chals.html', ctftime=ctftime()) + else: + return redirect(url_for('login', next="challenges")) + + +@challenges.route('/chals', methods=['GET']) +def chals(): + if not is_admin(): + if not ctftime(): + if view_after_ctf(): + pass + else: + return redirect('/') + if can_view_challenges(): + 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') + + +@challenges.route('/chals/solves') +def chals_per_solves(): + if can_view_challenges(): + 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")) + + +@challenges.route('/solves') +@challenges.route('/solves/') +def solves(teamid=None): + if teamid is None: + if authed(): + solves = Solves.query.filter_by(teamid=session['id']).all() else: - return "-1" + abort(401) + else: + solves = Solves.query.filter_by(teamid=teamid).all() + db.session.close() + json = {'solves':[]} + for x in solves: + json['solves'].append({ '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) + + +@challenges.route('/maxattempts') +def attempts(): + chals = Challenges.query.add_columns('id').all() + json = {'maxattempts':[]} + for chal, chalid in chals: + fails = WrongKeys.query.filter_by(team=session['id'], chal=chalid).count() + if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0: + json['maxattempts'].append({'chalid':chalid}) + return jsonify(json) + + +@challenges.route('/fails/', 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) + + +@challenges.route('/chal//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) + + +@challenges.route('/chal/', methods=['POST']) +def chal(chalid): + if not ctftime(): + return redirect('/challenges') + if authed(): + fails = WrongKeys.query.filter_by(team=session['id'],chal=chalid).count() + logger = logging.getLogger('keys') + data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id'])) + print "[{0}] {1} submitted {2} with kpm {3}".format(*data) + if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0: + return "4" #too many tries on this challenge + 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, flag=key) + db.session.add(solve) + db.session.commit() + db.session.close() + logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data)) + return "1" # key was correct + elif x.key_type == 1: #regex + res = re.match(str(x), key, re.IGNORECASE) + if res and res.group() == key: + solve = Solves(chalid=chalid, teamid=session['id'], ip=request.remote_addr, flag=key) + db.session.add(solve) + db.session.commit() + db.session.close() + logger.info("[{0}] {1} submitted {2} with kpm {3} [CORRECT]".format(*data)) + return "1" # key was correct + + 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" diff --git a/CTFd/errors.py b/CTFd/errors.py deleted file mode 100644 index f33a6da6..00000000 --- a/CTFd/errors.py +++ /dev/null @@ -1,18 +0,0 @@ -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 general_error(error): - return render_template('errors/500.html'), 500 - - @app.errorhandler(502) - def gateway_error(error): - return render_template('errors/502.html'), 502 diff --git a/CTFd/models.py b/CTFd/models.py index adf717ae..ab485c1f 100644 --- a/CTFd/models.py +++ b/CTFd/models.py @@ -1,4 +1,3 @@ -#from CTFd import db from flask.ext.sqlalchemy import SQLAlchemy from socket import inet_aton, inet_ntoa @@ -79,7 +78,6 @@ class Keys(db.Model): key_type = db.Column(db.Integer) flag = db.Column(db.Text) - def __init__(self, chal, flag, key_type): self.chal = chal self.flag = flag @@ -181,6 +179,7 @@ class Tracking(db.Model): def __repr__(self): return '' % self.team + class Config(db.Model): id = db.Column(db.Integer, primary_key=True) key = db.Column(db.Text) diff --git a/CTFd/scoreboard.py b/CTFd/scoreboard.py index 384f560b..5246805b 100644 --- a/CTFd/scoreboard.py +++ b/CTFd/scoreboard.py @@ -1,46 +1,50 @@ -from flask import current_app as app, session, render_template, jsonify +from flask import current_app as app, session, render_template, jsonify, Blueprint 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) - db.session.close() - return render_template('scoreboard.html', teams=teams) +scoreboard = Blueprint('scoreboard', __name__) - @app.route('/scores') - def 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('/top/') - def topteams(count): - try: - count = int(count) - except: - count = 10 - if count > 20 or count < 0: - count = 10 +@scoreboard.route('/scoreboard') +def scoreboard_view(): + 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('scoreboard.html', teams=teams) - json = {'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).limit(count) +@scoreboard.route('/scores') +def 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) - 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) +@scoreboard.route('/top/') +def topteams(count): + try: + count = int(count) + except: + count = 10 + if count > 20 or count < 0: + count = 10 + + json = {'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).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) diff --git a/static/admin/css/style.css b/CTFd/static/admin/css/style.css similarity index 100% rename from static/admin/css/style.css rename to CTFd/static/admin/css/style.css diff --git a/static/admin/js/chalboard.js b/CTFd/static/admin/js/chalboard.js similarity index 100% rename from static/admin/js/chalboard.js rename to CTFd/static/admin/js/chalboard.js diff --git a/static/admin/js/team.js b/CTFd/static/admin/js/team.js similarity index 100% rename from static/admin/js/team.js rename to CTFd/static/admin/js/team.js diff --git a/static/css/style.css b/CTFd/static/css/style.css similarity index 100% rename from static/css/style.css rename to CTFd/static/css/style.css diff --git a/static/img/ctfd.ai b/CTFd/static/img/ctfd.ai similarity index 100% rename from static/img/ctfd.ai rename to CTFd/static/img/ctfd.ai diff --git a/static/img/ctfd.svg b/CTFd/static/img/ctfd.svg similarity index 100% rename from static/img/ctfd.svg rename to CTFd/static/img/ctfd.svg diff --git a/static/img/favicon.ico b/CTFd/static/img/favicon.ico similarity index 100% rename from static/img/favicon.ico rename to CTFd/static/img/favicon.ico diff --git a/static/img/logo.png b/CTFd/static/img/logo.png similarity index 100% rename from static/img/logo.png rename to CTFd/static/img/logo.png diff --git a/static/img/logo_old.png b/CTFd/static/img/logo_old.png similarity index 100% rename from static/img/logo_old.png rename to CTFd/static/img/logo_old.png diff --git a/static/js/chalboard.js b/CTFd/static/js/chalboard.js similarity index 100% rename from static/js/chalboard.js rename to CTFd/static/js/chalboard.js diff --git a/static/js/scoreboard.js b/CTFd/static/js/scoreboard.js similarity index 100% rename from static/js/scoreboard.js rename to CTFd/static/js/scoreboard.js diff --git a/static/js/team.js b/CTFd/static/js/team.js similarity index 100% rename from static/js/team.js rename to CTFd/static/js/team.js diff --git a/static/uploads/index.html b/CTFd/static/uploads/index.html similarity index 100% rename from static/uploads/index.html rename to CTFd/static/uploads/index.html diff --git a/templates/admin/base.html b/CTFd/templates/admin/base.html similarity index 72% rename from templates/admin/base.html rename to CTFd/templates/admin/base.html index 7298e598..25d9560c 100644 --- a/templates/admin/base.html +++ b/CTFd/templates/admin/base.html @@ -9,7 +9,7 @@ - + @@ -32,22 +32,13 @@ diff --git a/templates/admin/chals.html b/CTFd/templates/admin/chals.html similarity index 100% rename from templates/admin/chals.html rename to CTFd/templates/admin/chals.html diff --git a/templates/admin/config.html b/CTFd/templates/admin/config.html similarity index 90% rename from templates/admin/config.html rename to CTFd/templates/admin/config.html index 97db6680..8faae385 100644 --- a/templates/admin/config.html +++ b/CTFd/templates/admin/config.html @@ -22,11 +22,6 @@ -
- - -
-
diff --git a/templates/admin/correct_keys.html b/CTFd/templates/admin/correct_keys.html similarity index 100% rename from templates/admin/correct_keys.html rename to CTFd/templates/admin/correct_keys.html diff --git a/templates/admin/editor.html b/CTFd/templates/admin/editor.html similarity index 100% rename from templates/admin/editor.html rename to CTFd/templates/admin/editor.html diff --git a/templates/admin/graphs.html b/CTFd/templates/admin/graphs.html similarity index 100% rename from templates/admin/graphs.html rename to CTFd/templates/admin/graphs.html diff --git a/templates/admin/hosts.html b/CTFd/templates/admin/hosts.html similarity index 100% rename from templates/admin/hosts.html rename to CTFd/templates/admin/hosts.html diff --git a/templates/admin/login.html b/CTFd/templates/admin/login.html similarity index 100% rename from templates/admin/login.html rename to CTFd/templates/admin/login.html diff --git a/templates/admin/pages.html b/CTFd/templates/admin/pages.html similarity index 52% rename from templates/admin/pages.html rename to CTFd/templates/admin/pages.html index 1f04675c..bdd22943 100644 --- a/templates/admin/pages.html +++ b/CTFd/templates/admin/pages.html @@ -16,30 +16,38 @@ ×
- - - - - - - - - {% for route in routes %} - - - - - {% endfor %} - -
RouteSettings
{{ route.route }}
-
- - -
+ +
+

CSS editor

+
{{ css }}
+
+ +
+

HTML Pages

+ + + + + + + + + {% for route in routes %} + + + + + {% endfor %} + +
RouteSettings
{{ route.route }}
+
{% endblock %} {% block scripts %} + + + {% endblock %} diff --git a/templates/admin/scoreboard.html b/CTFd/templates/admin/scoreboard.html similarity index 100% rename from templates/admin/scoreboard.html rename to CTFd/templates/admin/scoreboard.html diff --git a/templates/admin/static/css/style.css b/CTFd/templates/admin/static/css/style.css similarity index 100% rename from templates/admin/static/css/style.css rename to CTFd/templates/admin/static/css/style.css diff --git a/templates/admin/statistics.html b/CTFd/templates/admin/statistics.html similarity index 100% rename from templates/admin/statistics.html rename to CTFd/templates/admin/statistics.html diff --git a/templates/admin/team.html b/CTFd/templates/admin/team.html similarity index 100% rename from templates/admin/team.html rename to CTFd/templates/admin/team.html diff --git a/templates/admin/teams.html b/CTFd/templates/admin/teams.html similarity index 100% rename from templates/admin/teams.html rename to CTFd/templates/admin/teams.html diff --git a/templates/admin/wrong_keys.html b/CTFd/templates/admin/wrong_keys.html similarity index 100% rename from templates/admin/wrong_keys.html rename to CTFd/templates/admin/wrong_keys.html diff --git a/templates/base.html b/CTFd/templates/base.html similarity index 73% rename from templates/base.html rename to CTFd/templates/base.html index d5c9f092..776c53d8 100644 --- a/templates/base.html +++ b/CTFd/templates/base.html @@ -11,6 +11,7 @@ + @@ -40,29 +41,9 @@ {%else %}
  • {% if can_register() %} -
  • - Register - -
  • +
  • Register
  • {% endif %} -
  • - Login - -
  • +
  • Login
  • {% endif %} diff --git a/templates/chals.html b/CTFd/templates/chals.html similarity index 100% rename from templates/chals.html rename to CTFd/templates/chals.html diff --git a/templates/ctf.html b/CTFd/templates/ctf.html similarity index 100% rename from templates/ctf.html rename to CTFd/templates/ctf.html diff --git a/templates/errors/403.html b/CTFd/templates/errors/403.html similarity index 100% rename from templates/errors/403.html rename to CTFd/templates/errors/403.html diff --git a/templates/errors/404.html b/CTFd/templates/errors/404.html similarity index 100% rename from templates/errors/404.html rename to CTFd/templates/errors/404.html diff --git a/templates/errors/500.html b/CTFd/templates/errors/500.html similarity index 100% rename from templates/errors/500.html rename to CTFd/templates/errors/500.html diff --git a/templates/errors/502.html b/CTFd/templates/errors/502.html similarity index 100% rename from templates/errors/502.html rename to CTFd/templates/errors/502.html diff --git a/templates/login.html b/CTFd/templates/login.html similarity index 93% rename from templates/login.html rename to CTFd/templates/login.html index 320f6488..d762424d 100644 --- a/templates/login.html +++ b/CTFd/templates/login.html @@ -19,6 +19,7 @@

    Forgot your password?

    + diff --git a/templates/profile.html b/CTFd/templates/profile.html similarity index 100% rename from templates/profile.html rename to CTFd/templates/profile.html diff --git a/templates/register.html b/CTFd/templates/register.html similarity index 100% rename from templates/register.html rename to CTFd/templates/register.html diff --git a/templates/reset_password.html b/CTFd/templates/reset_password.html similarity index 100% rename from templates/reset_password.html rename to CTFd/templates/reset_password.html diff --git a/templates/scoreboard.html b/CTFd/templates/scoreboard.html similarity index 100% rename from templates/scoreboard.html rename to CTFd/templates/scoreboard.html diff --git a/templates/setup.html b/CTFd/templates/setup.html similarity index 93% rename from templates/setup.html rename to CTFd/templates/setup.html index 0c272d2a..29650360 100644 --- a/templates/setup.html +++ b/CTFd/templates/setup.html @@ -37,6 +37,7 @@

    Click here to login and setup your CTF


    + {# This nonce is implemented specially in the route itself #} diff --git a/templates/team.html b/CTFd/templates/team.html similarity index 100% rename from templates/team.html rename to CTFd/templates/team.html diff --git a/templates/teams.html b/CTFd/templates/teams.html similarity index 100% rename from templates/teams.html rename to CTFd/templates/teams.html diff --git a/CTFd/utils.py b/CTFd/utils.py index dbe42c79..3cadc1e8 100644 --- a/CTFd/utils.py +++ b/CTFd/utils.py @@ -3,7 +3,7 @@ from CTFd import mail from urlparse import urlparse, urljoin from functools import wraps -from flask import current_app as app, g, request, redirect, url_for, session +from flask import current_app as app, g, request, redirect, url_for, session, render_template from flask.ext.mail import Message from socket import inet_aton, inet_ntoa from struct import unpack, pack @@ -11,11 +11,71 @@ from struct import unpack, pack import time import datetime import hashlib -import digitalocean import shutil import requests +import logging +import os +import sys +def init_logs(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])) + + log_dir = os.path.join(parent, 'logs') + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + logs = [ + os.path.join(parent, 'logs', 'keys.log'), + os.path.join(parent, 'logs', 'logins.log'), + os.path.join(parent, 'logs', 'registers.log') + ] + + for log in logs: + if not os.path.exists(log): + open(log, 'a').close() + + key_log = logging.handlers.RotatingFileHandler(os.path.join(parent, 'logs', 'keys.log'), maxBytes=10000) + login_log = logging.handlers.RotatingFileHandler(os.path.join(parent, 'logs', 'logins.log'), maxBytes=10000) + register_log = logging.handlers.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 + + +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 general_error(error): + return render_template('errors/500.html'), 500 + + @app.errorhandler(502) + def gateway_error(error): + return render_template('errors/502.html'), 502 + def init_utils(app): app.jinja_env.filters['unix_time'] = unix_time app.jinja_env.filters['unix_time_millis'] = unix_time_millis @@ -25,6 +85,19 @@ def init_utils(app): app.jinja_env.globals.update(mailserver=mailserver) app.jinja_env.globals.update(ctf_name=ctf_name) + @app.context_processor + def inject_user(): + if authed(): + return dict(session) + return dict() + + @app.before_request + def needs_setup(): + if request.path == '/setup' or request.path.startswith('/static'): + return + if not is_setup(): + return redirect('/setup') + def ctf_name(): name = get_config('ctf_name') @@ -155,8 +228,8 @@ def set_config(key, value): else: config = Config(key, value) db.session.add(config) - db.session.commit() - return config + db.session.commit() + return config def mailserver(): @@ -205,10 +278,3 @@ def validate_url(url): def sha512(string): return hashlib.sha512(string).hexdigest() - -def get_digitalocean(): - token = get_config('do_api_key') - if token: - return digitalocean.Manager(token=token) - else: - return False diff --git a/CTFd/views.py b/CTFd/views.py index 247159be..5826df17 100644 --- a/CTFd/views.py +++ b/CTFd/views.py @@ -1,5 +1,5 @@ -from flask import current_app as app, render_template, render_template_string, request, redirect, abort, jsonify, json as json_mod, url_for, session -from CTFd.utils import authed, ip2long, long2ip, is_setup, validate_url, get_config +from flask import current_app as app, render_template, render_template_string, request, redirect, abort, jsonify, json as json_mod, url_for, session, Blueprint, Response +from CTFd.utils import authed, ip2long, long2ip, is_setup, validate_url, get_config, sha512 from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config from jinja2.exceptions import TemplateNotFound @@ -13,183 +13,196 @@ 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) +views = Blueprint('views', __name__) + + +@views.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() + + +@views.before_request +def csrf(): + if request.method == "POST": + if session['nonce'] != request.form.get('nonce'): + abort(403) + + +@views.before_request +def redirect_setup(): + if request.path == "/static/css/style.css": + return + if not is_setup() and request.path != "/setup": + return redirect('/setup') + + +@views.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': + ctf_name = request.form['ctf_name'] + ctf_name = Config('ctf_name', ctf_name) + + ## CSS + css = Config('start', '') + + ## 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) + + #max attempts per challenge + max_tries = Config("max_tries",0) + + + ## Start time + start = Config('start', None) + end = Config('end', None) + + ## Challenges cannot be viewed by unregistered users + view_challenges_unregistered = Config('view_challenges_unregistered', None) + + ## Allow/Disallow registration + prevent_registration = Config('prevent_registration', None) + + setup = Config('setup', True) + + db.session.add(ctf_name) + db.session.add(admin) + db.session.add(page) + db.session.add(max_tries) + db.session.add(start) + db.session.add(end) + db.session.add(view_challenges_unregistered) + db.session.add(prevent_registration) + db.session.add(css) + db.session.add(setup) + db.session.commit() + app.setup = False + return redirect('/') + return render_template('setup.html', nonce=session.get('nonce')) + return redirect('/') + + +# Custom CSS handler +@views.route('/static/user.css') +def custom_css(): + return Response(get_config("css"), mimetype='text/css') + + +# Static HTML files +@views.route("/", defaults={'template': 'index'}) +@views.route("/