diff --git a/problems/misc/survey/README.md b/problems/misc/survey/README.md index ca08a94..bb3cca5 100644 --- a/problems/misc/survey/README.md +++ b/problems/misc/survey/README.md @@ -8,28 +8,31 @@ The directory *must* contain a `problem.json`; this information will be loaded i ```javascript { - "pid": "survey", // required - "title": "Survey", // required - "description": "Take our survey.", // required - can use HTML - "hint": "No hint!", // optional - defaults to "" - "category": "Miscellaneous", // required - "autogen": false, // optional - defaults to false - "programming": false, // optional - defaults to false - "value": 20, // required - integer out of 800 - "bonus": 0, // optional - defaults to 0; see below for details - "threshold": 0, // recommended - defaults to 0; see below for details - "weightmap": { } // recommended - defaults to {} + "pid": "survey", // required + "title": "Survey", // required + "hint": "No hint!", // optional - defaults to "" + "category": "Miscellaneous", // required + "autogen": false, // optional - defaults to false + "programming": false, // optional - defaults to false + "value": 20, // required - integer out of 800 + "bonus": 0, // optional - defaults to 0; see below for details + "threshold": 0, // recommended - defaults to 0; see below for details + "weightmap": { } // recommended - defaults to {} } ``` +## `description.md` + +The directory *must* contain a `description.md`. Just write your description here in Markdown. If you're using `autogen: true`, you can include `${}` variables to produce dynamic content. + ## `grader.py` The directory must *also* contain a `grader.py`; this script must contain a `grade` function that takes in two parameters: `tid`, the team ID and `answer`, their attempted answer. The function must return a dict containing the following keys: ```python { - "correct": True # or False, indicating whether the answer is correct. - "message": "Congratulations!" # a custom message for feedback on the website. + "correct": True # or False, indicating whether the answer is correct. + "message": "Congratulations!" # a custom message for feedback on the website. } ``` @@ -37,9 +40,9 @@ For the most part, checking in flags will simply be a matter of comparing string ```python def grade(tid, answer): - if answer.find("csrf_protection_would_probably_have_been_a_good_idea_:/") != -1: - return { "correct": True, "message": "Indeed." } - return { "correct": False, "message": "Nope, that's not quite right." } + if answer.find("csrf_protection_would_probably_have_been_a_good_idea_:/") != -1: + return { "correct": True, "message": "Indeed." } + return { "correct": False, "message": "Nope, that's not quite right." } ``` You can copy-paste the above example into your grader and make any necessary adjustments. If you were wondering why we don't just compare strings, some problems may warrant a more complicated grader, like the problem H4SH3D from last year: @@ -48,31 +51,31 @@ You can copy-paste the above example into your grader and make any necessary adj import binascii def compute(uinput): - if len(uinput) > 32: return "" - blen = 32 - n = blen - len(uinput) % blen - if n == 0: - n = blen - pad = chr(n) - ninput = uinput + pad * n - r = "" - for i in range(0, blen, 4): - s = ninput[i:i+4] - h = 0 - for j in range(len(s)): - h = (h << 4) + ord(s[j]) - g = h & 4026531840 - if not(g == 0): - h ^= g >> 24 - h &= ~g - r += chr(h % 256) - h = binascii.hexlify(bytes(r, 'Latin-1')) - return h + if len(uinput) > 32: return "" + blen = 32 + n = blen - len(uinput) % blen + if n == 0: + n = blen + pad = chr(n) + ninput = uinput + pad * n + r = "" + for i in range(0, blen, 4): + s = ninput[i:i+4] + h = 0 + for j in range(len(s)): + h = (h << 4) + ord(s[j]) + g = h & 4026531840 + if not(g == 0): + h ^= g >> 24 + h &= ~g + r += chr(h % 256) + h = binascii.hexlify(bytes(r, 'Latin-1')) + return h def grade(tid, answer): - if compute(answer) == compute("they_see_me_hashin_they_hatin"): - return { "correct": True, "message": "They see me hashin' they hatin'" } - return { "correct": False, "message": "Nope." } + if compute(answer) == compute("they_see_me_hashin_they_hatin"): + return { "correct": True, "message": "They see me hashin' they hatin'" } + return { "correct": False, "message": "Nope." } ``` ## `static` @@ -98,7 +101,7 @@ Bonus points encourage teams to finish solving a problem first. Rather than an a | 4 | 6% | 8% | 10% | | 5 | 8% | 12% | 20% | -The table indicates how many percent bonus a team should receive if they solve a problem first, second, or third. Low problems such as the survey should not yield bonus points; only high-valued points should have bonus points in order to encourage teams t o solve them first. +The table indicates how many percent bonus a team should receive if they solve a problem first, second, or third. Low problems such as the survey should not yield bonus points; only high-valued points should have bonus points in order to encourage teams to solve them first. ## Problem Unlocking @@ -106,20 +109,20 @@ Problem unlocking is managed through a mechanism that involves a threshold and w ```javascript { - "pid": "launch-code", - "threshold": 5, - "weightmap": { - "php3": 1, - "faster-math": 1, - "biggerisbetter": 1, - "cave-johnson": 1, - "blackmesa": 1, - "rsa3": 1, - "yandere": 1, - "rsi": 1, - "adoughbee": 1, - "infinity_star": 1 - } + "pid": "launch-code", + "threshold": 5, + "weightmap": { + "php3": 1, + "faster-math": 1, + "biggerisbetter": 1, + "cave-johnson": 1, + "blackmesa": 1, + "rsa3": 1, + "yandere": 1, + "rsi": 1, + "adoughbee": 1, + "infinity_star": 1 + } } ``` diff --git a/problems/misc/survey/description.md b/problems/misc/survey/description.md new file mode 100644 index 0000000..e31e020 --- /dev/null +++ b/problems/misc/survey/description.md @@ -0,0 +1 @@ +Take our survey. \ No newline at end of file diff --git a/problems/misc/survey/problem.json b/problems/misc/survey/problem.json index 1e4bf11..94ee4c8 100644 --- a/problems/misc/survey/problem.json +++ b/problems/misc/survey/problem.json @@ -1,6 +1,6 @@ { + "pid": "survey", "title": "Survey", - "description": "Take our survey.", "hint": "No hint!", "category": "Miscellaneous", "autogen": false, diff --git a/problems/programming/cancer/description.md b/problems/programming/cancer/description.md new file mode 100644 index 0000000..01077f5 --- /dev/null +++ b/problems/programming/cancer/description.md @@ -0,0 +1 @@ +Help! I started growing this array culture, but it outgrew its petri dish! Using standard array syntax (either JavaScript or Python), indicate which element of the array contains the value ${value}. \ No newline at end of file diff --git a/problems/programming/cancer/problem.json b/problems/programming/cancer/problem.json new file mode 100644 index 0000000..34e1a20 --- /dev/null +++ b/problems/programming/cancer/problem.json @@ -0,0 +1,12 @@ +{ + "pid": "cancer", + "title": "Cancer", + "hint": "No hint!", + "category": "Miscellaneous", + "autogen": false, + "programming": false, + "value": 20, + "bonus": 0, + "threshold": 0, + "weightmap": { } +} \ No newline at end of file diff --git a/server/api/__init__.py b/server/api/__init__.py index 4c362a8..a477503 100644 --- a/server/api/__init__.py +++ b/server/api/__init__.py @@ -5,4 +5,5 @@ import problem import user import stats import team +import tools import utils diff --git a/server/api/problem.py b/server/api/problem.py index 47d1402..82e289c 100644 --- a/server/api/problem.py +++ b/server/api/problem.py @@ -130,3 +130,7 @@ def problem_data(): jason.append({"pid": problem[1], "name": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "files": problem_files}) return jsonify(data=jason) + +def insert_problem(data): + print data + pass \ No newline at end of file diff --git a/server/api/tools.py b/server/api/tools.py new file mode 100644 index 0000000..e69de29 diff --git a/server/app.py b/server/app.py index 91e4ef4..62d8f7e 100644 --- a/server/app.py +++ b/server/app.py @@ -1,3 +1,5 @@ +#!/usr/bin/python + from argparse import ArgumentParser from flask import Flask @@ -6,6 +8,7 @@ app = Flask(__name__) import api import config import json +import logging import os from api.decorators import api_wrapper @@ -34,12 +37,65 @@ api.logger.initialize_logs() def api_main(): return { "success": 1, "message": "The API is online." } -if __name__ == "__main__": +def run(args): with app.app_context(): - parser = ArgumentParser(description="EasyCTF Server Configuration") - parser.add_argument("-d", "--debug", action="store_true", help="Run the server in debug mode.", default=False) - args = parser.parse_args() - keyword_args, _ = dict(args._get_kwargs()), args._get_args() - app.debug = keyword_args["debug"] app.run(host="0.0.0.0", port=8000) + +def load_problems(args): + if not os.path.exists(config.PROBLEM_DIR): + logging.critical("Problems directory doesn't exist.") + return + + for (dirpath, dirnames, filenames) in os.walk(config.PROBLEM_DIR): + if "problem.json" in filenames: + json_file = os.path.join(dirpath, "problem.json") + contents = open(json_file).read() + + try: + data = json.loads(contents) + except ValueError as e: + logging.warning("Invalid JSON format in file {filename} ({exception})".format(filename=json_file, exception=e)) + continue + + if not isinstance(data, dict): + logging.warning("{filename} is not a dict.".format(filename=json_file)) + continue + + missing_keys = [] + for key in ["pid", "title", "category", "value"]: + if key not in data: + missing_keys.append(key) + if len(missing_keys) > 0: + logging.warning("{filename} is missing the following keys: {keys}".format(filename=json_file, keys=", ".join(missing_keys))) + continue + + relative_path = os.path.relpath(dirpath, config.PROBLEM_DIR) + logging.info("Found problem '{}'".format(data["title"])) + + try: + api.problem.insert_problem(data) + except Exception as e: + logging.warning("Problem '{}' was not added to the database. Error: {}".format(data["title"], e)) + +if __name__ == "__main__": + parser = ArgumentParser(description="EasyCTF Server Management") + + subparser = parser.add_subparsers(help="Select one of the following actions.") + parser_problems = subparser.add_parser("problems", help="Manage problems.") + subparser_problems = parser_problems.add_subparsers(help="Select one of the following actions.") + parser_problems_load = subparser_problems.add_parser("load", help="Load all problems into database.") + parser_problems_load.set_defaults(func=load_problems) + + parser_run = subparser.add_parser("run", help="Run the server.") + parser_run.add_argument("-d", "--debug", action="store_true", help="Run the server in debug mode.", default=False) + parser_run.set_defaults(func=run) + + args = parser.parse_args() + keyword_args, _ = dict(args._get_kwargs()), args._get_args() + logging.getLogger().setLevel(logging.INFO) + + if "func" in args: + args.func(args) + else: + parser.print_help() \ No newline at end of file diff --git a/server/config.py b/server/config.py index 9f47195..44f2c2e 100644 --- a/server/config.py +++ b/server/config.py @@ -23,3 +23,5 @@ CTF_END = 0 # To be used later MG_HOST = "" MG_API_KEY = "" ADMIN_EMAIL = "" + +PROBLEM_DIR = "../problems" \ No newline at end of file