Bugfixes and architectural changes

Moved some folders around, starting to remove subdomain handling,
blueprints, custom css, removed digital ocean interface, fixed some bugs
This commit is contained in:
CodeKevin
2015-09-13 23:55:15 -04:00
parent 5f6e610f2d
commit 7d766372df
53 changed files with 1191 additions and 1151 deletions

View File

@@ -3,22 +3,14 @@ from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.mail import Mail, Message from flask.ext.mail import Mail, Message
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from flask.ext.session import Session from flask.ext.session import Session
import logging
import os import os
import sqlalchemy 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: def create_app(config='CTFd.config'):
app.config.update( app = Flask("CTFd")
SQLALCHEMY_DATABASE_URI = 'mysql://'+username+':'+password+'@localhost:3306/' + subdomain + '_ctfd', with app.app_context():
HOST = subdomain + app.config["HOST"], app.config.from_object(config)
SESSION_FILE_DIR = app.config['SESSION_FILE_DIR'] + "/" + subdomain,
DEBUG = True
)
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking 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() db.create_all()
app.db = db app.db = db
# app.setup = True
global mail global mail
mail = Mail(app) mail = Mail(app)
Session(app) Session(app)
from CTFd.views import init_views from CTFd.views import views
init_views(app) from CTFd.challenges import challenges
from CTFd.errors import init_errors from CTFd.scoreboard import scoreboard
init_errors(app) from CTFd.auth import auth
from CTFd.challenges import init_challenges from CTFd.admin import admin
init_challenges(app) from CTFd.utils import init_utils, init_errors, init_logs
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) 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 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

View File

@@ -1,5 +1,5 @@
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, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, get_digitalocean, sendmail, rmdir 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 CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
from itsdangerous import TimedSerializer, BadTimeSignature from itsdangerous import TimedSerializer, BadTimeSignature
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@@ -14,15 +14,17 @@ import re
import os import os
import json import json
def init_admin(app): admin = Blueprint('admin', __name__)
@app.route('/admin', methods=['GET', 'POST'])
def admin():
@admin.route('/admin', methods=['GET', 'POST'])
def admin_view():
if request.method == 'POST': if request.method == 'POST':
username = request.form.get('name') username = request.form.get('name')
password = request.form.get('password') password = request.form.get('password')
admin = Teams.query.filter_by(name=request.form['name'], admin=True).first() admin_user= Teams.query.filter_by(name=request.form['name'], admin=True).first()
if admin and bcrypt_sha256.verify(request.form['password'], admin.password): if admin_user and bcrypt_sha256.verify(request.form['password'], admin.password):
try: try:
session.regenerate() # NO SESSION FIXATION FOR YOU session.regenerate() # NO SESSION FIXATION FOR YOU
except: except:
@@ -39,12 +41,14 @@ def init_admin(app):
return render_template('admin/login.html') return render_template('admin/login.html')
@app.route('/admin/graphs')
@admin.route('/admin/graphs')
@admins_only @admins_only
def admin_graphs(): def admin_graphs():
return render_template('admin/graphs.html') return render_template('admin/graphs.html')
@app.route('/admin/config', methods=['GET', 'POST'])
@admin.route('/admin/config', methods=['GET', 'POST'])
@admins_only @admins_only
def admin_config(): def admin_config():
if request.method == "POST": if request.method == "POST":
@@ -73,7 +77,6 @@ def init_admin(app):
ctf_name = set_config("ctf_name", request.form.get('ctf_name', None)) 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)) 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)) max_tries = set_config("max_tries", request.form.get('max_tries', None))
@@ -91,15 +94,11 @@ def init_admin(app):
ctf_name = get_config('ctf_name') ctf_name = get_config('ctf_name')
if not ctf_name: if not ctf_name:
set_config('do_api_key', None) set_config('ctf_name', None)
mg_api_key = get_config('do_api_key') mg_api_key = get_config('mg_api_key')
if not mg_api_key: if not mg_api_key:
set_config('do_api_key', None) set_config('mg_api_key', None)
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') max_tries = get_config('max_tries')
if not max_tries: if not max_tries:
@@ -137,14 +136,27 @@ def init_admin(app):
return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end, return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end,
max_tries=max_tries, max_tries=max_tries,
view_challenges_unregistered=view_challenges_unregistered, view_challenges_unregistered=view_challenges_unregistered,
prevent_registration=prevent_registration, do_api_key=do_api_key, mg_api_key=mg_api_key, prevent_registration=prevent_registration, mg_api_key=mg_api_key,
prevent_name_change=prevent_name_change, prevent_name_change=prevent_name_change,
view_after_ctf=view_after_ctf) view_after_ctf=view_after_ctf)
@app.route('/admin/pages', defaults={'route': None}, methods=['GET', 'POST'])
@app.route('/admin/pages/<route>', methods=['GET', 'POST']) @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/<route>', methods=['GET', 'POST'])
@admins_only @admins_only
def admin_pages(route): 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': if route and request.method == 'GET':
page = Pages.query.filter_by(route=route).first() page = Pages.query.filter_by(route=route).first()
return render_template('admin/editor.html', page=page) return render_template('admin/editor.html', page=page)
@@ -167,12 +179,11 @@ def init_admin(app):
db.session.add(page) db.session.add(page)
db.session.commit() db.session.commit()
return redirect('/admin/pages') return redirect('/admin/pages')
if not route and request.method == 'POST':
return render_template('admin/editor.html')
pages = Pages.query.all() pages = Pages.query.all()
return render_template('admin/pages.html', routes=pages) return render_template('admin/pages.html', routes=pages, css=get_config('css'))
@app.route('/admin/page/<pageroute>/delete', methods=['POST'])
@admin.route('/admin/page/<pageroute>/delete', methods=['POST'])
@admins_only @admins_only
def delete_page(pageroute): def delete_page(pageroute):
page = Pages.query.filter_by(route=pageroute).first() page = Pages.query.filter_by(route=pageroute).first()
@@ -180,23 +191,8 @@ def init_admin(app):
db.session.commit() db.session.commit()
return '1' 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() @admin.route('/admin/chals', methods=['POST', 'GET'])
slugs = m.get_all_sizes()
images = m.get_all_images()
regions = m.get_all_regions()
return render_template('admin/hosts.html', hosts=hosts, slugs=slugs, images=images, regions=regions)
@app.route('/admin/chals', methods=['POST', 'GET'])
@admins_only @admins_only
def admin_chals(): def admin_chals():
if request.method == 'POST': if request.method == 'POST':
@@ -211,7 +207,8 @@ def init_admin(app):
else: else:
return render_template('admin/chals.html') return render_template('admin/chals.html')
@app.route('/admin/keys/<chalid>', methods=['POST', 'GET'])
@admin.route('/admin/keys/<chalid>', methods=['POST', 'GET'])
@admins_only @admins_only
def admin_keys(chalid): def admin_keys(chalid):
if request.method == 'GET': if request.method == 'GET':
@@ -235,7 +232,8 @@ def init_admin(app):
db.session.close() db.session.close()
return '1' return '1'
@app.route('/admin/tags/<chalid>', methods=['GET', 'POST'])
@admin.route('/admin/tags/<chalid>', methods=['GET', 'POST'])
@admins_only @admins_only
def admin_tags(chalid): def admin_tags(chalid):
if request.method == 'GET': if request.method == 'GET':
@@ -254,7 +252,8 @@ def init_admin(app):
db.session.close() db.session.close()
return '1' return '1'
@app.route('/admin/tags/<tagid>/delete', methods=['POST'])
@admin.route('/admin/tags/<tagid>/delete', methods=['POST'])
@admins_only @admins_only
def admin_delete_tags(tagid): def admin_delete_tags(tagid):
if request.method == 'POST': if request.method == 'POST':
@@ -265,7 +264,7 @@ def init_admin(app):
return "1" return "1"
@app.route('/admin/files/<chalid>', methods=['GET', 'POST']) @admin.route('/admin/files/<chalid>', methods=['GET', 'POST'])
@admins_only @admins_only
def admin_files(chalid): def admin_files(chalid):
if request.method == 'GET': if request.method == 'GET':
@@ -310,13 +309,15 @@ def init_admin(app):
db.session.close() db.session.close()
return redirect('/admin/chals') return redirect('/admin/chals')
@app.route('/admin/teams')
@admin.route('/admin/teams')
@admins_only @admins_only
def admin_teams(): def admin_teams():
teams = Teams.query.all() teams = Teams.query.all()
return render_template('admin/teams.html', teams=teams) return render_template('admin/teams.html', teams=teams)
@app.route('/admin/team/<teamid>', methods=['GET', 'POST'])
@admin.route('/admin/team/<teamid>', methods=['GET', 'POST'])
@admins_only @admins_only
def admin_team(teamid): def admin_team(teamid):
user = Teams.query.filter_by(id=teamid).first() user = Teams.query.filter_by(id=teamid).first()
@@ -328,8 +329,8 @@ def init_admin(app):
if request.method == 'GET': if request.method == 'GET':
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place) return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, place=place)
elif request.method == 'POST': elif request.method == 'POST':
admin = request.form.get('admin', "false") admin_user = request.form.get('admin', "false")
admin = 1 if admin == "true" else 0 admin_user = 1 if admin_user == "true" else 0
if admin: if admin:
user.admin = 1 user.admin = 1
db.session.commit() db.session.commit()
@@ -367,7 +368,8 @@ def init_admin(app):
db.session.close() db.session.close()
return jsonify({'data':['success']}) return jsonify({'data':['success']})
@app.route('/admin/team/<teamid>/mail', methods=['POST'])
@admin.route('/admin/team/<teamid>/mail', methods=['POST'])
@admins_only @admins_only
def email_user(teamid): def email_user(teamid):
message = request.form.get('msg', None) message = request.form.get('msg', None)
@@ -377,7 +379,8 @@ def init_admin(app):
return "1" return "1"
return "0" return "0"
@app.route('/admin/team/<teamid>/ban', methods=['POST'])
@admin.route('/admin/team/<teamid>/ban', methods=['POST'])
@admins_only @admins_only
def ban(teamid): def ban(teamid):
user = Teams.query.filter_by(id=teamid).first() user = Teams.query.filter_by(id=teamid).first()
@@ -385,7 +388,8 @@ def init_admin(app):
db.session.commit() db.session.commit()
return redirect('/admin/scoreboard') return redirect('/admin/scoreboard')
@app.route('/admin/team/<teamid>/unban', methods=['POST'])
@admin.route('/admin/team/<teamid>/unban', methods=['POST'])
@admins_only @admins_only
def unban(teamid): def unban(teamid):
user = Teams.query.filter_by(id=teamid).first() user = Teams.query.filter_by(id=teamid).first()
@@ -393,7 +397,8 @@ def init_admin(app):
db.session.commit() db.session.commit()
return redirect('/admin/scoreboard') return redirect('/admin/scoreboard')
@app.route('/admin/team/<teamid>/delete', methods=['POST'])
@admin.route('/admin/team/<teamid>/delete', methods=['POST'])
@admins_only @admins_only
def delete_team(teamid): def delete_team(teamid):
user = Teams.query.filter_by(id=teamid).first() user = Teams.query.filter_by(id=teamid).first()
@@ -402,7 +407,7 @@ def init_admin(app):
return '1' return '1'
@app.route('/admin/graphs/<graph_type>') @admin.route('/admin/graphs/<graph_type>')
@admins_only @admins_only
def admin_graph(graph_type): def admin_graph(graph_type):
if graph_type == 'categories': if graph_type == 'categories':
@@ -418,7 +423,8 @@ def init_admin(app):
json[chal.chal.name] = count json[chal.chal.name] = count
return jsonify(json) return jsonify(json)
@app.route('/admin/scoreboard')
@admin.route('/admin/scoreboard')
@admins_only @admins_only
def admin_scoreboard(): def admin_scoreboard():
score = db.func.sum(Challenges.value).label('score') score = db.func.sum(Challenges.value).label('score')
@@ -427,7 +433,8 @@ def init_admin(app):
db.session.close() db.session.close()
return render_template('admin/scoreboard.html', teams=teams) return render_template('admin/scoreboard.html', teams=teams)
@app.route('/admin/scores')
@admin.route('/admin/scores')
@admins_only @admins_only
def admin_scores(): def admin_scores():
score = db.func.sum(Challenges.value).label('score') score = db.func.sum(Challenges.value).label('score')
@@ -439,7 +446,8 @@ def init_admin(app):
json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)}) json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)})
return jsonify(json) return jsonify(json)
@app.route('/admin/solves/<teamid>', methods=['GET'])
@admin.route('/admin/solves/<teamid>', methods=['GET'])
@admins_only @admins_only
def admin_solves(teamid="all"): def admin_solves(teamid="all"):
if teamid == "all": if teamid == "all":
@@ -453,7 +461,7 @@ def init_admin(app):
return jsonify(json) return jsonify(json)
@app.route('/admin/solves/<teamid>/<chalid>/delete', methods=['POST']) @admin.route('/admin/solves/<teamid>/<chalid>/delete', methods=['POST'])
@admins_only @admins_only
def delete_solve(teamid, chalid): def delete_solve(teamid, chalid):
solve = Solves.query.filter_by(teamid=teamid, chalid=chalid).first() solve = Solves.query.filter_by(teamid=teamid, chalid=chalid).first()
@@ -461,7 +469,8 @@ def init_admin(app):
db.session.commit() db.session.commit()
return '1' return '1'
@app.route('/admin/statistics', methods=['GET'])
@admin.route('/admin/statistics', methods=['GET'])
@admins_only @admins_only
def admin_stats(): def admin_stats():
db.session.commit() db.session.commit()
@@ -483,7 +492,8 @@ def init_admin(app):
least_solved=least_solved_chal least_solved=least_solved_chal
) )
@app.route('/admin/wrong_keys/<page>', methods=['GET'])
@admin.route('/admin/wrong_keys/<page>', methods=['GET'])
@admins_only @admins_only
def admin_wrong_key(page='1'): def admin_wrong_key(page='1'):
page = abs(int(page)) page = abs(int(page))
@@ -500,7 +510,8 @@ def init_admin(app):
return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages) return render_template('admin/wrong_keys.html', wrong_keys=wrong_keys, pages=pages)
@app.route('/admin/correct_keys/<page>', methods=['GET'])
@admin.route('/admin/correct_keys/<page>', methods=['GET'])
@admins_only @admins_only
def admin_correct_key(page='1'): def admin_correct_key(page='1'):
page = abs(int(page)) page = abs(int(page))
@@ -517,7 +528,8 @@ def init_admin(app):
return render_template('admin/correct_keys.html', solves=solves, pages=pages) return render_template('admin/correct_keys.html', solves=solves, pages=pages)
@app.route('/admin/fails/<teamid>', methods=['GET'])
@admin.route('/admin/fails/<teamid>', methods=['GET'])
@admins_only @admins_only
def admin_fails(teamid='all'): def admin_fails(teamid='all'):
if teamid == "all": if teamid == "all":
@@ -534,8 +546,7 @@ def init_admin(app):
return jsonify(json) return jsonify(json)
@admin.route('/admin/chal/new', methods=['POST'])
@app.route('/admin/chal/new', methods=['POST'])
def admin_create_chal(): def admin_create_chal():
files = request.files.getlist('files[]') files = request.files.getlist('files[]')
@@ -568,7 +579,8 @@ def init_admin(app):
db.session.close() db.session.close()
return redirect('/admin/chals') return redirect('/admin/chals')
@app.route('/admin/chal/delete', methods=['POST'])
@admin.route('/admin/chal/delete', methods=['POST'])
def admin_delete_chal(): def admin_delete_chal():
challenge = Challenges.query.filter_by(id=request.form['id']).first() challenge = Challenges.query.filter_by(id=request.form['id']).first()
if challenge: if challenge:
@@ -586,7 +598,8 @@ def init_admin(app):
db.session.close() db.session.close()
return '1' return '1'
@app.route('/admin/chal/update', methods=['POST'])
@admin.route('/admin/chal/update', methods=['POST'])
def admin_update_chal(): def admin_update_chal():
challenge = Challenges.query.filter_by(id=request.form['id']).first() challenge = Challenges.query.filter_by(id=request.form['id']).first()
challenge.name = request.form['name'] challenge.name = request.form['name']

View File

@@ -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.utils import sha512, is_safe_url, authed, mailserver, sendmail, can_register
from CTFd.models import db, Teams from CTFd.models import db, Teams
@@ -11,15 +11,11 @@ import time
import re import re
import os import os
def init_auth(app): auth = Blueprint('auth', __name__)
@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']) @auth.route('/reset_password', methods=['POST', 'GET'])
@auth.route('/reset_password/<data>', methods=['POST', 'GET'])
def reset_password(data=None): def reset_password(data=None):
if data is not None and request.method == "GET": if data is not None and request.method == "GET":
return render_template('reset_password.html', mode='set') return render_template('reset_password.html', mode='set')
@@ -54,7 +50,8 @@ Did you initiate a password reset?
return render_template('reset_password.html', errors=['Check your email']) return render_template('reset_password.html', errors=['Check your email'])
return render_template('reset_password.html') return render_template('reset_password.html')
@app.route('/register', methods=['POST', 'GET'])
@auth.route('/register', methods=['POST', 'GET'])
def register(): def register():
if not can_register(): if not can_register():
return redirect('/login') return redirect('/login')
@@ -102,7 +99,8 @@ Did you initiate a password reset?
else: else:
return render_template('register.html') return render_template('register.html')
@app.route('/login', methods=['POST', 'GET'])
@auth.route('/login', methods=['POST', 'GET'])
def login(): def login():
if request.method == 'POST': if request.method == 'POST':
errors = [] errors = []
@@ -134,7 +132,7 @@ Did you initiate a password reset?
return render_template('login.html') return render_template('login.html')
@app.route('/logout') @auth.route('/logout')
def logout(): def logout():
if authed(): if authed():
session.clear() session.clear()

View File

@@ -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.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 from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys
@@ -7,9 +7,11 @@ import time
import re import re
import logging import logging
def init_challenges(app): challenges = Blueprint('challenges', __name__)
@app.route('/challenges', methods=['GET'])
def challenges():
@challenges.route('/challenges', methods=['GET'])
def challenges_view():
if not is_admin(): if not is_admin():
if not ctftime(): if not ctftime():
if view_after_ctf(): if view_after_ctf():
@@ -21,7 +23,8 @@ def init_challenges(app):
else: else:
return redirect(url_for('login', next="challenges")) return redirect(url_for('login', next="challenges"))
@app.route('/chals', methods=['GET'])
@challenges.route('/chals', methods=['GET'])
def chals(): def chals():
if not is_admin(): if not is_admin():
if not ctftime(): if not ctftime():
@@ -43,7 +46,8 @@ def init_challenges(app):
db.session.close() db.session.close()
return redirect('/login') return redirect('/login')
@app.route('/chals/solves')
@challenges.route('/chals/solves')
def chals_per_solves(): def chals_per_solves():
if can_view_challenges(): if can_view_challenges():
solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all() solves = Solves.query.add_columns(db.func.count(Solves.chalid)).group_by(Solves.chalid).all()
@@ -53,8 +57,9 @@ def init_challenges(app):
return jsonify(json) return jsonify(json)
return redirect(url_for('login', next="/chals/solves")) return redirect(url_for('login', next="/chals/solves"))
@app.route('/solves')
@app.route('/solves/<teamid>') @challenges.route('/solves')
@challenges.route('/solves/<teamid>')
def solves(teamid=None): def solves(teamid=None):
if teamid is None: if teamid is None:
if authed(): if authed():
@@ -69,7 +74,8 @@ def init_challenges(app):
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)}) 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) return jsonify(json)
@app.route('/maxattempts')
@challenges.route('/maxattempts')
def attempts(): def attempts():
chals = Challenges.query.add_columns('id').all() chals = Challenges.query.add_columns('id').all()
json = {'maxattempts':[]} json = {'maxattempts':[]}
@@ -79,7 +85,8 @@ def init_challenges(app):
json['maxattempts'].append({'chalid':chalid}) json['maxattempts'].append({'chalid':chalid})
return jsonify(json) return jsonify(json)
@app.route('/fails/<teamid>', methods=['GET'])
@challenges.route('/fails/<teamid>', methods=['GET'])
def fails(teamid): def fails(teamid):
fails = WrongKeys.query.filter_by(team=teamid).count() fails = WrongKeys.query.filter_by(team=teamid).count()
solves = Solves.query.filter_by(teamid=teamid).count() solves = Solves.query.filter_by(teamid=teamid).count()
@@ -87,7 +94,8 @@ def init_challenges(app):
json = {'fails':str(fails), 'solves': str(solves)} json = {'fails':str(fails), 'solves': str(solves)}
return jsonify(json) return jsonify(json)
@app.route('/chal/<chalid>/solves', methods=['GET'])
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
def who_solved(chalid): def who_solved(chalid):
solves = Solves.query.filter_by(chalid=chalid) solves = Solves.query.filter_by(chalid=chalid)
json = {'teams':[]} json = {'teams':[]}
@@ -95,7 +103,8 @@ def init_challenges(app):
json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date}) json['teams'].append({'id':solve.team.id, 'name':solve.team.name, 'date':solve.date})
return jsonify(json) return jsonify(json)
@app.route('/chal/<chalid>', methods=['POST'])
@challenges.route('/chal/<chalid>', methods=['POST'])
def chal(chalid): def chal(chalid):
if not ctftime(): if not ctftime():
return redirect('/challenges') return redirect('/challenges')

View File

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

View File

@@ -1,4 +1,3 @@
#from CTFd import db
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
from socket import inet_aton, inet_ntoa from socket import inet_aton, inet_ntoa
@@ -79,7 +78,6 @@ class Keys(db.Model):
key_type = db.Column(db.Integer) key_type = db.Column(db.Integer)
flag = db.Column(db.Text) flag = db.Column(db.Text)
def __init__(self, chal, flag, key_type): def __init__(self, chal, flag, key_type):
self.chal = chal self.chal = chal
self.flag = flag self.flag = flag
@@ -181,6 +179,7 @@ class Tracking(db.Model):
def __repr__(self): def __repr__(self):
return '<ip %r>' % self.team return '<ip %r>' % self.team
class Config(db.Model): class Config(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.Text) key = db.Column(db.Text)

View File

@@ -1,17 +1,20 @@
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.utils import unix_time
from CTFd.models import db, Teams, Solves, Challenges from CTFd.models import db, Teams, Solves, Challenges
def init_scoreboard(app): scoreboard = Blueprint('scoreboard', __name__)
@app.route('/scoreboard')
def scoreboard():
@scoreboard.route('/scoreboard')
def scoreboard_view():
score = db.func.sum(Challenges.value).label('score') score = db.func.sum(Challenges.value).label('score')
quickest = db.func.max(Solves.date).label('quickest') quickest = db.func.max(Solves.date).label('quickest')
teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest) teams = db.session.query(Solves.teamid, Teams.name, score).join(Teams).join(Challenges).filter(Teams.banned == None).group_by(Solves.teamid).order_by(score.desc(), quickest)
db.session.close() db.session.close()
return render_template('scoreboard.html', teams=teams) return render_template('scoreboard.html', teams=teams)
@app.route('/scores')
@scoreboard.route('/scores')
def scores(): def scores():
score = db.func.sum(Challenges.value).label('score') score = db.func.sum(Challenges.value).label('score')
quickest = db.func.max(Solves.date).label('quickest') quickest = db.func.max(Solves.date).label('quickest')
@@ -22,7 +25,8 @@ def init_scoreboard(app):
json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)}) json['teams'].append({'place':i+1, 'id':x.teamid, 'name':x.name,'score':int(x.score)})
return jsonify(json) return jsonify(json)
@app.route('/top/<count>')
@scoreboard.route('/top/<count>')
def topteams(count): def topteams(count):
try: try:
count = int(count) count = int(count)

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -9,7 +9,7 @@
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon"> <link rel="icon" href="/static/img/favicon.ico" type="image/x-icon">
<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/normalize.min.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/foundation/5.4.7/css/foundation.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="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="/static/admin/css/style.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> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
</head> </head>
@@ -32,22 +32,13 @@
<!-- Left Nav Section --> <!-- Left Nav Section -->
<ul class="left"> <ul class="left">
<li><a href="/admin/graphs">Graphs</a> <li><a href="/admin/graphs">Graphs</a></li>
</li> <li><a href="/admin/pages">Pages</a></li>
<li><a href="/admin/pages">Pages</a> <li><a href="/admin/teams">Teams</a></li>
</li> <li><a href="/admin/scoreboard">Scoreboard</a></li>
<li><a href="/admin/hosts">Hosts</a> <li><a href="/admin/chals">Challenges</a></li>
</li> <li><a href="/admin/statistics">Statistics</a></li>
<li><a href="/admin/teams">Teams</a> <li><a href="/admin/config">Config</a></li>
</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> </ul>
</section> </section>
</nav> </nav>

View File

@@ -22,11 +22,6 @@
<input id='mg_api_key' name='mg_api_key' type='text' placeholder="Mailgun API Key" {% if mg_api_key is defined and mg_api_key != None %}value="{{ mg_api_key }}"{% endif %}> <input id='mg_api_key' name='mg_api_key' type='text' placeholder="Mailgun API Key" {% if mg_api_key is defined and mg_api_key != None %}value="{{ mg_api_key }}"{% endif %}>
</div> </div>
<div class="row">
<label for="start">Digital Ocean API Key:</label>
<input id='do_api_key' name='do_api_key' type='text' placeholder="Digital Ocean API Key" {% if do_api_key is defined and do_api_key != None %}value="{{ do_api_key }}"{% endif %}>
</div>
<div class="row"> <div class="row">
<label for="start">Start Date:</label> <label for="start">Start Date:</label>
<input id='start' name='start' type='text' placeholder="Start Date (UTC timestamp)" {% if start is defined and start != None %}value="{{ start }}"{% endif %}> <input id='start' name='start' type='text' placeholder="Start Date (UTC timestamp)" {% if start is defined and start != None %}value="{{ start }}"{% endif %}>

View File

@@ -16,6 +16,14 @@
</form> </form>
<a class="close-reveal-modal">&#215;</a> <a class="close-reveal-modal">&#215;</a>
</div> </div>
<div class="small-6 columns">
<h3>CSS editor <a onclick="save_css()"><i class="fa fa-floppy-o"></i></a></h3>
<div style="height: 500px;" id="admin-css-editor" name="css">{{ css }}</div>
</div>
<div class="small-6 columns">
<h3>HTML Pages <a href="/admin/pages?mode=create"><i class="fa fa-plus"></i></a></h3>
<table id="pages"> <table id="pages">
<thead> <thead>
<tr> <tr>
@@ -32,14 +40,14 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<form method="POST"> </div>
<input name='nonce' type='hidden' value="{{ nonce }}">
<button class="radius" type='submit'>New Page</button>
</form>
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ace.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/theme-github.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-css.js"></script>
<script> <script>
$('#delete-route').click(function(e){ $('#delete-route').click(function(e){
e.preventDefault(); e.preventDefault();
@@ -60,10 +68,23 @@ function load_confirm_modal(route){
$('#confirm').foundation('reveal', 'open'); $('#confirm').foundation('reveal', 'open');
} }
function save_css(){
var css = editor.getValue();
var nonce = $('#nonce').val();
$.post('/admin/css', {'css':css, 'nonce':nonce}, function(){
console.log('saved');
});
}
$('.fa-times').click(function(){ $('.fa-times').click(function(){
var elem = $(this).parent().parent(); var elem = $(this).parent().parent();
var name = elem.find('.route-name').text().trim(); var name = elem.find('.route-name').text().trim();
load_confirm_modal(name) load_confirm_modal(name)
}); });
var editor = ace.edit("admin-css-editor");
editor.setTheme("ace/theme/github");
editor.getSession().setMode("ace/mode/css");
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -11,6 +11,7 @@
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.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" 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"> <link rel="stylesheet" type="text/css" href="/static/css/style.css">
<link rel="stylesheet" type="text/css" href="/static/user.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
</head> </head>
<body> <body>
@@ -40,29 +41,9 @@
{%else %} {%else %}
<li class="has-form"> <li class="has-form">
{% if can_register() %} {% if can_register() %}
<li class="has-dropdown"> <li><a href="/register">Register</a></li>
<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>
{% endif %} {% endif %}
<li class="has-dropdown"> <li><a href="/login">Login</a></li>
<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 %} {% endif %}
</li> </li>
</ul> </ul>

View File

@@ -19,6 +19,7 @@
<input class="radius" type='text' name='name' placeholder='Name'><br/> <input class="radius" type='text' name='name' placeholder='Name'><br/>
<input class="radius" type='password' name='password' placeholder='Password'><br/> <input class="radius" type='password' name='password' placeholder='Password'><br/>
<p><a href="/reset_password">Forgot your password?</a></p> <p><a href="/reset_password">Forgot your password?</a></p>
<input type="hidden" name="nonce" value="{{nonce}}">
<button class="radius" type='submit'>Login</button> <button class="radius" type='submit'>Login</button>
</form> </form>
</div> </div>

View File

@@ -37,6 +37,7 @@
<h4 class="text-center"><a href="/admin">Click here</a> to login and setup your CTF</h4> <h4 class="text-center"><a href="/admin">Click here</a> to login and setup your CTF</h4>
</div> </div>
</textarea><br> </textarea><br>
<input type="hidden" name="nonce" value="{{nonce}}"> {# This nonce is implemented specially in the route itself #}
<button class="radius" type='submit'>Login</button> <button class="radius" type='submit'>Login</button>
</form> </form>
</div> </div>

View File

@@ -3,7 +3,7 @@ from CTFd import mail
from urlparse import urlparse, urljoin from urlparse import urlparse, urljoin
from functools import wraps 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 flask.ext.mail import Message
from socket import inet_aton, inet_ntoa from socket import inet_aton, inet_ntoa
from struct import unpack, pack from struct import unpack, pack
@@ -11,11 +11,71 @@ from struct import unpack, pack
import time import time
import datetime import datetime
import hashlib import hashlib
import digitalocean
import shutil import shutil
import requests 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): def init_utils(app):
app.jinja_env.filters['unix_time'] = unix_time app.jinja_env.filters['unix_time'] = unix_time
app.jinja_env.filters['unix_time_millis'] = unix_time_millis 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(mailserver=mailserver)
app.jinja_env.globals.update(ctf_name=ctf_name) 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(): def ctf_name():
name = get_config('ctf_name') name = get_config('ctf_name')
@@ -205,10 +278,3 @@ def validate_url(url):
def sha512(string): def sha512(string):
return hashlib.sha512(string).hexdigest() return hashlib.sha512(string).hexdigest()
def get_digitalocean():
token = get_config('do_api_key')
if token:
return digitalocean.Manager(token=token)
else:
return False

View File

@@ -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 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 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 CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
@@ -13,8 +13,10 @@ import sys
import json import json
import os import os
def init_views(app): views = Blueprint('views', __name__)
@app.before_request
@views.before_request
def tracker(): def tracker():
if authed(): if authed():
if not Tracking.query.filter_by(ip=ip2long(request.remote_addr)).first(): if not Tracking.query.filter_by(ip=ip2long(request.remote_addr)).first():
@@ -23,25 +25,23 @@ def init_views(app):
db.session.commit() db.session.commit()
db.session.close() db.session.close()
@app.before_request
@views.before_request
def csrf(): def csrf():
if authed() and request.method == "POST": if request.method == "POST":
if session['nonce'] != request.form.get('nonce'): if session['nonce'] != request.form.get('nonce'):
abort(403) abort(403)
@app.before_request
@views.before_request
def redirect_setup(): def redirect_setup():
if request.path == "/static/css/style.css": if request.path == "/static/css/style.css":
return return
if not is_setup() and request.path != "/setup": if not is_setup() and request.path != "/setup":
return redirect('/setup') return redirect('/setup')
@app.before_first_request
def needs_setup():
if not is_setup():
return redirect('/setup')
@app.route('/setup', methods=['GET', 'POST']) @views.route('/setup', methods=['GET', 'POST'])
def setup(): def setup():
# with app.app_context(): # with app.app_context():
# admin = Teams.query.filter_by(admin=True).first() # admin = Teams.query.filter_by(admin=True).first()
@@ -51,6 +51,9 @@ def init_views(app):
ctf_name = request.form['ctf_name'] ctf_name = request.form['ctf_name']
ctf_name = Config('ctf_name', ctf_name) ctf_name = Config('ctf_name', ctf_name)
## CSS
css = Config('start', '')
## Admin user ## Admin user
name = request.form['name'] name = request.form['name']
email = request.form['email'] email = request.form['email']
@@ -86,16 +89,24 @@ def init_views(app):
db.session.add(end) db.session.add(end)
db.session.add(view_challenges_unregistered) db.session.add(view_challenges_unregistered)
db.session.add(prevent_registration) db.session.add(prevent_registration)
db.session.add(css)
db.session.add(setup) db.session.add(setup)
db.session.commit() db.session.commit()
app.setup = False app.setup = False
return redirect('/') return redirect('/')
return render_template('setup.html') return render_template('setup.html', nonce=session.get('nonce'))
return redirect('/') return redirect('/')
# Custom CSS handler
@views.route('/static/user.css')
def custom_css():
return Response(get_config("css"), mimetype='text/css')
# Static HTML files # Static HTML files
@app.route("/", defaults={'template': 'index'}) @views.route("/", defaults={'template': 'index'})
@app.route("/<template>") @views.route("/<template>")
def static_html(template): def static_html(template):
try: try:
return render_template('%s.html' % template) return render_template('%s.html' % template)
@@ -106,12 +117,14 @@ def init_views(app):
else: else:
abort(404) abort(404)
@app.route('/teams')
@views.route('/teams')
def teams(): def teams():
teams = Teams.query.all() teams = Teams.query.all()
return render_template('teams.html', teams=teams) return render_template('teams.html', teams=teams)
@app.route('/team/<teamid>', methods=['GET', 'POST'])
@views.route('/team/<teamid>', methods=['GET', 'POST'])
def team(teamid): def team(teamid):
user = Teams.query.filter_by(id=teamid).first() user = Teams.query.filter_by(id=teamid).first()
solves = Solves.query.filter_by(teamid=teamid).all() solves = Solves.query.filter_by(teamid=teamid).all()
@@ -128,7 +141,7 @@ def init_views(app):
return jsonify(json) return jsonify(json)
@app.route('/profile', methods=['POST', 'GET']) @views.route('/profile', methods=['POST', 'GET'])
def profile(): def profile():
if authed(): if authed():
if request.method == "POST": if request.method == "POST":

View File

@@ -8,5 +8,4 @@ passlib==1.6.2
bcrypt bcrypt
six==1.8.0 six==1.8.0
itsdangerous==0.24 itsdangerous==0.24
python-digitalocean==1.4.2
requests==2.3.0 requests==2.3.0