mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 06:24:23 +01:00
Closes #15 (Thanks mwinstead3790), various fixes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from flask import render_template, request, redirect, abort, jsonify, url_for, session
|
||||
from CTFd.utils import sha512, is_safe_url, authed, admins_only, is_admin, unix_time, unix_time_millis, get_config, set_config, get_digitalocean, sendmail
|
||||
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 itsdangerous import TimedSerializer, BadTimeSignature
|
||||
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()
|
||||
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['id'] = admin.id
|
||||
session['admin'] = True
|
||||
@@ -445,7 +448,6 @@ def init_admin(app):
|
||||
|
||||
@app.route('/admin/chal/new', methods=['POST'])
|
||||
def admin_create_chal():
|
||||
|
||||
files = request.files.getlist('files[]')
|
||||
|
||||
# Create challenge
|
||||
@@ -466,20 +468,39 @@ def init_admin(app):
|
||||
|
||||
md5hash = hashlib.md5(filename).hexdigest()
|
||||
|
||||
if not os.path.exists(os.path.join(app.config['UPLOAD_FOLDER'], md5hash)):
|
||||
os.makedirs(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(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash))
|
||||
|
||||
f.save(os.path.join(app.config['UPLOAD_FOLDER'], md5hash, filename))
|
||||
db_f = Files(chal.id, 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(os.path.normpath(app.config['UPLOAD_FOLDER']), md5hash, filename))
|
||||
db.session.add(db_f)
|
||||
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return redirect('/admin/chals')
|
||||
|
||||
@app.route('/admin/chal/delete', methods=['POST'])
|
||||
def admin_delete_chal():
|
||||
challenge = Challenges.query.filter_by(id=request.form['id']).first()
|
||||
if challenge:
|
||||
WrongKeys.query.filter_by(chal=challenge.id).delete()
|
||||
Solves.query.filter_by(chalid=challenge.id).delete()
|
||||
Keys.query.filter_by(chal=challenge.id).delete()
|
||||
files = Files.query.filter_by(chal=challenge.id).all()
|
||||
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'])
|
||||
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.description = request.form['desc']
|
||||
challenge.value = request.form['value']
|
||||
|
||||
@@ -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']).first()
|
||||
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['id'] = team.id
|
||||
session['admin'] = team.admin
|
||||
|
||||
@@ -6,7 +6,7 @@ SESSION_TYPE = "filesystem"
|
||||
SESSION_FILE_DIR = "/tmp/flask_session"
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
HOST = ".ctfd.io"
|
||||
UPLOAD_FOLDER = 'static/uploads'
|
||||
UPLOAD_FOLDER = os.path.normpath('static/uploads')
|
||||
|
||||
##### EMAIL #####
|
||||
CTF_NAME = ''
|
||||
|
||||
@@ -12,6 +12,8 @@ import time
|
||||
import datetime
|
||||
import hashlib
|
||||
import digitalocean
|
||||
import shutil
|
||||
|
||||
|
||||
def init_utils(app):
|
||||
app.jinja_env.filters['unix_time'] = unix_time
|
||||
@@ -26,9 +28,11 @@ def pages():
|
||||
pages = Pages.query.filter(Pages.route!="index").all()
|
||||
return pages
|
||||
|
||||
|
||||
def authed():
|
||||
return bool(session.get('id', False))
|
||||
|
||||
|
||||
def is_setup():
|
||||
setup = Config.query.filter_by(key='setup').first()
|
||||
if setup:
|
||||
@@ -36,6 +40,7 @@ def is_setup():
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def is_admin():
|
||||
if authed():
|
||||
return session['admin']
|
||||
@@ -50,6 +55,7 @@ def can_register():
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def admins_only(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
@@ -89,6 +95,7 @@ def ctftime():
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def can_view_challenges():
|
||||
config = Config.query.filter_by(key="view_challenges_unregistered").first()
|
||||
if config:
|
||||
@@ -96,24 +103,30 @@ def can_view_challenges():
|
||||
else:
|
||||
return authed()
|
||||
|
||||
|
||||
def unix_time(dt):
|
||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||
delta = dt - epoch
|
||||
return int(delta.total_seconds())
|
||||
|
||||
|
||||
def unix_time_millis(dt):
|
||||
return unix_time(dt) * 1000
|
||||
|
||||
|
||||
def long2ip(ip_int):
|
||||
return inet_ntoa(pack('!I', ip_int))
|
||||
|
||||
|
||||
def ip2long(ip):
|
||||
return unpack('!I', inet_aton(ip))[0]
|
||||
|
||||
|
||||
def get_kpm(teamid): # keys per minute
|
||||
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())
|
||||
|
||||
|
||||
def get_config(key):
|
||||
config = Config.query.filter_by(key=key).first()
|
||||
if config:
|
||||
@@ -121,6 +134,7 @@ def get_config(key):
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def set_config(key, value):
|
||||
config = Config.query.filter_by(key=key).first()
|
||||
if config:
|
||||
@@ -137,6 +151,7 @@ def mailserver():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def sendmail(addr, text):
|
||||
try:
|
||||
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:
|
||||
return False
|
||||
|
||||
|
||||
def rmdir(dir):
|
||||
shutil.rmtree(dir, ignore_errors=True)
|
||||
|
||||
def is_safe_url(target):
|
||||
ref_url = urlparse(request.host_url)
|
||||
test_url = urlparse(urljoin(request.host_url, target))
|
||||
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
|
||||
|
||||
|
||||
def sha512(string):
|
||||
return hashlib.sha512(string).hexdigest()
|
||||
|
||||
|
||||
def get_digitalocean():
|
||||
token = get_config('do_api_key')
|
||||
if token:
|
||||
|
||||
@@ -5,7 +5,7 @@ Flask-SQLAlchemy==2.0
|
||||
Flask-Session==0.1.1
|
||||
SQLAlchemy==0.9.8
|
||||
passlib==1.6.2
|
||||
py-bcrypt==0.4
|
||||
bcrypt
|
||||
six==1.8.0
|
||||
itsdangerous
|
||||
python-digitalocean
|
||||
@@ -4,14 +4,14 @@ function loadchal(id) {
|
||||
obj = $.grep(challenges['game'], function (e) {
|
||||
return e.id == id;
|
||||
})[0]
|
||||
$('#update-challenge .chal-name').val(obj.name)
|
||||
$('#update-challenge .chal-desc').html(obj.description)
|
||||
$('#update-challenge .chal-value').val(obj.value)
|
||||
$('#update-challenge .chal-category').val(obj.category)
|
||||
$('#update-challenge .chal-id').val(obj.id)
|
||||
$('#update-challenge .chal-delete').attr({
|
||||
'href': '/admin/chal/close/' + (id + 1)
|
||||
})
|
||||
$('.chal-name').val(obj.name);
|
||||
$('.chal-desc').html(obj.description);
|
||||
$('.chal-value').val(obj.value);
|
||||
$('.chal-category').val(obj.category);
|
||||
$('.chal-id').val(obj.id);
|
||||
//$('#update-challenge .chal-delete').attr({
|
||||
// 'href': '/admin/chal/close/' + (id + 1)
|
||||
//})
|
||||
$('#update-challenge').foundation('reveal', 'open');
|
||||
|
||||
}
|
||||
@@ -27,14 +27,14 @@ function submitkey(chal, key) {
|
||||
|
||||
function loadkeys(chal){
|
||||
$.get('/admin/keys/' + chal, function(data){
|
||||
$('#keys-chal').val(chal)
|
||||
$('#keys-chal').val(chal);
|
||||
keys = $.parseJSON(JSON.stringify(data));
|
||||
keys = keys['keys']
|
||||
$('#current-keys').empty()
|
||||
keys = keys['keys'];
|
||||
$('#current-keys').empty();
|
||||
for(x=0; x<keys.length; x++){
|
||||
$('#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="1">Regex')
|
||||
$('#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="1">Regex');
|
||||
$('#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()});
|
||||
}
|
||||
|
||||
function deletechal(chalid){
|
||||
$.post('/admin/chal/delete', {'nonce':$('#nonce').val(), 'id':chalid});
|
||||
}
|
||||
|
||||
function updatetags(){
|
||||
tags = [];
|
||||
chal = $('#tags-chal').val()
|
||||
@@ -115,6 +119,7 @@ function deletefile(chal, file, elem){
|
||||
|
||||
|
||||
function loadchals(){
|
||||
$('#challenges').empty();
|
||||
$.post("/admin/chals", {
|
||||
'nonce': $('#nonce').val()
|
||||
}, function (data) {
|
||||
@@ -169,6 +174,20 @@ $('#submit-tags').click(function (e) {
|
||||
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) {
|
||||
if (e.keyCode == 13) {
|
||||
tag = $('.tag-insert').val()
|
||||
@@ -184,14 +203,14 @@ $(".tag-insert").keyup(function (e) {
|
||||
$('.create-category').click(function (e) {
|
||||
$('#new-category').foundation('reveal', 'open');
|
||||
});
|
||||
count = 1
|
||||
count = 1;
|
||||
$('#create-key').click(function (e) {
|
||||
$('#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="1">Regex');
|
||||
count++
|
||||
count++;
|
||||
});
|
||||
|
||||
$(function(){
|
||||
loadchals()
|
||||
loadchals();
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<h3>New Challenge</h3>
|
||||
<input type='text' name='name' placeholder='Name'><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="radio" name="key_type[0]" value="0">Static
|
||||
<input type="radio" name="key_type[0]" value="1">Regex
|
||||
@@ -33,7 +33,7 @@
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input type='text' name='name' placeholder='Name'><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 type='text' name='key' placeholder='Key'><br/>
|
||||
|
||||
@@ -49,6 +49,21 @@
|
||||
</form>
|
||||
</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">×</a>
|
||||
</div>
|
||||
|
||||
<div id="update-keys" class="reveal-modal" data-reveal>
|
||||
<form method="POST" action="/admin/keys">
|
||||
<h3>Keys</h3>
|
||||
@@ -100,7 +115,7 @@
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<input class="chal-name" type='text' name='name' placeholder='Name'><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">
|
||||
<option>-</option>
|
||||
</select>
|
||||
@@ -109,6 +124,7 @@
|
||||
<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-keys" class="secondary button">Keys</a>
|
||||
<a href="#" data-reveal-id="delete-chal" class="secondary alert button">Delete</a>
|
||||
<button type='submit'>Update</button>
|
||||
<a class="close-reveal-modal">×</a>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user