mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 14:04:20 +01:00
Merging changes from various forks
Until v1 is released all changes are fair game. Caching support Fixes for decoding confirmation and reset_password email tokens Starting work on #154 specifying why challenges are not open Adding a required parameter to HTML to sort of fix #153 Adding a column to specify when a team registered Check static key by default in new key Decreasing capability of pages functionality to address security concerns Fixing confirmations restrictions by modifying can__view_challenges()
This commit is contained in:
@@ -4,9 +4,11 @@ from logging.handlers import RotatingFileHandler
|
|||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
from sqlalchemy_utils import database_exists, create_database
|
from sqlalchemy_utils import database_exists, create_database
|
||||||
from jinja2 import FileSystemLoader, TemplateNotFound
|
from jinja2 import FileSystemLoader, TemplateNotFound
|
||||||
from utils import get_config, set_config
|
from utils import get_config, set_config, cache
|
||||||
import os
|
import os
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
from sqlalchemy.engine.url import make_url
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
|
|
||||||
|
|
||||||
class ThemeLoader(FileSystemLoader):
|
class ThemeLoader(FileSystemLoader):
|
||||||
@@ -24,15 +26,26 @@ def create_app(config='CTFd.config'):
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
## sqlite database creation is relative to the script which causes issues with serve.py
|
url = make_url(app.config['SQLALCHEMY_DATABASE_URI'])
|
||||||
if not database_exists(app.config['SQLALCHEMY_DATABASE_URI']) and not app.config['SQLALCHEMY_DATABASE_URI'].startswith('sqlite'):
|
if url.drivername == 'postgres':
|
||||||
create_database(app.config['SQLALCHEMY_DATABASE_URI'])
|
url.drivername = 'postgresql'
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not database_exists(url):
|
||||||
|
create_database(url)
|
||||||
|
db.create_all()
|
||||||
|
except OperationalError:
|
||||||
|
db.create_all()
|
||||||
|
else:
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
app.db = db
|
app.db = db
|
||||||
|
|
||||||
|
cache.init_app(app)
|
||||||
|
app.cache = cache
|
||||||
|
|
||||||
if not get_config('ctf_theme'):
|
if not get_config('ctf_theme'):
|
||||||
set_config('ctf_theme', 'original')
|
set_config('ctf_theme', 'original')
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import render_template, request, redirect, abort, jsonify, url_for, session, Blueprint
|
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, \
|
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, \
|
||||||
set_config, sendmail, rmdir, create_image, delete_image, run_image, container_status, container_ports, \
|
set_config, sendmail, rmdir, create_image, delete_image, run_image, container_status, container_ports, \
|
||||||
container_stop, container_start, get_themes
|
container_stop, container_start, get_themes, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
from CTFd.scoreboard import get_standings
|
from CTFd.scoreboard import get_standings
|
||||||
from itsdangerous import TimedSerializer, BadTimeSignature
|
from itsdangerous import TimedSerializer, BadTimeSignature
|
||||||
@@ -105,8 +105,12 @@ def admin_config():
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
with app.app_context():
|
||||||
|
cache.clear()
|
||||||
return redirect(url_for('admin.admin_config'))
|
return redirect(url_for('admin.admin_config'))
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
cache.clear()
|
||||||
ctf_name = get_config('ctf_name')
|
ctf_name = get_config('ctf_name')
|
||||||
ctf_theme = get_config('ctf_theme')
|
ctf_theme = get_config('ctf_theme')
|
||||||
max_tries = get_config('max_tries')
|
max_tries = get_config('max_tries')
|
||||||
@@ -758,7 +762,7 @@ def admin_wrong_key(page='1'):
|
|||||||
|
|
||||||
wrong_keys = WrongKeys.query.add_columns(WrongKeys.id, WrongKeys.chalid, WrongKeys.flag, WrongKeys.teamid, WrongKeys.date,\
|
wrong_keys = WrongKeys.query.add_columns(WrongKeys.id, WrongKeys.chalid, WrongKeys.flag, WrongKeys.teamid, WrongKeys.date,\
|
||||||
Challenges.name.label('chal_name'), Teams.name.label('team_name')).\
|
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()
|
join(Challenges).join(Teams).order_by(WrongKeys.date.desc()).slice(page_start, page_end).all()
|
||||||
|
|
||||||
wrong_count = db.session.query(db.func.count(WrongKeys.id)).first()[0]
|
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)
|
pages = int(wrong_count / results_per_page) + (wrong_count % results_per_page > 0)
|
||||||
@@ -776,7 +780,7 @@ def admin_correct_key(page='1'):
|
|||||||
|
|
||||||
solves = Solves.query.add_columns(Solves.id, Solves.chalid, Solves.teamid, Solves.date, Solves.flag, \
|
solves = Solves.query.add_columns(Solves.id, Solves.chalid, Solves.teamid, Solves.date, Solves.flag, \
|
||||||
Challenges.name.label('chal_name'), Teams.name.label('team_name')).\
|
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()
|
join(Challenges).join(Teams).order_by(Solves.date.desc()).slice(page_start, page_end).all()
|
||||||
|
|
||||||
solve_count = db.session.query(db.func.count(Solves.id)).first()[0]
|
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)
|
pages = int(solve_count / results_per_page) + (solve_count % results_per_page > 0)
|
||||||
|
|||||||
35
CTFd/auth.py
35
CTFd/auth.py
@@ -10,6 +10,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
import urllib
|
||||||
|
|
||||||
auth = Blueprint('auth', __name__)
|
auth = Blueprint('auth', __name__)
|
||||||
|
|
||||||
@@ -22,20 +23,28 @@ def confirm_user(data=None):
|
|||||||
if data and request.method == "GET": ## User is confirming email account
|
if data and request.method == "GET": ## User is confirming email account
|
||||||
try:
|
try:
|
||||||
s = Signer(app.config['SECRET_KEY'])
|
s = Signer(app.config['SECRET_KEY'])
|
||||||
email = s.unsign(data.decode('base64'))
|
email = s.unsign(urllib.unquote(data.decode('base64')))
|
||||||
except BadSignature:
|
except BadSignature:
|
||||||
return render_template('confirm.html', errors=['Your confirmation link seems wrong'])
|
return render_template('confirm.html', errors=['Your confirmation link seems wrong'])
|
||||||
|
except:
|
||||||
|
return render_template('reset_password.html', errors=['Your link appears broken, please try again.'])
|
||||||
team = Teams.query.filter_by(email=email).first()
|
team = Teams.query.filter_by(email=email).first()
|
||||||
team.verified = True
|
team.verified = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
logger = logging.getLogger('regs')
|
||||||
|
logger.warn("[{0}] {1} confirmed {2}".format(time.strftime("%m/%d/%Y %X"), team.name.encode('utf-8'), team.email.encode('utf-8')))
|
||||||
if authed():
|
if authed():
|
||||||
return redirect(url_for('challenges.challenges_view'))
|
return redirect(url_for('challenges.challenges_view'))
|
||||||
return redirect(url_for('auth.login'))
|
return redirect(url_for('auth.login'))
|
||||||
if not data and request.method == "GET": ## User has been directed to the confirm page because his account is not verified
|
if not data and request.method == "GET": ## User has been directed to the confirm page because his account is not verified
|
||||||
|
if not authed():
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
team = Teams.query.filter_by(id=session['id']).first()
|
team = Teams.query.filter_by(id=session['id']).first()
|
||||||
if team.verified:
|
if team.verified:
|
||||||
return redirect(url_for('views.profile'))
|
return redirect(url_for('views.profile'))
|
||||||
|
else:
|
||||||
|
verify_email(team.email)
|
||||||
return render_template('confirm.html', team=team)
|
return render_template('confirm.html', team=team)
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +57,7 @@ def reset_password(data=None):
|
|||||||
if data is not None and request.method == "POST":
|
if data is not None and request.method == "POST":
|
||||||
try:
|
try:
|
||||||
s = TimedSerializer(app.config['SECRET_KEY'])
|
s = TimedSerializer(app.config['SECRET_KEY'])
|
||||||
name = s.loads(data.decode('base64'), max_age=1800)
|
name = s.loads(urllib.unquote(data.decode('base64')), max_age=1800)
|
||||||
except BadTimeSignature:
|
except BadTimeSignature:
|
||||||
return render_template('reset_password.html', errors=['Your link has expired'])
|
return render_template('reset_password.html', errors=['Your link has expired'])
|
||||||
team = Teams.query.filter_by(name=name).first()
|
team = Teams.query.filter_by(name=name).first()
|
||||||
@@ -92,7 +101,7 @@ def register():
|
|||||||
emails = Teams.query.add_columns('email', 'id').filter_by(email=email).first()
|
emails = Teams.query.add_columns('email', 'id').filter_by(email=email).first()
|
||||||
pass_short = len(password) == 0
|
pass_short = len(password) == 0
|
||||||
pass_long = len(password) > 128
|
pass_long = len(password) > 128
|
||||||
valid_email = re.match("[^@]+@[^@]+\.[^@]+", request.form['email'])
|
valid_email = re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", request.form['email'])
|
||||||
|
|
||||||
if not valid_email:
|
if not valid_email:
|
||||||
errors.append("That email doesn't look right")
|
errors.append("That email doesn't look right")
|
||||||
@@ -121,10 +130,15 @@ def register():
|
|||||||
session['admin'] = team.admin
|
session['admin'] = team.admin
|
||||||
session['nonce'] = sha512(os.urandom(10))
|
session['nonce'] = sha512(os.urandom(10))
|
||||||
|
|
||||||
if can_send_mail() and get_config('verify_emails'):
|
if can_send_mail() and get_config('verify_emails'): ## Confirming users is enabled and we can send email.
|
||||||
verify_email(team.email)
|
db.session.close()
|
||||||
else:
|
logger = logging.getLogger('regs')
|
||||||
if can_send_mail():
|
logger.warn("[{0}] {1} registered (UNCONFIRMED) with {2}".format(time.strftime("%m/%d/%Y %X"),
|
||||||
|
request.form['name'].encode('utf-8'),
|
||||||
|
request.form['email'].encode('utf-8')))
|
||||||
|
return redirect(url_for('auth.confirm_user'))
|
||||||
|
else: ## Don't care about confirming users
|
||||||
|
if can_send_mail(): ## We want to notify the user that they have registered.
|
||||||
sendmail(request.form['email'], "You've successfully registered for {}".format(get_config('ctf_name')))
|
sendmail(request.form['email'], "You've successfully registered for {}".format(get_config('ctf_name')))
|
||||||
|
|
||||||
db.session.close()
|
db.session.close()
|
||||||
@@ -142,6 +156,7 @@ def login():
|
|||||||
errors = []
|
errors = []
|
||||||
name = request.form['name']
|
name = request.form['name']
|
||||||
team = Teams.query.filter_by(name=name).first()
|
team = Teams.query.filter_by(name=name).first()
|
||||||
|
if team:
|
||||||
if team and bcrypt_sha256.verify(request.form['password'], team.password):
|
if team and bcrypt_sha256.verify(request.form['password'], team.password):
|
||||||
try:
|
try:
|
||||||
session.regenerate() # NO SESSION FIXATION FOR YOU
|
session.regenerate() # NO SESSION FIXATION FOR YOU
|
||||||
@@ -159,10 +174,14 @@ def login():
|
|||||||
if request.args.get('next') and is_safe_url(request.args.get('next')):
|
if request.args.get('next') and is_safe_url(request.args.get('next')):
|
||||||
return redirect(request.args.get('next'))
|
return redirect(request.args.get('next'))
|
||||||
return redirect(url_for('challenges.challenges_view'))
|
return redirect(url_for('challenges.challenges_view'))
|
||||||
else:
|
else: # This user exists but the password is wrong
|
||||||
errors.append("That account doesn't seem to exist")
|
errors.append("That account doesn't seem to exist")
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return render_template('login.html', errors=errors)
|
return render_template('login.html', errors=errors)
|
||||||
|
else: # This user just doesn't exist
|
||||||
|
errors.append("Your username or password is incorrect")
|
||||||
|
db.session.close()
|
||||||
|
return render_template('login.html', errors=errors)
|
||||||
else:
|
else:
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from flask import current_app as app, render_template, request, redirect, abort, jsonify, json as json_mod, url_for, session, Blueprint
|
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, get_ip, is_verified
|
from CTFd.utils import ctftime, view_after_ctf, authed, unix_time, get_kpm, user_can_view_challenges, is_admin, get_config, get_ip, is_verified, ctf_started, ctf_ended, ctf_name
|
||||||
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards
|
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams, Awards
|
||||||
|
|
||||||
from sqlalchemy.sql import and_, or_, not_
|
from sqlalchemy.sql import and_, or_, not_
|
||||||
@@ -15,16 +15,26 @@ challenges = Blueprint('challenges', __name__)
|
|||||||
|
|
||||||
@challenges.route('/challenges', methods=['GET'])
|
@challenges.route('/challenges', methods=['GET'])
|
||||||
def challenges_view():
|
def challenges_view():
|
||||||
if not is_admin():
|
errors = []
|
||||||
|
start = get_config('start') or 0
|
||||||
|
end = get_config('end') or 0
|
||||||
|
if not is_admin(): # User is not an admin
|
||||||
if not ctftime():
|
if not ctftime():
|
||||||
if view_after_ctf():
|
# It is not CTF time
|
||||||
|
if view_after_ctf(): # But we are allowed to view after the CTF ends
|
||||||
pass
|
pass
|
||||||
else:
|
else: # We are NOT allowed to view after the CTF ends
|
||||||
|
errors.append('{} has ended'.format(ctf_name()))
|
||||||
|
return render_template('chals.html', errors=errors, start=int(start), end=int(end))
|
||||||
return redirect(url_for('views.static_html'))
|
return redirect(url_for('views.static_html'))
|
||||||
if get_config('verify_emails') and not is_verified():
|
if get_config('verify_emails') and not is_verified(): # User is not confirmed
|
||||||
return redirect(url_for('auth.confirm_user'))
|
return redirect(url_for('auth.confirm_user'))
|
||||||
if can_view_challenges():
|
if user_can_view_challenges(): # Do we allow unauthenticated users?
|
||||||
return render_template('chals.html', ctftime=ctftime())
|
if get_config('start') and not ctf_started():
|
||||||
|
errors.append('{} has not started yet'.format(ctf_name()))
|
||||||
|
if (get_config('end') and ctf_ended()) and not view_after_ctf():
|
||||||
|
errors.append('{} has ended'.format(ctf_name()))
|
||||||
|
return render_template('chals.html', errors=errors, start=int(start), end=int(end))
|
||||||
else:
|
else:
|
||||||
return redirect(url_for('auth.login', next='challenges'))
|
return redirect(url_for('auth.login', next='challenges'))
|
||||||
|
|
||||||
@@ -37,7 +47,7 @@ def chals():
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return redirect(url_for('views.static_html'))
|
return redirect(url_for('views.static_html'))
|
||||||
if can_view_challenges():
|
if user_can_view_challenges():
|
||||||
chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
|
chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).add_columns('id', 'name', 'value', 'description', 'category').order_by(Challenges.value).all()
|
||||||
|
|
||||||
json = {'game':[]}
|
json = {'game':[]}
|
||||||
@@ -55,7 +65,8 @@ def chals():
|
|||||||
|
|
||||||
@challenges.route('/chals/solves')
|
@challenges.route('/chals/solves')
|
||||||
def chals_per_solves():
|
def chals_per_solves():
|
||||||
if can_view_challenges():
|
if not user_can_view_challenges():
|
||||||
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves')).join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).group_by(Solves.chalid).subquery()
|
solves_sub = db.session.query(Solves.chalid, db.func.count(Solves.chalid).label('solves')).join(Teams, Solves.teamid == Teams.id).filter(Teams.banned == False).group_by(Solves.chalid).subquery()
|
||||||
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \
|
solves = db.session.query(solves_sub.columns.chalid, solves_sub.columns.solves, Challenges.name) \
|
||||||
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
.join(Challenges, solves_sub.columns.chalid == Challenges.id).all()
|
||||||
@@ -64,12 +75,14 @@ def chals_per_solves():
|
|||||||
json[name] = count
|
json[name] = count
|
||||||
db.session.close()
|
db.session.close()
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
return redirect(url_for('auth.login', next='chals/solves'))
|
|
||||||
|
|
||||||
|
|
||||||
@challenges.route('/solves')
|
@challenges.route('/solves')
|
||||||
@challenges.route('/solves/<teamid>')
|
@challenges.route('/solves/<teamid>')
|
||||||
def solves(teamid=None):
|
def solves(teamid=None):
|
||||||
|
if not user_can_view_challenges():
|
||||||
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
solves = None
|
solves = None
|
||||||
awards = None
|
awards = None
|
||||||
if teamid is None:
|
if teamid is None:
|
||||||
@@ -109,6 +122,8 @@ def solves(teamid=None):
|
|||||||
|
|
||||||
@challenges.route('/maxattempts')
|
@challenges.route('/maxattempts')
|
||||||
def attempts():
|
def attempts():
|
||||||
|
if not user_can_view_challenges():
|
||||||
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
chals = Challenges.query.add_columns('id').all()
|
chals = Challenges.query.add_columns('id').all()
|
||||||
json = {'maxattempts':[]}
|
json = {'maxattempts':[]}
|
||||||
for chal, chalid in chals:
|
for chal, chalid in chals:
|
||||||
@@ -120,6 +135,8 @@ def attempts():
|
|||||||
|
|
||||||
@challenges.route('/fails/<teamid>', methods=['GET'])
|
@challenges.route('/fails/<teamid>', methods=['GET'])
|
||||||
def fails(teamid):
|
def fails(teamid):
|
||||||
|
if not user_can_view_challenges():
|
||||||
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
fails = WrongKeys.query.filter_by(teamid=teamid).count()
|
fails = WrongKeys.query.filter_by(teamid=teamid).count()
|
||||||
solves = Solves.query.filter_by(teamid=teamid).count()
|
solves = Solves.query.filter_by(teamid=teamid).count()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
@@ -129,6 +146,8 @@ def fails(teamid):
|
|||||||
|
|
||||||
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
|
@challenges.route('/chal/<chalid>/solves', methods=['GET'])
|
||||||
def who_solved(chalid):
|
def who_solved(chalid):
|
||||||
|
if not user_can_view_challenges():
|
||||||
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid, Teams.banned == False).order_by(Solves.date.asc())
|
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Solves.chalid == chalid, Teams.banned == False).order_by(Solves.date.asc())
|
||||||
json = {'teams':[]}
|
json = {'teams':[]}
|
||||||
for solve in solves:
|
for solve in solves:
|
||||||
@@ -138,9 +157,11 @@ def who_solved(chalid):
|
|||||||
|
|
||||||
@challenges.route('/chal/<chalid>', methods=['POST'])
|
@challenges.route('/chal/<chalid>', methods=['POST'])
|
||||||
def chal(chalid):
|
def chal(chalid):
|
||||||
if not ctftime():
|
if ctf_ended() and not view_after_ctf():
|
||||||
return redirect(url_for('challenges.challenges_view'))
|
return redirect(url_for('challenges.challenges_view'))
|
||||||
if authed():
|
if not user_can_view_challenges():
|
||||||
|
return redirect(url_for('auth.login', next=request.path))
|
||||||
|
if authed() and is_verified() and (ctf_started() or view_after_ctf()):
|
||||||
fails = WrongKeys.query.filter_by(teamid=session['id'], chalid=chalid).count()
|
fails = WrongKeys.query.filter_by(teamid=session['id'], chalid=chalid).count()
|
||||||
logger = logging.getLogger('keys')
|
logger = logging.getLogger('keys')
|
||||||
data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id']))
|
data = (time.strftime("%m/%d/%Y %X"), session['username'].encode('utf-8'), request.form['key'].encode('utf-8'), get_kpm(session['id']))
|
||||||
@@ -148,6 +169,7 @@ def chal(chalid):
|
|||||||
|
|
||||||
# Anti-bruteforce / submitting keys too quickly
|
# Anti-bruteforce / submitting keys too quickly
|
||||||
if get_kpm(session['id']) > 10:
|
if get_kpm(session['id']) > 10:
|
||||||
|
if ctftime():
|
||||||
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||||
db.session.add(wrong)
|
db.session.add(wrong)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -176,6 +198,7 @@ def chal(chalid):
|
|||||||
if x['type'] == 0: #static key
|
if x['type'] == 0: #static key
|
||||||
print(x['flag'], key.strip().lower())
|
print(x['flag'], key.strip().lower())
|
||||||
if x['flag'] and x['flag'].strip().lower() == key.strip().lower():
|
if x['flag'] and x['flag'].strip().lower() == key.strip().lower():
|
||||||
|
if ctftime():
|
||||||
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=key)
|
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=key)
|
||||||
db.session.add(solve)
|
db.session.add(solve)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -186,6 +209,7 @@ def chal(chalid):
|
|||||||
elif x['type'] == 1: #regex
|
elif x['type'] == 1: #regex
|
||||||
res = re.match(str(x['flag']), key, re.IGNORECASE)
|
res = re.match(str(x['flag']), key, re.IGNORECASE)
|
||||||
if res and res.group() == key:
|
if res and res.group() == key:
|
||||||
|
if ctftime():
|
||||||
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=key)
|
solve = Solves(chalid=chalid, teamid=session['id'], ip=get_ip(), flag=key)
|
||||||
db.session.add(solve)
|
db.session.add(solve)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -194,6 +218,7 @@ def chal(chalid):
|
|||||||
# return "1" # key was correct
|
# return "1" # key was correct
|
||||||
return jsonify({'status': '1', 'message': 'Correct'})
|
return jsonify({'status': '1', 'message': 'Correct'})
|
||||||
|
|
||||||
|
if ctftime():
|
||||||
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
wrong = WrongKeys(session['id'], chalid, request.form['key'])
|
||||||
db.session.add(wrong)
|
db.session.add(wrong)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ with open('.ctfd_secret_key', 'a+') as secret:
|
|||||||
|
|
||||||
##### SERVER SETTINGS #####
|
##### SERVER SETTINGS #####
|
||||||
SECRET_KEY = key
|
SECRET_KEY = key
|
||||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///ctfd.db'
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///ctfd.db'
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
SESSION_TYPE = "filesystem"
|
SESSION_TYPE = "filesystem"
|
||||||
SESSION_FILE_DIR = "/tmp/flask_session"
|
SESSION_FILE_DIR = "/tmp/flask_session"
|
||||||
@@ -31,3 +31,7 @@ TRUSTED_PROXIES = [
|
|||||||
'^172\.(1[6-9]|2[0-9]|3[0-1])\.',
|
'^172\.(1[6-9]|2[0-9]|3[0-1])\.',
|
||||||
'^192\.168\.'
|
'^192\.168\.'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CACHE_TYPE = "simple"
|
||||||
|
if CACHE_TYPE == 'redis':
|
||||||
|
CACHE_REDIS_URL = os.environ.get('REDIS_URL')
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ class Teams(db.Model):
|
|||||||
banned = db.Column(db.Boolean, default=False)
|
banned = db.Column(db.Boolean, default=False)
|
||||||
verified = db.Column(db.Boolean, default=False)
|
verified = db.Column(db.Boolean, default=False)
|
||||||
admin = db.Column(db.Boolean, default=False)
|
admin = db.Column(db.Boolean, default=False)
|
||||||
|
joined = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
def __init__(self, name, email, password):
|
def __init__(self, name, email, password):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ $('#create-key').click(function(e){
|
|||||||
var elem = $('<div class="col-md-4">');
|
var elem = $('<div class="col-md-4">');
|
||||||
|
|
||||||
elem.append($("<div class='form-group'>").append($("<input class='current-key form-control' type='text'>")));
|
elem.append($("<div class='form-group'>").append($("<input class='current-key form-control' type='text'>")));
|
||||||
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+amt+']" value="0">Static</div>');
|
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+amt+']" value="0" checked>Static</div>');
|
||||||
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+amt+']" value="1">Regex</div>');
|
elem.append('<div class="radio-inline"><input type="radio" name="key_type['+amt+']" value="1">Regex</div>');
|
||||||
elem.append('<a href="#" onclick="$(this).parent().remove()" class="btn btn-danger key-remove-button">Remove</a>');
|
elem.append('<a href="#" onclick="$(this).parent().remove()" class="btn btn-danger key-remove-button">Remove</a>');
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="value">Value</label>
|
<label for="value">Value</label>
|
||||||
<input type="number" class="form-control" name="value" placeholder="Enter value">
|
<input type="number" class="form-control" name="value" placeholder="Enter value" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="height:75px">
|
<div class="form-group" style="height:75px">
|
||||||
<div class="col-md-9" style="padding-left:0px">
|
<div class="col-md-9" style="padding-left:0px">
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||||
<div style="text-align:center">
|
<div style="text-align:center">
|
||||||
<button class="btn btn-theme btn-outlined create-challenge" type="submit">Create</button>
|
<button class="btn btn-theme btn-outlined create-challenge-submit" type="submit">Create</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="value">Value</label>
|
<label for="value">Value</label>
|
||||||
<input type="number" class="form-control chal-value" name="value" placeholder="Enter value">
|
<input type="number" class="form-control chal-value" name="value" placeholder="Enter value" required>
|
||||||
</div>
|
</div>
|
||||||
<input class="chal-id" type='hidden' name='id' placeholder='ID'>
|
<input class="chal-id" type='hidden' name='id' placeholder='ID'>
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||||
<div style="text-align:center">
|
<div style="text-align:center">
|
||||||
<button class="btn btn-theme btn-outlined create-challenge" type="submit">Update</button>
|
<button class="btn btn-theme btn-outlined update-challenge-submit" type="submit">Update</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,11 +67,11 @@ input[type="checkbox"] { margin: 0px !important; position: relative; top: 5px; }
|
|||||||
<input type="hidden" name="id">
|
<input type="hidden" name="id">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">Team Name</label>
|
<label for="name">Team Name</label>
|
||||||
<input type="text" class="form-control" name="name" id="name" placeholder="Enter new team name">
|
<input type="text" class="form-control" name="name" id="name" placeholder="Enter new team name" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email</label>
|
<label for="email">Email</label>
|
||||||
<input type="email" class="form-control" name="email" id="email" placeholder="Enter new email">
|
<input type="email" class="form-control" name="email" id="email" placeholder="Enter new email" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
|
|||||||
@@ -80,6 +80,17 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
{% if errors %}
|
||||||
|
<div class="container main-container">
|
||||||
|
<div id='errors' class="row">
|
||||||
|
{% for error in errors %}
|
||||||
|
<h1>{{ error }}</h1>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
<div class="jumbotron home">
|
<div class="jumbotron home">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Challenges</h1>
|
<h1>Challenges</h1>
|
||||||
@@ -147,10 +158,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/utils.js"></script>
|
||||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/chalboard.js"></script>
|
{% if not errors %}<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/chalboard.js"></script>{% endif %}
|
||||||
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/style.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
75
CTFd/templates/original/page.html
Normal file
75
CTFd/templates/original/page.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ ctf_name() }}</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="shortcut icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico"
|
||||||
|
type="image/x-icon">
|
||||||
|
<link rel="icon" href="{{ request.script_root }}/static/{{ ctf_theme() }}/img/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/font-awesome/css/font-awesome.min.css"/>
|
||||||
|
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/lato.css' rel='stylesheet'
|
||||||
|
type='text/css'>
|
||||||
|
<link href='{{ request.script_root }}/static/{{ ctf_theme() }}/css/vendor/raleway.css' rel='stylesheet'
|
||||||
|
type='text/css'>
|
||||||
|
<link rel="stylesheet" href="{{ request.script_root }}/static/{{ ctf_theme() }}/css/style.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/user.css">
|
||||||
|
{% block stylesheets %}{% endblock %}
|
||||||
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/moment.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var script_root = "{{ request.script_root }}";
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="body-container">
|
||||||
|
<div class="navbar navbar-inverse home">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button class="navbar-toggle" data-target=".navbar-collapse" data-toggle="collapse" type="button">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a href="{{ request.script_root }}/" class="navbar-brand">{{ ctf_name() }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-collapse collapse" aria-expanded="false" style="height: 0px">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
{% for page in pages() %}
|
||||||
|
<li><a href="{{ request.script_root }}/{{ page.route }}">{{ page.route|title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
<li><a href="{{ request.script_root }}/teams">Teams</a></li>
|
||||||
|
<li><a href="{{ request.script_root }}/scoreboard">Scoreboard</a></li>
|
||||||
|
<li><a href="{{ request.script_root }}/challenges">Challenges</a></li>
|
||||||
|
</ul>
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
{% if username is defined %}
|
||||||
|
{% if admin %}
|
||||||
|
<li><a href="{{ request.script_root }}/admin">Admin</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><a href="{{ request.script_root }}/team/{{ id }}">Team</a></li>
|
||||||
|
<li><a href="{{ request.script_root }}/profile">Profile</a></li>
|
||||||
|
<li><a href="{{ request.script_root }}/logout">Logout</a></li>
|
||||||
|
{% else %}
|
||||||
|
{% if can_register() %}
|
||||||
|
<li><a href="{{ request.script_root }}/register">Register</a></li>
|
||||||
|
<li><a style="padding-left:0px;padding-right:0px;">|</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><a href="{{ request.script_root }}/login">Login</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ content | safe }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/jquery.min.js"></script>
|
||||||
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/marked.min.js"></script>
|
||||||
|
<script src="{{ request.script_root }}/static/{{ ctf_theme() }}/js/vendor/bootstrap.min.js"></script>
|
||||||
|
{% block scripts %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -43,6 +43,10 @@
|
|||||||
<div class="alert alert-info alert-dismissable submit-row" role="alert">
|
<div class="alert alert-info alert-dismissable submit-row" role="alert">
|
||||||
Your email address isn't confirmed!
|
Your email address isn't confirmed!
|
||||||
Please check your email to confirm your email address.
|
Please check your email to confirm your email address.
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
To have the confirmation email reset please <a href="{{ request.script_root }}/confirm">click
|
||||||
|
here.</a>
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||||
aria-hidden="true">×</span></button>
|
aria-hidden="true">×</span></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,7 +77,7 @@
|
|||||||
<span class="input">
|
<span class="input">
|
||||||
<input class="input-field" type="password" name="confirm" id="confirm-input" />
|
<input class="input-field" type="password" name="confirm" id="confirm-input" />
|
||||||
<label class="input-label" for="confirm-input">
|
<label class="input-label" for="confirm-input">
|
||||||
<span class="label-content" data-content="Password">Password</span>
|
<span class="label-content" data-content="Password">Current Password</span>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Containers, ip2long, long2ip
|
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Containers, ip2long, long2ip
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse, urljoin
|
from six.moves.urllib.parse import urlparse, urljoin
|
||||||
|
import six
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import current_app as app, g, request, redirect, url_for, session, render_template, abort
|
from flask import current_app as app, g, request, redirect, url_for, session, render_template, abort
|
||||||
|
from flask_cache import Cache
|
||||||
from itsdangerous import Signer, BadSignature
|
from itsdangerous import Signer, BadSignature
|
||||||
from socket import inet_aton, inet_ntoa, socket
|
from socket import inet_aton, inet_ntoa, socket
|
||||||
from struct import unpack, pack, error
|
from struct import unpack, pack, error
|
||||||
@@ -24,8 +26,11 @@ import smtplib
|
|||||||
import email
|
import email
|
||||||
import tempfile
|
import tempfile
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import urllib
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
cache = Cache()
|
||||||
|
|
||||||
|
|
||||||
def init_logs(app):
|
def init_logs(app):
|
||||||
logger_keys = logging.getLogger('keys')
|
logger_keys = logging.getLogger('keys')
|
||||||
@@ -132,11 +137,13 @@ def init_utils(app):
|
|||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def ctf_name():
|
def ctf_name():
|
||||||
name = get_config('ctf_name')
|
name = get_config('ctf_name')
|
||||||
return name if name else 'CTFd'
|
return name if name else 'CTFd'
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def ctf_theme():
|
def ctf_theme():
|
||||||
theme = get_config('ctf_theme')
|
theme = get_config('ctf_theme')
|
||||||
return theme if theme else ''
|
return theme if theme else ''
|
||||||
@@ -152,13 +159,17 @@ def authed():
|
|||||||
|
|
||||||
|
|
||||||
def is_verified():
|
def is_verified():
|
||||||
|
if get_config('verify_emails'):
|
||||||
team = Teams.query.filter_by(id=session.get('id')).first()
|
team = Teams.query.filter_by(id=session.get('id')).first()
|
||||||
if team:
|
if team:
|
||||||
return team.verified
|
return team.verified
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def is_setup():
|
def is_setup():
|
||||||
setup = Config.query.filter_by(key='setup').first()
|
setup = Config.query.filter_by(key='setup').first()
|
||||||
if setup:
|
if setup:
|
||||||
@@ -174,12 +185,9 @@ def is_admin():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def can_register():
|
def can_register():
|
||||||
config = Config.query.filter_by(key='prevent_registration').first()
|
return not bool(get_config('prevent_registration'))
|
||||||
if config:
|
|
||||||
return config.value != '1'
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def admins_only(f):
|
def admins_only(f):
|
||||||
@@ -192,11 +200,9 @@ def admins_only(f):
|
|||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def view_after_ctf():
|
def view_after_ctf():
|
||||||
if get_config('view_after_ctf') == '1' and time.time() > int(get_config("end")):
|
return bool(get_config('view_after_ctf'))
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def ctftime():
|
def ctftime():
|
||||||
@@ -234,10 +240,19 @@ def ctftime():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def can_view_challenges():
|
def ctf_started():
|
||||||
config = Config.query.filter_by(key="view_challenges_unregistered").first()
|
return time.time() > int(get_config("start") or 0)
|
||||||
|
|
||||||
|
|
||||||
|
def ctf_ended():
|
||||||
|
return time.time() > int(get_config("end") or 0)
|
||||||
|
|
||||||
|
|
||||||
|
def user_can_view_challenges():
|
||||||
|
config = bool(get_config('view_challenges_unregistered'))
|
||||||
|
verify_emails = bool(get_config('verify_emails'))
|
||||||
if config:
|
if config:
|
||||||
return authed() or config.value == '1'
|
return authed() or config
|
||||||
else:
|
else:
|
||||||
return authed()
|
return authed()
|
||||||
|
|
||||||
@@ -284,12 +299,18 @@ def get_themes():
|
|||||||
if os.path.isdir(os.path.join(dir, name))]
|
if os.path.isdir(os.path.join(dir, name))]
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def get_config(key):
|
def get_config(key):
|
||||||
config = Config.query.filter_by(key=key).first()
|
config = Config.query.filter_by(key=key).first()
|
||||||
if config and config.value:
|
if config and config.value:
|
||||||
value = config.value
|
value = config.value
|
||||||
if value and value.isdigit():
|
if value and value.isdigit():
|
||||||
return int(value)
|
return int(value)
|
||||||
|
elif value and isinstance(value, six.string_types):
|
||||||
|
if value.lower() == 'true':
|
||||||
|
return True
|
||||||
|
elif value.lower() == 'false':
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
@@ -308,10 +329,12 @@ def set_config(key, value):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def can_send_mail():
|
def can_send_mail():
|
||||||
return mailgun() or mailserver()
|
return mailgun() or mailserver()
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def mailgun():
|
def mailgun():
|
||||||
if app.config.get('MAILGUN_API_KEY') and app.config.get('MAILGUN_BASE_URL'):
|
if app.config.get('MAILGUN_API_KEY') and app.config.get('MAILGUN_BASE_URL'):
|
||||||
return True
|
return True
|
||||||
@@ -319,6 +342,8 @@ def mailgun():
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def mailserver():
|
def mailserver():
|
||||||
if (get_config('mail_server') and get_config('mail_port')):
|
if (get_config('mail_server') and get_config('mail_port')):
|
||||||
return True
|
return True
|
||||||
@@ -384,7 +409,7 @@ def verify_email(addr):
|
|||||||
token = s.sign(addr)
|
token = s.sign(addr)
|
||||||
text = """Please click the following link to confirm your email address for {}: {}""".format(
|
text = """Please click the following link to confirm your email address for {}: {}""".format(
|
||||||
get_config('ctf_name'),
|
get_config('ctf_name'),
|
||||||
url_for('auth.confirm_user', _external=True) + '/' + token.encode('base64')
|
url_for('auth.confirm_user', _external=True) + '/' + urllib.quote_plus(token.encode('base64'))
|
||||||
)
|
)
|
||||||
sendmail(addr, text)
|
sendmail(addr, text)
|
||||||
|
|
||||||
@@ -407,6 +432,7 @@ def sha512(string):
|
|||||||
return hashlib.sha512(string).hexdigest()
|
return hashlib.sha512(string).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
def can_create_container():
|
def can_create_container():
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(['docker', 'version'])
|
output = subprocess.check_output(['docker', 'version'])
|
||||||
|
|||||||
@@ -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, Blueprint, Response
|
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, set_config, sha512, get_ip
|
from CTFd.utils import authed, ip2long, long2ip, is_setup, validate_url, get_config, set_config, sha512, get_ip, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
|
||||||
|
|
||||||
from jinja2.exceptions import TemplateNotFound
|
from jinja2.exceptions import TemplateNotFound
|
||||||
@@ -90,6 +90,8 @@ def setup():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
app.setup = False
|
app.setup = False
|
||||||
|
with app.app_context():
|
||||||
|
cache.clear()
|
||||||
return redirect(url_for('views.static_html'))
|
return redirect(url_for('views.static_html'))
|
||||||
return render_template('setup.html', nonce=session.get('nonce'))
|
return render_template('setup.html', nonce=session.get('nonce'))
|
||||||
return redirect(url_for('views.static_html'))
|
return redirect(url_for('views.static_html'))
|
||||||
@@ -110,7 +112,7 @@ def static_html(template):
|
|||||||
except TemplateNotFound:
|
except TemplateNotFound:
|
||||||
page = Pages.query.filter_by(route=template).first()
|
page = Pages.query.filter_by(route=template).first()
|
||||||
if page:
|
if page:
|
||||||
return render_template_string('{% extends "base.html" %}{% block content %}' + page.html + '{% endblock %}')
|
return render_template('page.html', content=page.html)
|
||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
Flask
|
Flask
|
||||||
Flask-SQLAlchemy
|
Flask-SQLAlchemy
|
||||||
Flask-Session
|
Flask-Session
|
||||||
|
Flask-Caching
|
||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
sqlalchemy-utils
|
sqlalchemy-utils
|
||||||
passlib
|
passlib
|
||||||
|
|||||||
Reference in New Issue
Block a user