mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 05:54:19 +01:00
Adding email verification
This commit has some model changes. It could be difficult to upgrade to this commit.
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
from flask import Flask, render_template, request, redirect, abort, session, jsonify, json as json_mod, url_for
|
from flask import Flask, render_template, request, redirect, abort, session, jsonify, json as json_mod, url_for
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
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 os
|
import os
|
||||||
@@ -19,9 +18,6 @@ def create_app(config='CTFd.config'):
|
|||||||
|
|
||||||
app.db = db
|
app.db = db
|
||||||
|
|
||||||
global mail
|
|
||||||
mail = Mail(app)
|
|
||||||
|
|
||||||
#Session(app)
|
#Session(app)
|
||||||
|
|
||||||
from CTFd.views import views
|
from CTFd.views import views
|
||||||
|
|||||||
@@ -69,21 +69,38 @@ def admin_config():
|
|||||||
prevent_registration = bool(request.form.get('prevent_registration', None))
|
prevent_registration = bool(request.form.get('prevent_registration', None))
|
||||||
prevent_name_change = bool(request.form.get('prevent_name_change', None))
|
prevent_name_change = bool(request.form.get('prevent_name_change', None))
|
||||||
view_after_ctf = bool(request.form.get('view_after_ctf', None))
|
view_after_ctf = bool(request.form.get('view_after_ctf', None))
|
||||||
|
verify_emails = bool(request.form.get('verify_emails', None))
|
||||||
|
mail_tls = bool(request.form.get('mail_tls', None))
|
||||||
|
mail_ssl = bool(request.form.get('mail_ssl', None))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
view_challenges_unregistered = None
|
view_challenges_unregistered = None
|
||||||
prevent_registration = None
|
prevent_registration = None
|
||||||
prevent_name_change = None
|
prevent_name_change = None
|
||||||
view_after_ctf = None
|
view_after_ctf = None
|
||||||
|
verify_emails = None
|
||||||
|
mail_tls = None
|
||||||
|
mail_ssl = None
|
||||||
finally:
|
finally:
|
||||||
view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
|
view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
|
||||||
prevent_registration = set_config('prevent_registration', prevent_registration)
|
prevent_registration = set_config('prevent_registration', prevent_registration)
|
||||||
prevent_name_change = set_config('prevent_name_change', prevent_name_change)
|
prevent_name_change = set_config('prevent_name_change', prevent_name_change)
|
||||||
view_after_ctf = set_config('view_after_ctf', view_after_ctf)
|
view_after_ctf = set_config('view_after_ctf', view_after_ctf)
|
||||||
|
verify_emails = set_config('verify_emails', verify_emails)
|
||||||
|
mail_tls = set_config('mail_tls', mail_tls)
|
||||||
|
mail_ssl = set_config('mail_ssl', mail_ssl)
|
||||||
|
|
||||||
|
mail_server = set_config("mail_server", request.form.get('mail_server', None))
|
||||||
|
mail_port = set_config("mail_port", request.form.get('mail_port', None))
|
||||||
|
|
||||||
|
mail_username = set_config("mail_username", request.form.get('mail_username', None))
|
||||||
|
mail_password = set_config("mail_password", request.form.get('mail_password', None))
|
||||||
|
|
||||||
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))
|
|
||||||
max_tries = set_config("max_tries", request.form.get('max_tries', None))
|
|
||||||
|
|
||||||
|
mg_base_url = set_config("mg_base_url", request.form.get('mg_base_url', None))
|
||||||
|
mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
|
||||||
|
|
||||||
|
max_tries = set_config("max_tries", request.form.get('max_tries', None))
|
||||||
|
|
||||||
db_start = Config.query.filter_by(key='start').first()
|
db_start = Config.query.filter_by(key='start').first()
|
||||||
db_start.value = start
|
db_start.value = start
|
||||||
@@ -98,42 +115,30 @@ def admin_config():
|
|||||||
return redirect(url_for('admin.admin_config'))
|
return redirect(url_for('admin.admin_config'))
|
||||||
|
|
||||||
ctf_name = get_config('ctf_name')
|
ctf_name = get_config('ctf_name')
|
||||||
if not ctf_name:
|
|
||||||
set_config('ctf_name', None)
|
mail_server = get_config('mail_server')
|
||||||
|
mail_port = get_config('mail_port')
|
||||||
|
mail_username = get_config('mail_username')
|
||||||
|
mail_password = get_config('mail_password')
|
||||||
|
|
||||||
mg_api_key = get_config('mg_api_key')
|
mg_api_key = get_config('mg_api_key')
|
||||||
if not mg_api_key:
|
mg_base_url = get_config('mg_base_url')
|
||||||
set_config('mg_api_key', None)
|
|
||||||
|
|
||||||
max_tries = get_config('max_tries')
|
max_tries = get_config('max_tries')
|
||||||
if not max_tries:
|
if not max_tries:
|
||||||
set_config('max_tries', 0)
|
set_config('max_tries', 0)
|
||||||
max_tries = 0
|
max_tries = 0
|
||||||
|
|
||||||
view_after_ctf = get_config('view_after_ctf') == '1'
|
view_after_ctf = get_config('view_after_ctf')
|
||||||
if not view_after_ctf:
|
|
||||||
set_config('view_after_ctf', 0)
|
|
||||||
view_after_ctf = 0
|
|
||||||
|
|
||||||
start = get_config('start')
|
start = get_config('start')
|
||||||
if not start:
|
|
||||||
set_config('start', None)
|
|
||||||
|
|
||||||
end = get_config('end')
|
end = get_config('end')
|
||||||
if not end:
|
|
||||||
set_config('end', None)
|
|
||||||
|
|
||||||
view_challenges_unregistered = get_config('view_challenges_unregistered') == '1'
|
mail_tls = get_config('mail_tls')
|
||||||
if not view_challenges_unregistered:
|
mail_ssl = get_config('mail_ssl')
|
||||||
set_config('view_challenges_unregistered', None)
|
|
||||||
|
|
||||||
prevent_registration = get_config('prevent_registration') == '1'
|
view_challenges_unregistered = get_config('view_challenges_unregistered')
|
||||||
if not prevent_registration:
|
prevent_registration = get_config('prevent_registration')
|
||||||
set_config('prevent_registration', None)
|
prevent_name_change = get_config('prevent_name_change')
|
||||||
|
verify_emails = get_config('verify_emails')
|
||||||
prevent_name_change = get_config('prevent_name_change') == '1'
|
|
||||||
if not prevent_name_change:
|
|
||||||
set_config('prevent_name_change', None)
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
@@ -155,12 +160,27 @@ def admin_config():
|
|||||||
end = datetime.datetime.fromtimestamp(float(end))
|
end = datetime.datetime.fromtimestamp(float(end))
|
||||||
end_days = calendar.monthrange(end.year, end.month)[1]
|
end_days = calendar.monthrange(end.year, end.month)[1]
|
||||||
|
|
||||||
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,
|
||||||
|
mail_server=mail_server,
|
||||||
|
mail_port=mail_port,
|
||||||
|
mail_username=mail_username,
|
||||||
|
mail_password=mail_password,
|
||||||
|
mail_tls=mail_tls,
|
||||||
|
mail_ssl=mail_ssl,
|
||||||
view_challenges_unregistered=view_challenges_unregistered,
|
view_challenges_unregistered=view_challenges_unregistered,
|
||||||
prevent_registration=prevent_registration, mg_api_key=mg_api_key,
|
prevent_registration=prevent_registration,
|
||||||
|
mg_base_url=mg_base_url,
|
||||||
|
mg_api_key=mg_api_key,
|
||||||
prevent_name_change=prevent_name_change,
|
prevent_name_change=prevent_name_change,
|
||||||
view_after_ctf=view_after_ctf, months=months, curr_year=curr_year, start_days=start_days,
|
verify_emails=verify_emails,
|
||||||
|
view_after_ctf=view_after_ctf,
|
||||||
|
months=months,
|
||||||
|
curr_year=curr_year,
|
||||||
|
start_days=start_days,
|
||||||
end_days=end_days)
|
end_days=end_days)
|
||||||
|
|
||||||
|
|
||||||
@@ -371,7 +391,7 @@ def admin_team(teamid):
|
|||||||
solve_ids = [s.chalid for s in solves]
|
solve_ids = [s.chalid for s in solves]
|
||||||
missing = Challenges.query.filter( not_(Challenges.id.in_(solve_ids) ) ).all()
|
missing = Challenges.query.filter( not_(Challenges.id.in_(solve_ids) ) ).all()
|
||||||
addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all()
|
addrs = Tracking.query.filter_by(team=teamid).order_by(Tracking.date.desc()).group_by(Tracking.ip).all()
|
||||||
wrong_keys = WrongKeys.query.filter_by(team=teamid).order_by(WrongKeys.date.desc()).all()
|
wrong_keys = WrongKeys.query.filter_by(teamid=teamid).order_by(WrongKeys.date.desc()).all()
|
||||||
score = user.score()
|
score = user.score()
|
||||||
place = user.place()
|
place = user.place()
|
||||||
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
return render_template('admin/team.html', solves=solves, team=user, addrs=addrs, score=score, missing=missing,
|
||||||
@@ -451,7 +471,7 @@ def unban(teamid):
|
|||||||
@admins_only
|
@admins_only
|
||||||
def delete_team(teamid):
|
def delete_team(teamid):
|
||||||
try:
|
try:
|
||||||
WrongKeys.query.filter_by(team=teamid).delete()
|
WrongKeys.query.filter_by(teamid=teamid).delete()
|
||||||
Solves.query.filter_by(teamid=teamid).delete()
|
Solves.query.filter_by(teamid=teamid).delete()
|
||||||
Tracking.query.filter_by(team=teamid).delete()
|
Tracking.query.filter_by(team=teamid).delete()
|
||||||
Teams.query.filter_by(id=teamid).delete()
|
Teams.query.filter_by(id=teamid).delete()
|
||||||
@@ -565,7 +585,7 @@ def admin_wrong_key(page='1'):
|
|||||||
page_start = results_per_page * ( page - 1 )
|
page_start = results_per_page * ( page - 1 )
|
||||||
page_end = results_per_page * ( page - 1 ) + results_per_page
|
page_end = results_per_page * ( page - 1 ) + results_per_page
|
||||||
|
|
||||||
wrong_keys = WrongKeys.query.add_columns(WrongKeys.chalid, WrongKeys.flag, WrongKeys.team, WrongKeys.date,\
|
wrong_keys = WrongKeys.query.add_columns(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('team_name ASC').slice(page_start, page_end).all()
|
||||||
|
|
||||||
@@ -597,13 +617,13 @@ def admin_correct_key(page='1'):
|
|||||||
@admins_only
|
@admins_only
|
||||||
def admin_fails(teamid='all'):
|
def admin_fails(teamid='all'):
|
||||||
if teamid == "all":
|
if teamid == "all":
|
||||||
fails = WrongKeys.query.join(Teams, WrongKeys.team == Teams.id).filter(Teams.banned==None).count()
|
fails = WrongKeys.query.join(Teams, WrongKeys.teamid == Teams.id).filter(Teams.banned==None).count()
|
||||||
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned==None).count()
|
solves = Solves.query.join(Teams, Solves.teamid == Teams.id).filter(Teams.banned==None).count()
|
||||||
db.session.close()
|
db.session.close()
|
||||||
json_data = {'fails':str(fails), 'solves': str(solves)}
|
json_data = {'fails':str(fails), 'solves': str(solves)}
|
||||||
return jsonify(json_data)
|
return jsonify(json_data)
|
||||||
else:
|
else:
|
||||||
fails = WrongKeys.query.filter_by(team=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()
|
||||||
json_data = {'fails':str(fails), 'solves': str(solves)}
|
json_data = {'fails':str(fails), 'solves': str(solves)}
|
||||||
|
|||||||
41
CTFd/auth.py
41
CTFd/auth.py
@@ -1,8 +1,8 @@
|
|||||||
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, mailserver, sendmail, can_register
|
from CTFd.utils import sha512, is_safe_url, authed, mailserver, sendmail, can_register, get_config, verify_email
|
||||||
from CTFd.models import db, Teams
|
from CTFd.models import db, Teams
|
||||||
|
|
||||||
from itsdangerous import TimedSerializer, BadTimeSignature
|
from itsdangerous import TimedSerializer, BadTimeSignature, Signer, BadSignature
|
||||||
from passlib.hash import bcrypt_sha256
|
from passlib.hash import bcrypt_sha256
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
|
|
||||||
@@ -14,6 +14,32 @@ import os
|
|||||||
auth = Blueprint('auth', __name__)
|
auth = Blueprint('auth', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@auth.route('/confirm', methods=['POST', 'GET'])
|
||||||
|
@auth.route('/confirm/<data>', methods=['GET'])
|
||||||
|
def confirm_user(data=None):
|
||||||
|
if not get_config('verify_emails'):
|
||||||
|
return redirect(url_for('challenges.challenges_view'))
|
||||||
|
if data and request.method == "GET": ## User is confirming email account
|
||||||
|
try:
|
||||||
|
s = Signer(app.config['SECRET_KEY'])
|
||||||
|
email = s.unsign(data.decode('base64'))
|
||||||
|
except BadSignature:
|
||||||
|
return render_template('confirm.html', errors=['Your confirmation link seems wrong'])
|
||||||
|
team = Teams.query.filter_by(email=email).first()
|
||||||
|
team.verified = True
|
||||||
|
db.session.commit()
|
||||||
|
db.session.close()
|
||||||
|
if authed():
|
||||||
|
return redirect(url_for('challenges.challenges_view'))
|
||||||
|
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
|
||||||
|
team = Teams.query.filter_by(id=session['id']).first()
|
||||||
|
if team.verified:
|
||||||
|
return redirect(url_for('views.profile'))
|
||||||
|
return render_template('confirm.html', team=team)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@auth.route('/reset_password', methods=['POST', 'GET'])
|
@auth.route('/reset_password', methods=['POST', 'GET'])
|
||||||
@auth.route('/reset_password/<data>', methods=['POST', 'GET'])
|
@auth.route('/reset_password/<data>', methods=['POST', 'GET'])
|
||||||
def reset_password(data=None):
|
def reset_password(data=None):
|
||||||
@@ -43,7 +69,7 @@ Did you initiate a password reset?
|
|||||||
|
|
||||||
{0}/reset_password/{1}
|
{0}/reset_password/{1}
|
||||||
|
|
||||||
""".format(app.config['HOST'], token.encode('base64'))
|
""".format(url_for('auth.reset_password', _external=True), token.encode('base64'))
|
||||||
|
|
||||||
sendmail(email, text)
|
sendmail(email, text)
|
||||||
|
|
||||||
@@ -85,7 +111,7 @@ def register():
|
|||||||
return render_template('register.html', errors=errors, name=request.form['name'], email=request.form['email'], password=request.form['password'])
|
return render_template('register.html', errors=errors, name=request.form['name'], email=request.form['email'], password=request.form['password'])
|
||||||
else:
|
else:
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
team = Teams(name, email, password)
|
team = Teams(name, email.lower(), password)
|
||||||
db.session.add(team)
|
db.session.add(team)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
@@ -95,8 +121,11 @@ def register():
|
|||||||
session['admin'] = team.admin
|
session['admin'] = team.admin
|
||||||
session['nonce'] = sha512(os.urandom(10))
|
session['nonce'] = sha512(os.urandom(10))
|
||||||
|
|
||||||
if mailserver():
|
if mailserver() and get_config('verify_emails'):
|
||||||
sendmail(request.form['email'], "You've successfully registered for the CTF")
|
verify_email(team.email)
|
||||||
|
else:
|
||||||
|
if mailserver():
|
||||||
|
sendmail(request.form['email'], "You've successfully registered for {}".format(get_config('ctf_name')))
|
||||||
|
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams
|
from CTFd.models import db, Challenges, Files, Solves, WrongKeys, Keys, Tags, Teams
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@@ -19,6 +19,8 @@ def challenges_view():
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
|
if get_config('verify_emails') and not is_verified():
|
||||||
|
return redirect(url_for('auth.confirm_user'))
|
||||||
if can_view_challenges():
|
if can_view_challenges():
|
||||||
return render_template('chals.html', ctftime=ctftime())
|
return render_template('chals.html', ctftime=ctftime())
|
||||||
else:
|
else:
|
||||||
@@ -84,7 +86,7 @@ def attempts():
|
|||||||
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:
|
||||||
fails = WrongKeys.query.filter_by(team=session['id'], chalid=chalid).count()
|
fails = WrongKeys.query.filter_by(teamid=session['id'], chalid=chalid).count()
|
||||||
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
|
if fails >= int(get_config("max_tries")) and int(get_config("max_tries")) > 0:
|
||||||
json['maxattempts'].append({'chalid':chalid})
|
json['maxattempts'].append({'chalid':chalid})
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
@@ -92,7 +94,7 @@ def attempts():
|
|||||||
|
|
||||||
@challenges.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(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()
|
||||||
json = {'fails':str(fails), 'solves': str(solves)}
|
json = {'fails':str(fails), 'solves': str(solves)}
|
||||||
@@ -113,7 +115,7 @@ def chal(chalid):
|
|||||||
if not ctftime():
|
if not ctftime():
|
||||||
return redirect(url_for('challenges.challenges_view'))
|
return redirect(url_for('challenges.challenges_view'))
|
||||||
if authed():
|
if authed():
|
||||||
fails = WrongKeys.query.filter_by(team=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']))
|
||||||
print("[{0}] {1} submitted {2} with kpm {3}".format(*data))
|
print("[{0}] {1} submitted {2} with kpm {3}".format(*data))
|
||||||
|
|||||||
@@ -28,17 +28,3 @@ 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\.'
|
||||||
]
|
]
|
||||||
|
|
||||||
##### EMAIL (Mailgun and non-Mailgun) #####
|
|
||||||
|
|
||||||
# The first address will be used as the from address of messages sent from CTFd
|
|
||||||
ADMINS = []
|
|
||||||
|
|
||||||
##### EMAIL (if not using Mailgun) #####
|
|
||||||
CTF_NAME = ''
|
|
||||||
MAIL_SERVER = ''
|
|
||||||
MAIL_PORT = 0
|
|
||||||
MAIL_USE_TLS = False
|
|
||||||
MAIL_USE_SSL = False
|
|
||||||
MAIL_USERNAME = ''
|
|
||||||
MAIL_PASSWORD = ''
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ class Teams(db.Model):
|
|||||||
country = db.Column(db.String(32))
|
country = db.Column(db.String(32))
|
||||||
bracket = db.Column(db.String(32))
|
bracket = db.Column(db.String(32))
|
||||||
banned = db.Column(db.Boolean)
|
banned = db.Column(db.Boolean)
|
||||||
|
verified = db.Column(db.Boolean)
|
||||||
admin = db.Column(db.Boolean)
|
admin = db.Column(db.Boolean)
|
||||||
|
|
||||||
def __init__(self, name, email, password):
|
def __init__(self, name, email, password):
|
||||||
@@ -165,13 +166,13 @@ class Solves(db.Model):
|
|||||||
class WrongKeys(db.Model):
|
class WrongKeys(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
chalid = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
chalid = db.Column(db.Integer, db.ForeignKey('challenges.id'))
|
||||||
team = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
teamid = db.Column(db.Integer, db.ForeignKey('teams.id'))
|
||||||
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
|
||||||
flag = db.Column(db.Text)
|
flag = db.Column(db.Text)
|
||||||
chal = db.relationship('Challenges', foreign_keys="WrongKeys.chalid", lazy='joined')
|
chal = db.relationship('Challenges', foreign_keys="WrongKeys.chalid", lazy='joined')
|
||||||
|
|
||||||
def __init__(self, team, chalid, flag):
|
def __init__(self, teamid, chalid, flag):
|
||||||
self.team = team
|
self.teamid = teamid
|
||||||
self.chalid = chalid
|
self.chalid = chalid
|
||||||
self.flag = flag
|
self.flag = flag
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ scoreboard = Blueprint('scoreboard', __name__)
|
|||||||
def scoreboard_view():
|
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)
|
||||||
|
|
||||||
@@ -18,7 +22,11 @@ def scoreboard_view():
|
|||||||
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')
|
||||||
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()
|
||||||
json = {'standings':[]}
|
json = {'standings':[]}
|
||||||
for i, x in enumerate(teams):
|
for i, x in enumerate(teams):
|
||||||
@@ -39,12 +47,23 @@ def topteams(count):
|
|||||||
|
|
||||||
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).limit(count)
|
teams = db.session.query(Solves.teamid, Teams.name, score)\
|
||||||
|
.join(Teams)\
|
||||||
|
.join(Challenges)\
|
||||||
|
.filter(Teams.banned == None)\
|
||||||
|
.group_by(Solves.teamid).order_by(score.desc(), quickest)\
|
||||||
|
.limit(count)
|
||||||
|
|
||||||
for team in teams:
|
for team in teams:
|
||||||
solves = Solves.query.filter_by(teamid=team.teamid).all()
|
solves = Solves.query.filter_by(teamid=team.teamid).all()
|
||||||
json['scores'][team.name] = []
|
json['scores'][team.name] = []
|
||||||
for x in solves:
|
for x in solves:
|
||||||
json['scores'][team.name].append({'id':x.teamid, 'chal':x.chalid, 'team':x.teamid, 'value': x.chal.value, 'time':unix_time(x.date)})
|
json['scores'][team.name].append({
|
||||||
|
'id': x.teamid,
|
||||||
|
'chal': x.chalid,
|
||||||
|
'team': x.teamid,
|
||||||
|
'value': x.chal.value,
|
||||||
|
'time': unix_time(x.date)
|
||||||
|
})
|
||||||
|
|
||||||
return jsonify(json)
|
return jsonify(json)
|
||||||
|
|||||||
@@ -25,11 +25,83 @@
|
|||||||
<input class="form-control" id='max_tries' name='max_tries' type='text' placeholder="0" {% if max_tries is defined and max_tries != None %}value="{{ max_tries }}"{% endif %}>
|
<input class="form-control" id='max_tries' name='max_tries' type='text' placeholder="0" {% if max_tries is defined and max_tries != None %}value="{{ max_tries }}"{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="start">Mailgun API Key:</label>
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<input class="form-control" 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 %}>
|
<li role="presentation" class="active">
|
||||||
|
<a href="#mailserver" aria-controls="mailserver" role="tab" data-toggle="tab">Mail Server</a>
|
||||||
|
</li>
|
||||||
|
<li role="presentation">
|
||||||
|
<a href="#mailgun" aria-controls="mailgun" role="tab" data-toggle="tab">Mailgun</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane active" id="mailserver">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start">Mail Server Address:</label>
|
||||||
|
<input class="form-control" id='mail_server' name='mail_server' type='text'
|
||||||
|
placeholder="Mail Server Address"
|
||||||
|
{% if mail_server is defined and mail_server != None %}value="{{ mail_server }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start">Mail Server Address:</label>
|
||||||
|
<input class="form-control" id='mail_server' name='mail_server' type='text'
|
||||||
|
placeholder="Mail Server Address"
|
||||||
|
{% if mail_server is defined and mail_server != None %}value="{{ mail_server }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start">Mail Server Port:</label>
|
||||||
|
<input class="form-control" id='mail_port' name='mail_port' type='text'
|
||||||
|
placeholder="Mail Server Port"
|
||||||
|
{% if mail_port is defined and mail_port != None %}value="{{ mail_port }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start">Username:</label>
|
||||||
|
<input class="form-control" id='mail_username' name='mail_username' type='text'
|
||||||
|
placeholder="Username"
|
||||||
|
{% if mail_username is defined and mail_username != None %}value="{{ mail_username }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start">Password:</label>
|
||||||
|
<input class="form-control" id='mail_password' name='mail_password' type='password'
|
||||||
|
placeholder="Password"
|
||||||
|
{% if mail_password is defined and mail_password != None %}value="{{ mail_password }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input id="mail_ssl" name="mail_ssl" type="checkbox" {% if mail_ssl %}checked{% endif %}>
|
||||||
|
SSL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input id="mail_tls" name="mail_tls" type="checkbox" {% if mail_tls %}checked{% endif %}>
|
||||||
|
TLS
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="mailgun">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start">Mailgun API Base URL:</label>
|
||||||
|
<input class="form-control" id='mg_base_url' name='mg_base_url' type='text'
|
||||||
|
placeholder="Mailgun API Base URL"
|
||||||
|
{% if mg_base_url is defined and mg_base_url != None %}value="{{ mg_base_url }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start">Mailgun API Key:</label>
|
||||||
|
<input class="form-control" 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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li role="presentation" class="active">
|
<li role="presentation" class="active">
|
||||||
@@ -161,6 +233,13 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input id="verify_emails" name="verify_emails" type="checkbox" {% if verify_emails %}checked{% endif %}>
|
||||||
|
Only allow users with verified emails
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input id="view_challenges_unregistered" name="view_challenges_unregistered" type="checkbox" {% if view_challenges_unregistered %}checked{% endif %}>
|
<input id="view_challenges_unregistered" name="view_challenges_unregistered" type="checkbox" {% if view_challenges_unregistered %}checked{% endif %}>
|
||||||
|
|||||||
@@ -45,9 +45,10 @@ input[type="checkbox"] { margin: 0px !important; position: relative; top: 5px; }
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="id">
|
<input type="hidden" name="id">
|
||||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||||
<textarea name="msg" placeholder="Enter your message here" rows="15"></textarea>
|
<textarea class="form-control" name="msg" placeholder="Enter your message here" rows="15"></textarea>
|
||||||
|
<br>
|
||||||
<div id="email-user-errors"></div>
|
<div id="email-user-errors"></div>
|
||||||
<button type="button" id="send-user-email">Send Message</button>
|
<button type="button" id="send-user-email" class="btn btn-theme btn-outlined">Send Message</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
51
CTFd/templates/confirm.html
Normal file
51
CTFd/templates/confirm.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link rel="stylesheet" href="/static/css/input.css">
|
||||||
|
<style>
|
||||||
|
#login-container {
|
||||||
|
padding-left: 60px;
|
||||||
|
padding-right: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done-row {
|
||||||
|
padding-top: 15px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="jumbotron home">
|
||||||
|
<div class="container">
|
||||||
|
<h1>Confirm</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container main-container">
|
||||||
|
<div class="row">
|
||||||
|
<div id="login-container" class="col-md-6 col-md-offset-3">
|
||||||
|
{% for error in errors %}
|
||||||
|
<div class="alert alert-danger alert-dismissable" role="alert">
|
||||||
|
<span class="sr-only">Error:</span>
|
||||||
|
{{ error }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||||
|
aria-hidden="true">×</span></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<h3 class="text-center">
|
||||||
|
We've sent a confirmation email to {{ team.email }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<h3 class="text-center">
|
||||||
|
Please click the link that email to confirm your account and access the rest of the CTF.
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/classie/1.0.1/classie.min.js"></script>
|
||||||
|
<script src="/static/js/input.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
CTF
|
|
||||||
@@ -40,6 +40,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if confirm_email %}
|
||||||
|
<div class="alert alert-info alert-dismissable submit-row" role="alert">
|
||||||
|
Your email address isn't confirmed!
|
||||||
|
Please check your email to confirm your email address.
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
|
||||||
|
aria-hidden="true">×</span></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
|
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|||||||
@@ -14,13 +14,14 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="login-container" class="col-md-6 col-md-offset-3">
|
<div id="login-container" class="col-md-6 col-md-offset-3">
|
||||||
{% for error in errors %}
|
{% for error in errors %}
|
||||||
<div class="alert alert-danger alert-dismissable" role="alert">
|
<div class="alert alert-info alert-dismissable" role="alert">
|
||||||
<span class="sr-only">Error:</span>
|
<span class="sr-only">Error:</span>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
|
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
|
||||||
|
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||||
{% if mode %}
|
{% if mode %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
|
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
|
||||||
<input type="hidden" name="nonce" value="{{nonce}}"> {# This nonce is implemented specially in the route itself #}
|
<input type="hidden" name="nonce" value="{{nonce}}"> {# This nonce is implemented specially in the route itself #}
|
||||||
<span class="input">
|
<span class="input">
|
||||||
<input class="input-field" type="text" name="ctf_name" id="ctf_name-input" />
|
<input class="input-field" type="text" name="ctf_name" id="ctf_name-input"/>
|
||||||
<label class="input-label" for="ctf_name-input">
|
<label class="input-label" for="ctf_name-input">
|
||||||
<span class="label-content" data-content="CTF Name">CTF Name</span>
|
<span class="label-content" data-content="CTF Name">CTF Name</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -40,39 +40,24 @@
|
|||||||
<span class="input">
|
<span class="input">
|
||||||
<input class="input-field" type="text" name="name" id="username-input" />
|
<input class="input-field" type="text" name="name" id="username-input" />
|
||||||
<label class="input-label" for="username-input">
|
<label class="input-label" for="username-input">
|
||||||
<span class="label-content" data-content="Username">Username</span>
|
<span class="label-content" data-content="Username">Admin Username</span>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
<span class="input">
|
<span class="input">
|
||||||
<input class="input-field" type="email" name="email" id="email-input" />
|
<input class="input-field" type="email" name="email" id="email-input" />
|
||||||
<label class="input-label" for="email-input">
|
<label class="input-label" for="email-input">
|
||||||
<span class="label-content" data-content="Email">Email</span>
|
<span class="label-content" data-content="Email">Admin Email</span>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
<span class="input">
|
<span class="input">
|
||||||
<input class="input-field" type="password" name="password" id="password-input" />
|
<input class="input-field" type="password" name="password" id="password-input" />
|
||||||
<label class="input-label" for="password-input">
|
<label class="input-label" for="password-input">
|
||||||
<span class="label-content" data-content="Password">Password</span>
|
<span class="label-content" data-content="Password">Admin Password</span>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container" style="margin-bottom:50px;">
|
|
||||||
<h2 class="text-center">Index Page</h2>
|
|
||||||
<textarea id="pages-editor" name="html">
|
|
||||||
<div class="container main-container">
|
|
||||||
<img class="logo" src="/static/img/logo.png" />
|
|
||||||
<h3 class="text-center">
|
|
||||||
Welcome to a cool CTF framework written by <a href="https://github.com/ColdHeat">Kevin Chung</a> of <a href="https://github.com/isislab">@isislab</a>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<h4 class="text-center">
|
|
||||||
<a href="/admin">Click here</a> to login and setup your CTF
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="container main-container" style="margin-bottom:100px;">
|
<div class="container main-container" style="margin-bottom:100px;">
|
||||||
<div id="login-container" class="col-md-6 col-md-offset-3">
|
<div id="login-container" class="col-md-6 col-md-offset-3">
|
||||||
<div class="submit-row text-center">
|
<div class="submit-row text-center">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from CTFd.models import db, WrongKeys, Pages, Config, Tracking
|
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams
|
||||||
from CTFd import mail
|
|
||||||
|
|
||||||
from six.moves.urllib.parse import urlparse, urljoin
|
from six.moves.urllib.parse import urlparse, urljoin
|
||||||
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.ext.mail import Message
|
from flask.ext.mail import Message
|
||||||
|
from itsdangerous import Signer, BadSignature
|
||||||
from socket import inet_aton, inet_ntoa
|
from socket import inet_aton, inet_ntoa
|
||||||
from struct import unpack, pack
|
from struct import unpack, pack
|
||||||
from sqlalchemy.engine.url import make_url
|
from sqlalchemy.engine.url import make_url
|
||||||
@@ -20,7 +20,8 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import smtplib
|
||||||
|
import email
|
||||||
|
|
||||||
def init_logs(app):
|
def init_logs(app):
|
||||||
logger_keys = logging.getLogger('keys')
|
logger_keys = logging.getLogger('keys')
|
||||||
@@ -138,6 +139,12 @@ def pages():
|
|||||||
def authed():
|
def authed():
|
||||||
return bool(session.get('id', False))
|
return bool(session.get('id', False))
|
||||||
|
|
||||||
|
def is_verified():
|
||||||
|
team = Teams.query.filter_by(id=session.get('id')).first()
|
||||||
|
if team:
|
||||||
|
return team.verified
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def is_setup():
|
def is_setup():
|
||||||
setup = Config.query.filter_by(key='setup').first()
|
setup = Config.query.filter_by(key='setup').first()
|
||||||
@@ -261,14 +268,19 @@ def ip2long(ip):
|
|||||||
|
|
||||||
def get_kpm(teamid): # keys per minute
|
def get_kpm(teamid): # keys per minute
|
||||||
one_min_ago = datetime.datetime.utcnow() + datetime.timedelta(minutes=-1)
|
one_min_ago = datetime.datetime.utcnow() + datetime.timedelta(minutes=-1)
|
||||||
return len(db.session.query(WrongKeys).filter(WrongKeys.team == teamid, WrongKeys.date >= one_min_ago).all())
|
return len(db.session.query(WrongKeys).filter(WrongKeys.teamid == teamid, WrongKeys.date >= one_min_ago).all())
|
||||||
|
|
||||||
|
|
||||||
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:
|
if config:
|
||||||
return config.value
|
value = config.value
|
||||||
|
if value and value.isdigit():
|
||||||
|
return int(value)
|
||||||
|
else:
|
||||||
|
return value
|
||||||
else:
|
else:
|
||||||
|
set_config(key, None)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -284,34 +296,75 @@ def set_config(key, value):
|
|||||||
|
|
||||||
|
|
||||||
def mailserver():
|
def mailserver():
|
||||||
if (get_config('mg_api_key') and app.config['ADMINS']) or (app.config['MAIL_SERVER'] and app.config['MAIL_PORT'] and app.config['ADMINS']):
|
if (get_config('mg_api_key')) or (get_config('mail_server') and get_config('mail_port')):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_smtp(host, port, username=None, password=None, TLS=None, SSL=None):
|
||||||
|
smtp = smtplib.SMTP(host, port)
|
||||||
|
smtp.ehlo()
|
||||||
|
if TLS:
|
||||||
|
smtp.starttls()
|
||||||
|
smtp.ehlo()
|
||||||
|
smtp.login(username, password)
|
||||||
|
return smtp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sendmail(addr, text):
|
def sendmail(addr, text):
|
||||||
if get_config('mg_api_key') and app.config['ADMINS']:
|
if get_config('mg_api_key') and get_config('mg_base_url'):
|
||||||
ctf_name = get_config('ctf_name')
|
ctf_name = get_config('ctf_name')
|
||||||
mg_api_key = get_config('mg_api_key')
|
mg_api_key = get_config('mg_api_key')
|
||||||
return requests.post(
|
mg_base_url = get_config('mg_base_url')
|
||||||
"https://api.mailgun.net/v2/mail"+app.config['HOST']+"/messages",
|
r = requests.post(
|
||||||
|
mg_base_url + '/messages',
|
||||||
auth=("api", mg_api_key),
|
auth=("api", mg_api_key),
|
||||||
data={"from": "{} Admin <{}>".format(ctf_name, app.config['ADMINS'][0]),
|
data={"from": "{} Admin <{}>".format(ctf_name, 'noreply@ctfd.io'),
|
||||||
"to": [addr],
|
"to": [addr],
|
||||||
"subject": "Message from {0}".format(ctf_name),
|
"subject": "Message from {0}".format(ctf_name),
|
||||||
"text": text})
|
"text": text})
|
||||||
elif app.config['MAIL_SERVER'] and app.config['MAIL_PORT'] and app.config['ADMINS']:
|
if r.status_code == 200:
|
||||||
try:
|
|
||||||
msg = Message("Message from {0}".format(get_config('ctf_name')), sender=app.config['ADMINS'][0], recipients=[addr])
|
|
||||||
msg.body = text
|
|
||||||
mail.send(msg)
|
|
||||||
return True
|
return True
|
||||||
except:
|
else:
|
||||||
return False
|
return False
|
||||||
|
elif get_config('mail_server') and get_config('mail_port'):
|
||||||
|
data = {
|
||||||
|
'host': get_config('mail_server'),
|
||||||
|
'port': int(get_config('mail_port'))
|
||||||
|
}
|
||||||
|
if get_config('mail_username'):
|
||||||
|
data['username'] = get_config('mail_username')
|
||||||
|
if get_config('mail_password'):
|
||||||
|
data['password'] = get_config('mail_password')
|
||||||
|
if get_config('mail_tls'):
|
||||||
|
data['TLS'] = get_config('mail_tls')
|
||||||
|
if get_config('mail_ssl'):
|
||||||
|
data['SSL'] = get_config('mail_ssl')
|
||||||
|
|
||||||
|
smtp = get_smtp(**data)
|
||||||
|
msg = email.mime.text.MIMEText(text)
|
||||||
|
msg['Subject'] = "Message from {0}".format(get_config('ctf_name'))
|
||||||
|
msg['From'] = 'noreply@ctfd.io'
|
||||||
|
msg['To'] = addr
|
||||||
|
|
||||||
|
smtp.sendmail(msg['From'], [msg['To']], msg.as_string())
|
||||||
|
smtp.quit()
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def verify_email(addr):
|
||||||
|
s = Signer(app.config['SECRET_KEY'])
|
||||||
|
token = s.sign(addr)
|
||||||
|
text = """Please click the following link to confirm your email address for {}: {}""".format(
|
||||||
|
get_config('ctf_name'),
|
||||||
|
url_for('auth.confirm_user', _external=True) + '/' + token.encode('base64')
|
||||||
|
)
|
||||||
|
sendmail(addr, text)
|
||||||
|
|
||||||
|
|
||||||
def rmdir(dir):
|
def rmdir(dir):
|
||||||
shutil.rmtree(dir, ignore_errors=True)
|
shutil.rmtree(dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,16 @@ def setup():
|
|||||||
admin.banned = True
|
admin.banned = True
|
||||||
|
|
||||||
## Index page
|
## Index page
|
||||||
html = request.form['html']
|
page = Pages('index', """<div class="container main-container">
|
||||||
page = Pages('index', html)
|
<img class="logo" src="/static/img/logo.png" />
|
||||||
|
<h3 class="text-center">
|
||||||
|
Welcome to a cool CTF framework written by <a href="https://github.com/ColdHeat">Kevin Chung</a> of <a href="https://github.com/isislab">@isislab</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<h4 class="text-center">
|
||||||
|
<a href="/admin">Click here</a> to login and setup your CTF
|
||||||
|
</h4>
|
||||||
|
</div>""")
|
||||||
|
|
||||||
#max attempts per challenge
|
#max attempts per challenge
|
||||||
max_tries = Config("max_tries",0)
|
max_tries = Config("max_tries",0)
|
||||||
@@ -66,6 +74,22 @@ def setup():
|
|||||||
## Allow/Disallow registration
|
## Allow/Disallow registration
|
||||||
prevent_registration = Config('prevent_registration', None)
|
prevent_registration = Config('prevent_registration', None)
|
||||||
|
|
||||||
|
## Verify emails
|
||||||
|
verify_emails = Config('verify_emails', None)
|
||||||
|
|
||||||
|
mail_server = Config('mail_server', None)
|
||||||
|
mail_port = Config('mail_port', None)
|
||||||
|
mail_tls = Config('mail_tls', None)
|
||||||
|
mail_ssl = Config('mail_ssl', None)
|
||||||
|
mail_username = Config('mail_username', None)
|
||||||
|
mail_password = Config('mail_password', None)
|
||||||
|
db.session.add(mail_server)
|
||||||
|
db.session.add(mail_port)
|
||||||
|
db.session.add(mail_tls)
|
||||||
|
db.session.add(mail_ssl)
|
||||||
|
db.session.add(mail_username)
|
||||||
|
db.session.add(mail_password)
|
||||||
|
|
||||||
setup = Config('setup', True)
|
setup = Config('setup', True)
|
||||||
|
|
||||||
db.session.add(ctf_name)
|
db.session.add(ctf_name)
|
||||||
@@ -76,6 +100,7 @@ def setup():
|
|||||||
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(verify_emails)
|
||||||
db.session.add(css)
|
db.session.add(css)
|
||||||
db.session.add(setup)
|
db.session.add(setup)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -113,8 +138,8 @@ def teams(page):
|
|||||||
page_start = results_per_page * ( page - 1 )
|
page_start = results_per_page * ( page - 1 )
|
||||||
page_end = results_per_page * ( page - 1 ) + results_per_page
|
page_end = results_per_page * ( page - 1 ) + results_per_page
|
||||||
|
|
||||||
teams = Teams.query.slice(page_start, page_end).all()
|
teams = Teams.query.filter_by(verified=True).slice(page_start, page_end).all()
|
||||||
count = db.session.query(db.func.count(Teams.id)).first()[0]
|
count = len(teams)
|
||||||
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
pages = int(count / results_per_page) + (count % results_per_page > 0)
|
||||||
return render_template('teams.html', teams=teams, team_pages=pages, curr_page=page)
|
return render_template('teams.html', teams=teams, team_pages=pages, curr_page=page)
|
||||||
|
|
||||||
@@ -178,7 +203,10 @@ def profile():
|
|||||||
team = Teams.query.filter_by(id=session['id']).first()
|
team = Teams.query.filter_by(id=session['id']).first()
|
||||||
if not get_config('prevent_name_change'):
|
if not get_config('prevent_name_change'):
|
||||||
team.name = name
|
team.name = name
|
||||||
team.email = email
|
if team.email != email.lower():
|
||||||
|
team.email = email.lower()
|
||||||
|
if get_config('verify_emails'):
|
||||||
|
team.verified = False
|
||||||
session['username'] = team.name
|
session['username'] = team.name
|
||||||
|
|
||||||
if 'password' in request.form.keys() and not len(request.form['password']) == 0:
|
if 'password' in request.form.keys() and not len(request.form['password']) == 0:
|
||||||
@@ -197,7 +225,8 @@ def profile():
|
|||||||
affiliation = user.affiliation
|
affiliation = user.affiliation
|
||||||
country = user.country
|
country = user.country
|
||||||
prevent_name_change = get_config('prevent_name_change')
|
prevent_name_change = get_config('prevent_name_change')
|
||||||
|
confirm_email = not user.verified
|
||||||
return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation,
|
return render_template('profile.html', name=name, email=email, website=website, affiliation=affiliation,
|
||||||
country=country, prevent_name_change=prevent_name_change)
|
country=country, prevent_name_change=prevent_name_change, confirm_email=confirm_email)
|
||||||
else:
|
else:
|
||||||
return redirect(url_for('auth.login'))
|
return redirect(url_for('auth.login'))
|
||||||
|
|||||||
@@ -246,7 +246,9 @@ if __name__ == '__main__':
|
|||||||
name = gen_name()
|
name = gen_name()
|
||||||
if name not in used:
|
if name not in used:
|
||||||
used.append(name)
|
used.append(name)
|
||||||
db.session.add(Teams(name, name.lower() + gen_email(), 'password'))
|
team = Teams(name, name.lower() + gen_email(), 'password')
|
||||||
|
team.verified = True
|
||||||
|
db.session.add(team)
|
||||||
count += 1
|
count += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
APScheduler
|
|
||||||
Flask
|
Flask
|
||||||
Flask-Mail
|
|
||||||
Flask-SQLAlchemy
|
Flask-SQLAlchemy
|
||||||
Flask-Session
|
Flask-Session
|
||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
|
|||||||
Reference in New Issue
Block a user