Closes #15 (Thanks mwinstead3790), various fixes

This commit is contained in:
CodeKevin
2015-03-14 23:01:21 -04:00
parent b4dd54d36a
commit f2484c519a
7 changed files with 111 additions and 31 deletions

View File

@@ -1,5 +1,5 @@
from flask import render_template, request, redirect, abort, jsonify, url_for, session from flask import render_template, request, redirect, abort, jsonify, url_for, session
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, get_digitalocean, sendmail from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, get_digitalocean, sendmail, rmdir
from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config
from itsdangerous import TimedSerializer, BadTimeSignature from itsdangerous import TimedSerializer, BadTimeSignature
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@@ -23,7 +23,10 @@ def init_admin(app):
admin = Teams.query.filter_by(name=request.form['name'], admin=True).first() admin = Teams.query.filter_by(name=request.form['name'], admin=True).first()
if admin and bcrypt_sha256.verify(request.form['password'], admin.password): if admin and bcrypt_sha256.verify(request.form['password'], admin.password):
session.regenerate() # NO SESSION FIXATION FOR YOU try:
session.regenerate() # NO SESSION FIXATION FOR YOU
except:
pass # TODO: Some session objects dont implement regenerate :(
session['username'] = admin.name session['username'] = admin.name
session['id'] = admin.id session['id'] = admin.id
session['admin'] = True session['admin'] = True
@@ -445,7 +448,6 @@ def init_admin(app):
@app.route('/admin/chal/new', methods=['POST']) @app.route('/admin/chal/new', methods=['POST'])
def admin_create_chal(): def admin_create_chal():
files = request.files.getlist('files[]') files = request.files.getlist('files[]')
# Create challenge # Create challenge
@@ -466,20 +468,39 @@ def init_admin(app):
md5hash = hashlib.md5(filename).hexdigest() md5hash = hashlib.md5(filename).hexdigest()
if not os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], md5hash)): if not os.path.exists(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash)):
os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], md5hash)) os.makedirs(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash))
f.save(os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename)) f.save(os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename))
db_f = Files(chal.id, os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename)) db_f = Files(chal.id, os.path.join(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename))
db.session.add(db_f) db.session.add(db_f)
db.session.commit() db.session.commit()
db.session.close() db.session.close()
return redirect('/admin/chals') return redirect('/admin/chals')
@app.route('/admin/chal/delete', methods=['POST'])
def admin_delete_chal():
challenge = Challenges.query.filter_by(id=request.form['id']).first()
if challenge:
WrongKeys.query.filter_by(chal=challenge.id).delete()
Solves.query.filter_by(chalid=challenge.id).delete()
Keys.query.filter_by(chal=challenge.id).delete()
files = Files.query.filter_by(chal=challenge.id).all()
print files
Files.query.filter_by(chal=challenge.id).delete()
for file in files:
folder = os.path.dirname(file.location)
rmdir(folder)
Tags.query.filter_by(chal=challenge.id).delete()
Challenges.query.filter_by(id=challenge.id).delete()
db.session.commit()
db.session.close()
return '1'
@app.route('/admin/chal/update', methods=['POST']) @app.route('/admin/chal/update', methods=['POST'])
def admin_update_chal(): def admin_update_chal():
challenge=Challenges.query.filter_by(id=request.form['id']).first() challenge = Challenges.query.filter_by(id=request.form['id']).first()
challenge.name = request.form['name'] challenge.name = request.form['name']
challenge.description = request.form['desc'] challenge.description = request.form['desc']
challenge.value = request.form['value'] challenge.value = request.form['value']

View File

@@ -105,7 +105,10 @@ Did you initiate a password reset?
# team = Teams.query.filter_by(name=request.form['name'], password=sha512(request.form['password'])).first() # team = Teams.query.filter_by(name=request.form['name'], password=sha512(request.form['password'])).first()
team = Teams.query.filter_by(name=request.form['name']).first() team = Teams.query.filter_by(name=request.form['name']).first()
if team and bcrypt_sha256.verify(request.form['password'], team.password): if team and bcrypt_sha256.verify(request.form['password'], team.password):
# session.regenerate() # NO SESSION FIXATION FOR YOU try:
session.regenerate() # NO SESSION FIXATION FOR YOU
except:
pass # TODO: Some session objects don't implement regenerate :(
session['username'] = team.name session['username'] = team.name
session['id'] = team.id session['id'] = team.id
session['admin'] = team.admin session['admin'] = team.admin

View File

@@ -6,7 +6,7 @@ SESSION_TYPE = "filesystem"
SESSION_FILE_DIR = "/tmp/flask_session" SESSION_FILE_DIR = "/tmp/flask_session"
SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True
HOST = ".ctfd.io" HOST = ".ctfd.io"
UPLOAD_FOLDER = 'static/uploads' UPLOAD_FOLDER = os.path.normpath('static/uploads')
##### EMAIL ##### ##### EMAIL #####
CTF_NAME = '' CTF_NAME = ''

View File

@@ -12,6 +12,8 @@ import time
import datetime import datetime
import hashlib import hashlib
import digitalocean import digitalocean
import shutil
def init_utils(app): def init_utils(app):
app.jinja_env.filters['unix_time'] = unix_time app.jinja_env.filters['unix_time'] = unix_time
@@ -26,9 +28,11 @@ def pages():
pages = Pages.query.filter(Pages.route!="index").all() pages = Pages.query.filter(Pages.route!="index").all()
return pages return pages
def authed(): def authed():
return bool(session.get('id', False)) return bool(session.get('id', False))
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:
@@ -36,6 +40,7 @@ def is_setup():
else: else:
return False return False
def is_admin(): def is_admin():
if authed(): if authed():
return session['admin'] return session['admin']
@@ -50,6 +55,7 @@ def can_register():
else: else:
return True return True
def admins_only(f): def admins_only(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
@@ -89,6 +95,7 @@ def ctftime():
return False return False
def can_view_challenges(): def can_view_challenges():
config = Config.query.filter_by(key="view_challenges_unregistered").first() config = Config.query.filter_by(key="view_challenges_unregistered").first()
if config: if config:
@@ -96,24 +103,30 @@ def can_view_challenges():
else: else:
return authed() return authed()
def unix_time(dt): def unix_time(dt):
epoch = datetime.datetime.utcfromtimestamp(0) epoch = datetime.datetime.utcfromtimestamp(0)
delta = dt - epoch delta = dt - epoch
return int(delta.total_seconds()) return int(delta.total_seconds())
def unix_time_millis(dt): def unix_time_millis(dt):
return unix_time(dt) * 1000 return unix_time(dt) * 1000
def long2ip(ip_int): def long2ip(ip_int):
return inet_ntoa(pack('!I', ip_int)) return inet_ntoa(pack('!I', ip_int))
def ip2long(ip): def ip2long(ip):
return unpack('!I', inet_aton(ip))[0] return unpack('!I', inet_aton(ip))[0]
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.team == 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:
@@ -121,6 +134,7 @@ def get_config(key):
else: else:
return None return None
def set_config(key, value): def set_config(key, value):
config = Config.query.filter_by(key=key).first() config = Config.query.filter_by(key=key).first()
if config: if config:
@@ -137,6 +151,7 @@ def mailserver():
return True return True
return False return False
def sendmail(addr, text): def sendmail(addr, text):
try: try:
msg = Message("Message from {0}".format(app.config['CTF_NAME']), sender = app.config['ADMINS'][0], recipients = [addr]) msg = Message("Message from {0}".format(app.config['CTF_NAME']), sender = app.config['ADMINS'][0], recipients = [addr])
@@ -146,14 +161,20 @@ def sendmail(addr, text):
except: except:
return False return False
def rmdir(dir):
shutil.rmtree(dir, ignore_errors=True)
def is_safe_url(target): def is_safe_url(target):
ref_url = urlparse(request.host_url) ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target)) test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
def sha512(string): def sha512(string):
return hashlib.sha512(string).hexdigest() return hashlib.sha512(string).hexdigest()
def get_digitalocean(): def get_digitalocean():
token = get_config('do_api_key') token = get_config('do_api_key')
if token: if token:

View File

@@ -5,7 +5,7 @@ Flask-SQLAlchemy==2.0
Flask-Session==0.1.1 Flask-Session==0.1.1
SQLAlchemy==0.9.8 SQLAlchemy==0.9.8
passlib==1.6.2 passlib==1.6.2
py-bcrypt==0.4 bcrypt
six==1.8.0 six==1.8.0
itsdangerous itsdangerous
python-digitalocean python-digitalocean

View File

@@ -4,14 +4,14 @@ function loadchal(id) {
obj = $.grep(challenges['game'], function (e) { obj = $.grep(challenges['game'], function (e) {
return e.id == id; return e.id == id;
})[0] })[0]
$('#update-challenge .chal-name').val(obj.name) $('.chal-name').val(obj.name);
$('#update-challenge .chal-desc').html(obj.description) $('.chal-desc').html(obj.description);
$('#update-challenge .chal-value').val(obj.value) $('.chal-value').val(obj.value);
$('#update-challenge .chal-category').val(obj.category) $('.chal-category').val(obj.category);
$('#update-challenge .chal-id').val(obj.id) $('.chal-id').val(obj.id);
$('#update-challenge .chal-delete').attr({ //$('#update-challenge .chal-delete').attr({
'href': '/admin/chal/close/' + (id + 1) // 'href': '/admin/chal/close/' + (id + 1)
}) //})
$('#update-challenge').foundation('reveal', 'open'); $('#update-challenge').foundation('reveal', 'open');
} }
@@ -27,14 +27,14 @@ function submitkey(chal, key) {
function loadkeys(chal){ function loadkeys(chal){
$.get('/admin/keys/' + chal, function(data){ $.get('/admin/keys/' + chal, function(data){
$('#keys-chal').val(chal) $('#keys-chal').val(chal);
keys = $.parseJSON(JSON.stringify(data)); keys = $.parseJSON(JSON.stringify(data));
keys = keys['keys'] keys = keys['keys'];
$('#current-keys').empty() $('#current-keys').empty();
for(x=0; x<keys.length; x++){ for(x=0; x<keys.length; x++){
$('#current-keys').append($("<input class='current-key' type='text'>").val(keys[x].key)) $('#current-keys').append($("<input class='current-key' type='text'>").val(keys[x].key));
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="0">Static') $('#current-keys').append('<input type="radio" name="key_type['+x+']" value="0">Static');
$('#current-keys').append('<input type="radio" name="key_type['+x+']" value="1">Regex') $('#current-keys').append('<input type="radio" name="key_type['+x+']" value="1">Regex');
$('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true); $('#current-keys input[name="key_type['+x+']"][value="'+keys[x].type+'"]').prop('checked',true);
} }
}); });
@@ -76,6 +76,10 @@ function deletetag(tagid){
$.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()}); $.post('/admin/tags/'+tagid+'/delete', {'nonce': $('#nonce').val()});
} }
function deletechal(chalid){
$.post('/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
}
function updatetags(){ function updatetags(){
tags = []; tags = [];
chal = $('#tags-chal').val() chal = $('#tags-chal').val()
@@ -115,6 +119,7 @@ function deletefile(chal, file, elem){
function loadchals(){ function loadchals(){
$('#challenges').empty();
$.post("/admin/chals", { $.post("/admin/chals", {
'nonce': $('#nonce').val() 'nonce': $('#nonce').val()
}, function (data) { }, function (data) {
@@ -169,6 +174,20 @@ $('#submit-tags').click(function (e) {
updatetags() updatetags()
}); });
$('#delete-chal > form').submit(function(e){
e.preventDefault();
$.post('/admin/chal/delete', $(this).serialize(), function(data){
console.log(data)
if (data){
loadchals();
$('#delete-chal').foundation('reveal', 'close');
}
else {
alert('There was an error');
}
})
});
$(".tag-insert").keyup(function (e) { $(".tag-insert").keyup(function (e) {
if (e.keyCode == 13) { if (e.keyCode == 13) {
tag = $('.tag-insert').val() tag = $('.tag-insert').val()
@@ -184,14 +203,14 @@ $(".tag-insert").keyup(function (e) {
$('.create-category').click(function (e) { $('.create-category').click(function (e) {
$('#new-category').foundation('reveal', 'open'); $('#new-category').foundation('reveal', 'open');
}); });
count = 1 count = 1;
$('#create-key').click(function (e) { $('#create-key').click(function (e) {
$('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>"); $('#current-keys').append("<input class='current-key' type='text' placeholder='Blank Key'>");
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="0">Static'); $('#current-keys').append('<input type="radio" name="key_type['+count+']" value="0">Static');
$('#current-keys').append('<input type="radio" name="key_type['+count+']" value="1">Regex'); $('#current-keys').append('<input type="radio" name="key_type['+count+']" value="1">Regex');
count++ count++;
}); });
$(function(){ $(function(){
loadchals() loadchals();
}) })

View File

@@ -15,7 +15,7 @@
<h3>New Challenge</h3> <h3>New Challenge</h3>
<input type='text' name='name' placeholder='Name'><br/> <input type='text' name='name' placeholder='Name'><br/>
<textarea name='desc' placeholder='Description'></textarea><br/> <textarea name='desc' placeholder='Description'></textarea><br/>
<input type='text' name='value' placeholder='Value'><br/> <input type='number' name='value' placeholder='Value'><br/>
<input type='text' name='key' placeholder='Key'><br/> <input type='text' name='key' placeholder='Key'><br/>
<input type="radio" name="key_type[0]" value="0">Static <input type="radio" name="key_type[0]" value="0">Static
<input type="radio" name="key_type[0]" value="1">Regex <input type="radio" name="key_type[0]" value="1">Regex
@@ -33,7 +33,7 @@
<input name='nonce' type='hidden' value="{{ nonce }}"> <input name='nonce' type='hidden' value="{{ nonce }}">
<input type='text' name='name' placeholder='Name'><br/> <input type='text' name='name' placeholder='Name'><br/>
<textarea class="textbox" name='desc' placeholder='Description'></textarea><br/> <textarea class="textbox" name='desc' placeholder='Description'></textarea><br/>
<input type='text' name='value' placeholder='Value'><br/> <input type='number' name='value' placeholder='Value'><br/>
<input id="new-chal-category" type="hidden" name="category"> <input id="new-chal-category" type="hidden" name="category">
<input type='text' name='key' placeholder='Key'><br/> <input type='text' name='key' placeholder='Key'><br/>
@@ -49,6 +49,21 @@
</form> </form>
</div> </div>
<div id="delete-chal" class="reveal-modal" data-reveal>
<h2 class="text-center">Delete Challenge</h2>
<form method="POST" action="/admin/chal/delete">
<input type="hidden" name="nonce" value="{{ nonce }}">
<input type="hidden" name="id" class="chal-id">
<div class="small-6 small-centered text-center columns">
<p>Are you sure you want to delete this challenge?</p>
<p>Solves, wrong keys, files, tags will all be deleted.</p>
<button type="button" class="button alert radius" onclick="$('#delete-chal').foundation('reveal', 'close');">No</button>
<button type="submit" id="delete-user" class="button success radius">Yes</button>
</div>
</form>
<a class="close-reveal-modal">&#215;</a>
</div>
<div id="update-keys" class="reveal-modal" data-reveal> <div id="update-keys" class="reveal-modal" data-reveal>
<form method="POST" action="/admin/keys"> <form method="POST" action="/admin/keys">
<h3>Keys</h3> <h3>Keys</h3>
@@ -100,7 +115,7 @@
<input name='nonce' type='hidden' value="{{ nonce }}"> <input name='nonce' type='hidden' value="{{ nonce }}">
<input class="chal-name" type='text' name='name' placeholder='Name'><br/> <input class="chal-name" type='text' name='name' placeholder='Name'><br/>
<textarea class="chal-desc textbox" name='desc' placeholder='Description'></textarea><br/> <textarea class="chal-desc textbox" name='desc' placeholder='Description'></textarea><br/>
<input class="chal-value" type='text' name='value' placeholder='Value'><br/> <input class="chal-value" type='number' name='value' placeholder='Value'><br/>
<select class="chal-category" name="category"> <select class="chal-category" name="category">
<option>-</option> <option>-</option>
</select> </select>
@@ -109,6 +124,7 @@
<a href="#" data-reveal-id="update-tags" class="secondary button">Tags</a> <a href="#" data-reveal-id="update-tags" class="secondary button">Tags</a>
<a href="#" data-reveal-id="update-files" class="secondary button">Files</a> <a href="#" data-reveal-id="update-files" class="secondary button">Files</a>
<a href="#" data-reveal-id="update-keys" class="secondary button">Keys</a> <a href="#" data-reveal-id="update-keys" class="secondary button">Keys</a>
<a href="#" data-reveal-id="delete-chal" class="secondary alert button">Delete</a>
<button type='submit'>Update</button> <button type='submit'>Update</button>
<a class="close-reveal-modal">&#215;</a> <a class="close-reveal-modal">&#215;</a>
</form> </form>