diff --git a/README.md b/README.md index b2f7eb9..894aff0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ tinyctf-platform `tinyctf-platform` is yet another open-source (jeopardy style) CTF platform. It is relatively easy to set up and modify. Hopefully it will become even better over time, with other people contributing. -![alt text](http://i.imgur.com/dqGeLNM.jpg "tinyctf-platform in action") +![alt text](https://i.imgur.com/ofB52E4.png "tinyctf-platform in action") Deployment ---------- @@ -20,11 +20,14 @@ Install some prerequisites yum install -y git yum install -y gcc-c++ yum install -y python-devel + yum install -y sqlite3 Install Flask and dataset easy_install Flask easy_install dataset + easy_install python-dateutil + easy_install bleach exit Clone the repo @@ -32,9 +35,9 @@ Clone the repo git clone https://github.com/balidani/tinyctf-platform.git cd tinyctf-platform/ -Import the tasks +Build the database - python task_import.py + ./buildTables.sh Start the server @@ -46,4 +49,3 @@ Caveats ------- * CSRF is currently not addressed -* The platform does not support tasks with the same score and category right now diff --git a/buildTables.sh b/buildTables.sh new file mode 100755 index 0000000..99c312a --- /dev/null +++ b/buildTables.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +mkdir static/files + +sqlite3 ctf.db 'CREATE TABLE categories ( id INTEGER PRIMARY KEY, name TEXT );' +sqlite3 ctf.db 'CREATE TABLE tasks (id INTEGER PRIMARY KEY, name TEXT, desc TEXT, file TEXT, flag TEXT, score INT, category INT, FOREIGN KEY(category) REFERENCES categories(id) ON DELETE CASCADE);' + +sqlite3 ctf.db 'CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT NOT NULL, email TEXT, isAdmin BOOLEAN, isHidden BOOLEAN, password TEXT)'; + +sqlite3 ctf.db 'CREATE TABLE flags (task_id INTEGER, user_id INTEGER, score INTEGER, timestamp BIGINT, ip TEXT, PRIMARY KEY (task_id, user_id), FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE);' diff --git a/config.json b/config.json index 18bafec..8f69b1b 100755 --- a/config.json +++ b/config.json @@ -3,6 +3,10 @@ "host": "0.0.0.0", "port": 8888, + + "isProxied": false, + + "startTime": "3-31-16 8:00AM", "db": "sqlite:///ctf.db", @@ -10,4 +14,4 @@ "language": "english", "debug": false -} \ No newline at end of file +} diff --git a/lang.json b/lang.json index 53952cd..98619de 100755 --- a/lang.json +++ b/lang.json @@ -28,23 +28,54 @@ "submit": "Submit", "success": "Correct flag!", "failure": "Incorrect flag", + "points_format": "%d points", "solution_format": "solved by %d", "no_description": "(none)", - "placeholder": "flag{insert_flag_here}" + "placeholder": "Flag goes here" }, + "addcat" : { + "add_title": "Add a Category", + "edit_title": "Edit a Category", + "delete_title": "Delete a Category", + "delete_desc": "Are you sure you want to delete this category?", + "name_label": "Category Name: " + }, + "addtask" : { + "add_title": "Add a Task", + "edit_title": "Edit a Task", + "delete_title": "Delete task", + "delete_desc": "Are you sure you want to delete this task?", + "cat_label": "Category: ", + "name_label": "Task Name: ", + "description_label": "Description: ", + "flag_label": "Flag: ", + "score_label": "Score: " + }, "scoreboard": { "title": "Scoreboard", "player": "Player", "score": "Score" }, + "settings": { + "title": "Settings", + "old_pw": "Current Password:", + "new_pw": "New Password:", + "email": "Email:" + }, "error": { "title": "Error", "unknown": "Unknown error", "login_required": "You need to be logged in to see this page", + "admin_required": "You need to be an admin to see this page", "invalid_credentials": "Invalid username or password", + "invalid_password": "The password you entered does not match your current password", "already_registered": "This user is already registered", "empty_user": "Empty username is not allowed", - "task_not_found": "TBD" + "task_not_found": "TBD", + "form": "Invalid form input", + "not_started": "Competition hasn't started yet" + + } } -} \ No newline at end of file +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..512baf4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Flask +dataset +python-dateutil +bleach diff --git a/server.py b/server.py index 6c19389..ea19c33 100755 --- a/server.py +++ b/server.py @@ -6,10 +6,20 @@ import json import random import time +import hashlib +import datetime +import os +import dateutil.parser +import bleach from base64 import b64decode from functools import wraps +from sqlalchemy import event +from sqlalchemy.engine import Engine +from sqlite3 import Connection as SQLite3Connection +from werkzeug.contrib.fixers import ProxyFix + from flask import Flask from flask import jsonify from flask import make_response @@ -18,6 +28,7 @@ from flask import request from flask import session from flask import url_for +from flask import Response app = Flask(__name__, static_folder='static', static_url_path='') @@ -25,6 +36,8 @@ lang = None config = None +descAllowedTags = bleach.ALLOWED_TAGS + ['br', 'pre'] + def login_required(f): """Ensures that an user is logged in""" @@ -35,45 +48,68 @@ def decorated_function(*args, **kwargs): return f(*args, **kwargs) return decorated_function +def admin_required(f): + """Ensures that an user is logged in""" + + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + return redirect(url_for('error', msg='login_required')) + user = get_user() + if user["isAdmin"] == False: + return redirect(url_for('error', msg='admin_required')) + return f(*args, **kwargs) + return decorated_function + def get_user(): """Looks up the current user in the database""" - login = 'user_id' in session + login = 'user_id' in session if login: - return (True, db['users'].find_one(id=session['user_id'])) + return db['users'].find_one(id=session['user_id']) - return (False, None) + return None -def get_task(category, score): +def get_task(tid): """Finds a task with a given category and score""" - task = db.query('''select t.* from tasks t, categories c, cat_task ct - where t.id = ct.task_id and c.id = ct.cat_id - and t.score=:score and lower(c.short_name)=:cat''', - score=score, cat=category) - return list(task)[0] + task = db.query("SELECT t.*, c.name cat_name FROM tasks t JOIN categories c on c.id = t.category WHERE t.id = :tid", + tid=tid) + + return task.next() def get_flags(): """Returns the flags of the current user""" - flags = db.query('''select f.task_id from flags f + flags = db.query('''select f.task_id from flags f where f.user_id = :user_id''', user_id=session['user_id']) return [f['task_id'] for f in list(flags)] -@app.route('/error/') +def get_total_completion_count(): + """Returns dictionary where key is task id and value is the number of users who have submitted the flag""" + + c = db.query("select t.id, count(t.id) count from tasks t join flags f on t.id = f.task_id group by t.id;") + + res = {} + for r in c: + res.update({r['id']: r['count']}) + + return res + +@app.route('/error/') def error(msg): """Displays an error message""" - if msg in lang: + if msg in lang['error']: message = lang['error'][msg] else: message = lang['error']['unknown'] - login, user = get_user() + user = get_user() - render = render_template('frame.html', lang=lang, page='error.html', - message=message, login=login, user=user) + render = render_template('frame.html', lang=lang, page='error.html', + message=message, user=user) return make_response(render) def session_login(username): @@ -81,6 +117,14 @@ def session_login(username): user = db['users'].find_one(username=username) session['user_id'] = user['id'] +@event.listens_for(Engine, "connect") +def _set_sqlite_pragma(dbapi_connection, connection_record): + """ Enforces sqlite foreign key constrains """ + if isinstance(dbapi_connection, SQLite3Connection): + cursor = dbapi_connection.cursor() + cursor.execute("PRAGMA foreign_keys=ON;") + cursor.close() + @app.route('/login', methods = ['POST']) def login(): """Attempts to log the user in""" @@ -104,10 +148,14 @@ def login(): def register(): """Displays the register form""" + userCount = db['users'].count() + if datetime.datetime.today() < config['startTime'] and userCount != 0: + return redirect('/error/not_started') + # Render template - render = render_template('frame.html', lang=lang, + render = render_template('frame.html', lang=lang, page='register.html', login=False) - return make_response(render) + return make_response(render) @app.route('/register/submit', methods = ['POST']) def register_submit(): @@ -116,6 +164,7 @@ def register_submit(): from werkzeug.security import generate_password_hash username = request.form['user'] + email = request.form['email'] password = request.form['password'] if not username: @@ -124,9 +173,22 @@ def register_submit(): user_found = db['users'].find_one(username=username) if user_found: return redirect('/error/already_registered') - - new_user = dict(hidden=0, username=username, - password=generate_password_hash(password)) + + isAdmin = False + isHidden = False + userCount = db['users'].count() + + #if no users, make first user admin + if userCount == 0: + isAdmin = True + isHidden = True + elif datetime.datetime.today() < config['startTime']: + return redirect('/error/not_started') + + + new_user = dict(username=username, email=email, + password=generate_password_hash(password), isAdmin=isAdmin, + isHidden=isHidden) db['users'].insert(new_user) # Set up the user id for this session @@ -139,50 +201,234 @@ def register_submit(): def tasks(): """Displays all the tasks in a grid""" - login, user = get_user() - flags = get_flags() + user = get_user() + userCount = db['users'].count(isHidden=0) + isAdmin = user['isAdmin'] categories = db['categories'] + catCount = categories.count() + + flags = get_flags() - tasks = db.query('''select c.id as cat_id, t.id as id, c.short_name, - t.score, t.row from categories c, tasks t, cat_task c_t - where c.id = c_t.cat_id and t.id = c_t.task_id''') + tasks = db.query("SELECT * FROM tasks ORDER BY category, score") tasks = list(tasks) + taskCompletedCount = get_total_completion_count() grid = [] - # Find the max row number - max_row = max(t['row'] for t in tasks) - for row in range(max_row + 1): + for cat in categories: + cTasks = [x for x in tasks if x['category'] == cat['id']] + gTasks = [] - row_tasks = [] - for cat in categories: - - # Find the task with the correct row - for task in tasks: - if task['row'] == row and task['cat_id'] == cat['id']: - break + gTasks.append(cat) + for task in cTasks: + tid = task['id'] + if tid in taskCompletedCount: + percentComplete = (float(taskCompletedCount[tid]) / userCount) * 100 else: - task = None + percentComplete = 0 + + #hax for bad css (if 100, nothing will show) + if percentComplete == 100: + percentComplete = 99.99 + + task['percentComplete'] = percentComplete - row_tasks.append(task) + task['isComplete'] = tid in flags + gTasks.append(task) - grid.append(row_tasks) + if isAdmin: + gTasks.append({'add': True, 'category': cat['id']}) + + grid.append(gTasks) # Render template - render = render_template('frame.html', lang=lang, page='tasks.html', - login=login, user=user, categories=categories, grid=grid, - flags=flags) - return make_response(render) + render = render_template('frame.html', lang=lang, page='tasks.html', + user=user, categories=categories, grid=grid) + return make_response(render) + +@app.route('/addcat/', methods=['GET']) +@admin_required +def addcat(): + user = get_user() + render = render_template('frame.html', lang=lang, user=user, page='addcat.html') + return make_response(render) + +@app.route('/addcat/', methods=['POST']) +@admin_required +def addcatsubmit(): + try: + name = bleach.clean(request.form['name'], tags=[]) + except KeyError: + return redirect('/error/form') + else: + categories = db['categories'] + categories.insert(dict(name=name)) + return redirect('/tasks') + +@app.route('/editcat//', methods=['GET']) +@admin_required +def editcat(id): + user = get_user() + category = db['categories'].find_one(id=id) + render = render_template('frame.html', lang=lang, user=user, category=category, page='editcat.html') + return make_response(render) -@app.route('/tasks//') +@app.route('/editcat//', methods=['POST']) +@admin_required +def editcatsubmit(catId): + try: + name = bleach.clean(request.form['name'], tags=[]) + except KeyError: + return redirect('/error/form') + else: + categories = db['categories'] + categories.update(dict(name=name, id=catId), ['id']) + return redirect('/tasks') + +@app.route('/editcat//delete', methods=['GET']) +@admin_required +def deletecat(catId): + category = db['categories'].find_one(id=catId) + + user = get_user() + render = render_template('frame.html', lang=lang, user=user, page='deletecat.html', category=category) + return make_response(render) + +@app.route('/editcat//delete', methods=['POST']) +@admin_required +def deletecatsubmit(catId): + db['categories'].delete(id=catId) + return redirect('/tasks') + +@app.route('/addtask//', methods=['GET']) +@admin_required +def addtask(cat): + category = db['categories'].find_one(id=cat) + + user = get_user() + + render = render_template('frame.html', lang=lang, user=user, + cat_name=category['name'], cat_id=category['id'], page='addtask.html') + return make_response(render) + +@app.route('/addtask//', methods=['POST']) +@admin_required +def addtasksubmit(cat): + try: + name = bleach.clean(request.form['name'], tags=[]) + desc = bleach.clean(request.form['desc'], tags=descAllowedTags) + category = int(request.form['category']) + score = int(request.form['score']) + flag = request.form['flag'] + except KeyError: + return redirect('/error/form') + + else: + tasks = db['tasks'] + task = dict( + name=name, + desc=desc, + category=category, + score=score, + flag=flag) + file = request.files['file'] + + if file: + filename, ext = os.path.splitext(file.filename) + #hash current time for file name + filename = hashlib.md5(str(datetime.datetime.utcnow())).hexdigest() + #if upload has extension, append to filename + if ext: + filename = filename + ext + file.save(os.path.join("static/files/", filename)) + task["file"] = filename + + tasks.insert(task) + return redirect('/tasks') + +@app.route('/tasks//edit', methods=['GET']) +@admin_required +def edittask(tid): + user = get_user() + + task = db["tasks"].find_one(id=tid); + category = db["categories"].find_one(id=task['category']) + + render = render_template('frame.html', lang=lang, user=user, + cat_name=category['name'], cat_id=category['id'], + page='edittask.html', task=task) + return make_response(render) + +@app.route('/tasks//edit', methods=['POST']) +@admin_required +def edittasksubmit(tid): + try: + name = bleach.clean(request.form['name'], tags=[]) + desc = bleach.clean(request.form['desc'], tags=descAllowedTags) + category = int(request.form['category']) + score = int(request.form['score']) + flag = request.form['flag'] + except KeyError: + return redirect('/error/form') + + else: + tasks = db['tasks'] + task = tasks.find_one(id=tid) + task['id']=tid + task['name']=name + task['desc']=desc + task['category']=category + task['score']=score + + #only replace flag if value specified + if flag: + task['flag']=flag + + file = request.files['file'] + + if file: + filename, ext = os.path.splitext(file.filename) + #hash current time for file name + filename = hashlib.md5(str(datetime.datetime.utcnow())).hexdigest() + #if upload has extension, append to filename + if ext: + filename = filename + ext + file.save(os.path.join("static/files/", filename)) + + #remove old file + if task['file']: + os.remove(os.path.join("static/files/", task['file'])) + + task["file"] = filename + + tasks.update(task, ['id']) + return redirect('/tasks') + +@app.route('/tasks//delete', methods=['GET']) +@admin_required +def deletetask(tid): + tasks = db['tasks'] + task = tasks.find_one(id=tid) + + user = get_user() + render = render_template('frame.html', lang=lang, user=user, page='deletetask.html', task=task) + return make_response(render) + +@app.route('/tasks//delete', methods=['POST']) +@admin_required +def deletetasksubmit(tid): + db['tasks'].delete(id=tid) + return redirect('/tasks') + +@app.route('/tasks//') @login_required -def task(category, score): +def task(tid): """Displays a task with a given category and score""" - login, user = get_user() + user = get_user() - task = get_task(category, score) + task = get_task(tid) if not task: return redirect('/error/task_not_found') @@ -193,21 +439,19 @@ def task(category, score): solutions = len(list(solutions)) # Render template - render = render_template('frame.html', lang=lang, page='task.html', + render = render_template('frame.html', lang=lang, page='task.html', task_done=task_done, login=login, solutions=solutions, - user=user, category=category, task=task, score=score) + user=user, category=task["cat_name"], task=task, score=task["score"]) return make_response(render) -@app.route('/submit///') +@app.route('/submit//') @login_required -def submit(category, score, flag): +def submit(tid, flag): """Handles the submission of flags""" - print "ok" + user = get_user() - login, user = get_user() - - task = get_task(category, score) + task = get_task(tid) flags = get_flags() task_done = task['id'] in flags @@ -215,10 +459,12 @@ def submit(category, score, flag): if not task_done and task['flag'] == b64decode(flag): timestamp = int(time.time() * 1000) + ip = request.remote_addr + print "flag submitter ip: {}".format(ip) # Insert flag - new_flag = dict(task_id=task['id'], user_id=session['user_id'], - score=score, timestamp=timestamp) + new_flag = dict(task_id=task['id'], user_id=session['user_id'], + score=task["score"], timestamp=timestamp, ip=ip) db['flags'].insert(new_flag) result['success'] = True @@ -230,30 +476,74 @@ def submit(category, score, flag): def scoreboard(): """Displays the scoreboard""" - login, user = get_user() - scores = db.query('''select u.username, ifnull(sum(f.score), 0) as score, - max(timestamp) as last_submit from users u left join flags f - on u.id = f.user_id where u.hidden = 0 group by u.username + user = get_user() + scores = db.query('''select u.username, ifnull(sum(f.score), 0) as score, + max(timestamp) as last_submit from users u left join flags f + on u.id = f.user_id where u.isHidden = 0 group by u.username order by score desc, last_submit asc''') scores = list(scores) # Render template - render = render_template('frame.html', lang=lang, page='scoreboard.html', - login=login, user=user, scores=scores) - return make_response(render) + render = render_template('frame.html', lang=lang, page='scoreboard.html', + user=user, scores=scores) + return make_response(render) + +@app.route('/scoreboard.json') +def scoreboard_json(): + scores = db.query('''select u.username, ifnull(sum(f.score), 0) as score, + max(timestamp) as last_submit from users u left join flags f + on u.id = f.user_id where u.isHidden = 0 group by u.username + order by score desc, last_submit asc''') + + scores = list(scores) + + return Response(json.dumps(scores), mimetype='application/json') @app.route('/about') @login_required def about(): """Displays the about menu""" - login, user = get_user() + user = get_user() # Render template - render = render_template('frame.html', lang=lang, page='about.html', - login=login, user=user) - return make_response(render) + render = render_template('frame.html', lang=lang, page='about.html', + user=user) + return make_response(render) + +@app.route('/settings') +@login_required +def settings(): + user = get_user() + render = render_template('frame.html', lang=lang, page='settings.html', + user=user) + return make_response(render) + +@app.route('/settings', methods = ['POST']) +@login_required +def settings_submit(): + from werkzeug.security import check_password_hash + from werkzeug.security import generate_password_hash + + user = get_user() + try: + old_pw = request.form['old-pw'] + new_pw = request.form['new-pw'] + email = request.form['email'] + except KeyError: + return redirect('/error/form') + + if old_pw and check_password_hash(user['password'], old_pw): + if new_pw: + user['password'] = generate_password_hash(new_pw) + if email: + user['email'] = email + else: + return redirect('/error/invalid_password') + + db["users"].update(user, ['id']) + return redirect('/tasks') @app.route('/logout') @login_required @@ -267,42 +557,42 @@ def logout(): def index(): """Displays the main page""" - login, user = get_user() + user = get_user() # Render template - render = render_template('frame.html', lang=lang, - page='main.html', login=login, user=user) + render = render_template('frame.html', lang=lang, + page='main.html', user=user) return make_response(render) -if __name__ == '__main__': - """Initializes the database and sets up the language""" +"""Initializes the database and sets up the language""" - # Load config - config_str = open('config.json', 'rb').read() - config = json.loads(config_str) +# Load config +config_str = open('config.json', 'rb').read() +config = json.loads(config_str) - app.secret_key = config['secret_key'] +app.secret_key = config['secret_key'] - # Load language - lang_str = open(config['language_file'], 'rb').read() - lang = json.loads(lang_str) +# Convert start date to python object +if config['startTime']: + config['startTime'] = dateutil.parser.parse(config['startTime']) +else: + config['startTime'] = datetime.datetime.min - # Only a single language is supported for now - lang = lang[config['language']] +# Load language +lang_str = open(config['language_file'], 'rb').read() +lang = json.loads(lang_str) - # Connect to database - db = dataset.connect(config['db']) +# Only a single language is supported for now +lang = lang[config['language']] - # Setup the flags table at first execution - if 'flags' not in db.tables: - db.query('''create table flags ( - task_id INTEGER, - user_id INTEGER, - score INTEGER, - timestamp BIGINT, - PRIMARY KEY (task_id, user_id))''') +# Connect to database +db = dataset.connect(config['db']) +if config['isProxied']: + app.wsgi_app = ProxyFix(app.wsgi_app) + +if __name__ == '__main__': # Start web server - app.run(host=config['host'], port=config['port'], + app.run(host=config['host'], port=config['port'], debug=config['debug'], threaded=True) diff --git a/static/css/ctf.css b/static/css/ctf.css index 8678835..e803c02 100755 --- a/static/css/ctf.css +++ b/static/css/ctf.css @@ -1,5 +1,26 @@ body { - background: transparent; + background: transparent; +} + + + +* { + font-family: 'Open Sans', sans-serif; +} + + +/* Background style */ +#particles-js { + position: fixed; + top: 0; + left: 0; + width: 100%; + background-size: cover; + background-position: center 75%; +} + +#particles-js > canvas { + background-color: rgb(103,124,131); } /* @@ -7,12 +28,15 @@ body { */ .full { -background: url('../img/background.jpg') no-repeat center center fixed; +background-color: rgb(68,78,82); -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; background-size: cover; } +.table{ + position:relative; +} .table > thead > tr > th { background-color: rgba(0, 0, 0, 0.5); color: #eee; @@ -38,7 +62,8 @@ background-size: cover; */ .landing-page { - padding-top: 100px; + padding-top: 70px; + padding-bottom: 40px; } .landing-page h1 { @@ -50,17 +75,15 @@ background-size: cover; * Main circle */ -.circle { - margin: 0; - width:550px; - height:550px; - padding-top: 120px; - border-radius:50%; +.main { + position: relative; + top: 20px; + padding: 20px 0; text-align:center; - background-color: rgba(255, 255, 255, 0.75); + background-color: rgba(255, 255, 255, 0.75); } -.circle-inner { +.main > p { padding-left: 50px; padding-right: 50px; @@ -76,26 +99,96 @@ background-size: cover; * Tasks page */ +.category-column > a{ + float: left; + clear: left; +} + +.category-column{ + float: left; +} + + +.chall-box-score { + z-index:100; + position:relative; + width: 130px; + left: 0; + top: 40px; + text-align:center; + font-size: 20pt +} .chall-box { - margin: 20px; - width: 120px; - height: 120px; - padding-top: 45px; + margin: 20px; + display: inline-block; + position: relative; + width: 120px; + height: 120px; + border-radius: 50%; + background-color: #e5e5e5; + background-image: linear-gradient(to right, transparent 50%, green 0); + color: black; + text-align: center; + +} + +@keyframes spin { + to { transform: rotate(.5turn); } +} +@keyframes bg { + 50% { background: green; } +} + +.chall-box::before { + content: ''; + position: absolute; + top: 0; left: 50%; + width: 50%; height: 100%; + border-radius: 0 100% 100% 0 / 50%; + background-color: inherit; + transform-origin: left; + animation: spin 50s linear infinite, bg 100s step-end infinite; + animation-play-state: paused; + animation-delay: inherit; +} + +.chall-box::after{ + content: ''; + top: 7px; left: 7px; + width: 106px; height: 106px; + position:absolute; + border-radius: 100%; + background-color: #ccc; +} + + +.add-box { + + margin: 50px; + width: 60px; + height: 60px; border-radius: 50%; text-align:center; - background-color: rgba(255, 255, 255, 0.75); - font-size: 16pt; - color: #333; + background-color: rgba(0,0,0,0.25); + font-size: 30pt; + color: white; } .chall-category { + margin: 20px; + width: 119px; + height: 119px; + padding-top: 45px; + border-radius: 50%; + text-align: center; background-color: rgba(0, 0, 0, 0.5); + font-size: 16pt; color: white; } .chall-done { - -webkit-box-shadow: 0 0 10px 3px #51A351; - box-shadow: 0 0 10px 3px #51A351; + -webkit-box-shadow: 0 0 10px 5px #51A351; + box-shadow: 0 0 10px 5px #51A351; } .chall-highlight { @@ -104,6 +197,10 @@ background-size: cover; } .chall-hidden { + margin: 20px; + width: 120px; + height: 120px; + padding-top: 45px; background-color: rgba(255, 255, 255, 0); } @@ -114,7 +211,7 @@ background-size: cover; .task-box { background-color: rgba(255, 255, 255, 0.75); padding-bottom: 20px; - width: 600px; + max-width: 600px; } .task-box input { @@ -127,4 +224,21 @@ background-size: cover; .task-box h5 { text-align: center; -} \ No newline at end of file +} + +.admin-task-button { + position: absolute; + top: 0; + padding: 15px; + font-size: 20pt; + background-color: #999; + color: black; +} + +#task-edit-button { + right: 0; +} + +#task-delete-button { + left: 0; +} diff --git a/static/files/cry100.zip b/static/files/cry100.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/cry100.zip and /dev/null differ diff --git a/static/files/cry200.zip b/static/files/cry200.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/cry200.zip and /dev/null differ diff --git a/static/files/misc100.zip b/static/files/misc100.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/misc100.zip and /dev/null differ diff --git a/static/files/misc50.zip b/static/files/misc50.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/misc50.zip and /dev/null differ diff --git a/static/files/pwn200.zip b/static/files/pwn200.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/pwn200.zip and /dev/null differ diff --git a/static/files/rev100.zip b/static/files/rev100.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/rev100.zip and /dev/null differ diff --git a/static/files/stego100.zip b/static/files/stego100.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/stego100.zip and /dev/null differ diff --git a/static/files/web100.zip b/static/files/web100.zip deleted file mode 100755 index e220365..0000000 Binary files a/static/files/web100.zip and /dev/null differ diff --git a/static/fonts/glyphicons-halflings-regular.ttf b/static/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.ttf differ diff --git a/static/fonts/glyphicons-halflings-regular.woff b/static/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/static/fonts/glyphicons-halflings-regular.woff differ diff --git a/static/img/background.jpg b/static/img/background.jpg deleted file mode 100755 index cd7ed4f..0000000 Binary files a/static/img/background.jpg and /dev/null differ diff --git a/static/js/bootstrap.min.js b/static/js/bootstrap.min.js new file mode 100644 index 0000000..e9d8b53 --- /dev/null +++ b/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/static/js/index.html b/static/js/index.html new file mode 100644 index 0000000..e540eea --- /dev/null +++ b/static/js/index.html @@ -0,0 +1,14 @@ + + + + + + + + +
+ + diff --git a/static/js/submit.js b/static/js/submit.js index 93c6047..6ca2e27 100755 --- a/static/js/submit.js +++ b/static/js/submit.js @@ -1,13 +1,10 @@ $("#flag-submission").click(function() { + var flag = $("#flag-input").val(); - var cat = $(".task-box").data("category"); - var score = $(".task-box").data("score"); - var flag = $("#flag-input").val(); - - console.log("/submit/" + cat + "/" + score + "/" + btoa(flag)); + var id = $("#task-id").val() $.ajax({ - url: "/submit/" + cat + "/" + score + "/" + btoa(flag) + url: "/submit/" + id + "/" + btoa(flag) }).done(function(data) { console.log(data); @@ -21,4 +18,4 @@ $("#flag-submission").click(function() { $("#flag-input").val($(".lang").data("failure")); } }); -}); \ No newline at end of file +}); diff --git a/task_import.py b/task_import.py deleted file mode 100755 index 50dd31d..0000000 --- a/task_import.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python - -"""task_import.py -- imports tasks from 'tasks.json' into the database""" - -import dataset -import json -import sys - -if __name__ == '__main__': - - purge = False - - # Handle the optional purge argument - if len(sys.argv) > 1: - if sys.argv[1] == 'purge': - - purge = True - print "[*] Purge mode is on, old tasks deleted" - - # Load the json structure - tasks_str = open('tasks.json', 'rb').read() - tasks_json = json.loads(tasks_str) - - # Connect to database - db = dataset.connect('sqlite:///ctf.db') - cat_table = db['categories'] - tasks_table = db['tasks'] - cat_task_table = db['cat_task'] - - if purge: - cat_table.delete() - tasks_table.delete() - cat_task_table.delete() - - # Parse the json file and add rows to the table - old_cat_count = len(list(cat_table)) - old_task_count = len(list(tasks_table)) - - # First, create categories - for category in tasks_json['categories']: - - cat = category.copy() - del cat['tasks'] - - if cat_table.find_one(id=cat['id']): - - # Update existing category - cat_table.update(cat, ['id']) - - else: - cat_table.insert(cat) - - new_cat_count = len(list(cat_table)) - print "[*] Imported %d new categories" % (new_cat_count - old_cat_count) - - # Then create tasks - for category in tasks_json['categories']: - for task in category['tasks']: - - if tasks_table.find_one(id=task['id']): - - # Update existing table - tasks_table.update(task, ['id']) - else: - tasks_table.insert(task) - - # In this case, we should add an entry to the category_task table as well - row = dict(cat_id=category['id'], task_id=task['id']) - cat_task_table.insert(row) - - new_task_count = len(list(tasks_table)) - print "[*] Imported %d new tasks" % (new_task_count - old_task_count) \ No newline at end of file diff --git a/tasks.json b/tasks.json deleted file mode 100755 index 59c89d4..0000000 --- a/tasks.json +++ /dev/null @@ -1,86 +0,0 @@ -{"categories": [ - {"id": 0, "name": "Misc.", "short_name": "Misc", "tasks": [ - { - "id": 0, - "row": 0, - "name": "Test flag, please ignore", - "score": "50", - "flag": "flag{hello_world}", - "desc": "The flag is hello_world, with the flag tag and brackets", - "file": "misc50.zip" - }, - { - "id": 1, - "row": 1, - "name": "Test flag, please ignore 2", - "score": "100", - "flag": "flag{test_test}", - "desc": "The flag is test_test, with the flag tag and brackets", - "file": "misc100.zip" - } - ]}, - {"id": 1, "name": "Web", "short_name": "Web", "tasks": [ - { - "id": 2, - "row": 0, - "name": "Some sqli task", - "score": "100", - "flag": "flag{web_test}", - "desc": "The flag is web_test, with the flag tag and brackets", - "file": "web100.zip" - } - ]}, - {"id": 2, "name": "Reversing", "short_name": "Rev", "tasks": [ - { - "id": 3, - "row": 0, - "name": "gdb is your friend", - "score": "100", - "flag": "flag{reversing_is_fun}", - "desc": "The flag is reversing_is_fun, with the flag tag and brackets", - "file": "rev100.zip" - } - ]}, - {"id": 3, "name": "Exploit", "short_name": "Exp", "tasks": [ - { - "id": 4, - "row": 0, - "name": "Smashing the stack for fun and profit", - "score": "200", - "flag": "flag{pwning_is_even_more_fun}", - "desc": "The flag is pwning_is_even_more_fun, with the flag tag and brackets", - "file": "pwn200.zip" - } - ]}, - {"id": 4, "name": "Crypto", "short_name": "Cry", "tasks": [ - { - "id": 5, - "row": 0, - "name": "Some simple crypto task here", - "score": "100", - "flag": "flag{some_crypto_task}", - "desc": "The flag is some_crypto_task, with the flag tag and brackets", - "file": "cry100.zip" - }, - { - "id": 6, - "row": 1, - "name": "More crypto tasks!", - "score": "200", - "flag": "flag{more_crypto}", - "desc": "The flag is more_crypto, with the flag tag and brackets", - "file": "cry200.zip" - } - ]}, - {"id": 5, "name": "Stego", "short_name": "Steg", "tasks": [ - { - "id": 7, - "row": 0, - "name": "Ucucuga", - "score": "100", - "flag": "flag{ucucuga}", - "desc": "The flag is ucucuga, with the flag tag and brackets", - "file": "stego100.zip" - } - ]} -]} \ No newline at end of file diff --git a/templates/addcat.html b/templates/addcat.html new file mode 100755 index 0000000..d29dbcb --- /dev/null +++ b/templates/addcat.html @@ -0,0 +1,14 @@ +
+
+
+

{{ lang.addcat.add_title }}

+
+ + + +
+
+ +
+
+ diff --git a/templates/addtask.html b/templates/addtask.html new file mode 100755 index 0000000..d17dfd5 --- /dev/null +++ b/templates/addtask.html @@ -0,0 +1,31 @@ +
+
+
+

{{ lang.addtask.add_title }}

+
{{ lang.addtask.cat_label}}{{ cat_name }}
+
+ + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ diff --git a/templates/deletecat.html b/templates/deletecat.html new file mode 100755 index 0000000..935c866 --- /dev/null +++ b/templates/deletecat.html @@ -0,0 +1,14 @@ +
+
+
+

{{ lang.addcat.delete_title }}

+ +

{{ category.name }}

+
+ +
+
+ +
+
+ diff --git a/templates/deletetask.html b/templates/deletetask.html new file mode 100755 index 0000000..7cea2a6 --- /dev/null +++ b/templates/deletetask.html @@ -0,0 +1,16 @@ +
+
+
+

{{ lang.addtask.delete_title }}

+ +

{{ task.name }}

+ +

{{ task.score }}

+
+ +
+
+ +
+
+ diff --git a/templates/editcat.html b/templates/editcat.html new file mode 100755 index 0000000..585d28d --- /dev/null +++ b/templates/editcat.html @@ -0,0 +1,17 @@ +
+
+
+ + + +

{{ lang.addcat.edit_title }}

+
+ + + +
+
+ +
+
+ diff --git a/templates/edittask.html b/templates/edittask.html new file mode 100755 index 0000000..35fe37e --- /dev/null +++ b/templates/edittask.html @@ -0,0 +1,32 @@ +
+
+
+

{{ lang.addtask.edit_title }}

+
{{ lang.addtask.cat_label}}{{ cat_name }}
+
+ + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ diff --git a/templates/error.html b/templates/error.html index 20389ed..ed09788 100755 --- a/templates/error.html +++ b/templates/error.html @@ -1,3 +1,5 @@
-

{{ lang.error.title }}: {{ message }}

-
\ No newline at end of file +
+

{{ lang.error.title }}: {{ message }}

+
+ diff --git a/templates/frame.html b/templates/frame.html index 8b0cc59..74919aa 100755 --- a/templates/frame.html +++ b/templates/frame.html @@ -2,15 +2,20 @@ + + + {{ lang.frame.title }} - + +