mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-18 14:34:21 +01:00
* Rename flag files to simplify naming * Fix update.html loading from custom folder in update view
206 lines
6.9 KiB
Python
206 lines
6.9 KiB
Python
from CTFd.plugins import register_plugin_assets_directory
|
|
from CTFd.plugins.flags import get_flag_class
|
|
from CTFd.models import db, Solves, Fails, Flags, Challenges, ChallengeFiles, Tags, Hints
|
|
from CTFd import utils
|
|
from CTFd.utils.user import get_ip
|
|
from CTFd.utils.uploads import upload_file, delete_file
|
|
from flask import Blueprint
|
|
import six
|
|
|
|
|
|
class BaseChallenge(object):
|
|
id = None
|
|
name = None
|
|
templates = {}
|
|
scripts = {}
|
|
|
|
|
|
class CTFdStandardChallenge(BaseChallenge):
|
|
id = "standard" # Unique identifier used to register challenges
|
|
name = "standard" # Name of a challenge type
|
|
templates = { # Templates used for each aspect of challenge editing & viewing
|
|
'create': '/plugins/challenges/assets/create.html',
|
|
'update': '/plugins/challenges/assets/update.html',
|
|
'view': '/plugins/challenges/assets/view.html',
|
|
}
|
|
scripts = { # Scripts that are loaded when a template is loaded
|
|
'create': '/plugins/challenges/assets/create.js',
|
|
'update': '/plugins/challenges/assets/update.js',
|
|
'view': '/plugins/challenges/assets/view.js',
|
|
}
|
|
# Route at which files are accessible. This must be registered using register_plugin_assets_directory()
|
|
route = '/plugins/challenges/assets/'
|
|
# Blueprint used to access the static_folder directory.
|
|
blueprint = Blueprint('standard', __name__, template_folder='templates', static_folder='assets')
|
|
|
|
@staticmethod
|
|
def create(request):
|
|
"""
|
|
This method is used to process the challenge creation request.
|
|
|
|
:param request:
|
|
:return:
|
|
"""
|
|
data = request.form or request.get_json()
|
|
|
|
challenge = Challenges(**data)
|
|
|
|
db.session.add(challenge)
|
|
db.session.commit()
|
|
|
|
return challenge
|
|
|
|
@staticmethod
|
|
def read(challenge):
|
|
"""
|
|
This method is in used to access the data of a challenge in a format processable by the front end.
|
|
|
|
:param challenge:
|
|
:return: Challenge object, data dictionary to be returned to the user
|
|
"""
|
|
data = {
|
|
'id': challenge.id,
|
|
'name': challenge.name,
|
|
'value': challenge.value,
|
|
'description': challenge.description,
|
|
'category': challenge.category,
|
|
'state': challenge.state,
|
|
'max_attempts': challenge.max_attempts,
|
|
'type': challenge.type,
|
|
'type_data': {
|
|
'id': CTFdStandardChallenge.id,
|
|
'name': CTFdStandardChallenge.name,
|
|
'templates': CTFdStandardChallenge.templates,
|
|
'scripts': CTFdStandardChallenge.scripts,
|
|
}
|
|
}
|
|
return data
|
|
|
|
@staticmethod
|
|
def update(challenge, request):
|
|
"""
|
|
This method is used to update the information associated with a challenge. This should be kept strictly to the
|
|
Challenges table and any child tables.
|
|
|
|
:param challenge:
|
|
:param request:
|
|
:return:
|
|
"""
|
|
data = request.form or request.get_json()
|
|
for attr, value in data.items():
|
|
setattr(challenge, attr, value)
|
|
|
|
db.session.commit()
|
|
return challenge
|
|
|
|
@staticmethod
|
|
def delete(challenge):
|
|
"""
|
|
This method is used to delete the resources used by a challenge.
|
|
|
|
:param challenge:
|
|
:return:
|
|
"""
|
|
Fails.query.filter_by(challenge_id=challenge.id).delete()
|
|
Solves.query.filter_by(challenge_id=challenge.id).delete()
|
|
Flags.query.filter_by(challenge_id=challenge.id).delete()
|
|
files = ChallengeFiles.query.filter_by(challenge_id=challenge.id).all()
|
|
for f in files:
|
|
delete_file(f.id)
|
|
ChallengeFiles.query.filter_by(challenge_id=challenge.id).delete()
|
|
Tags.query.filter_by(challenge_id=challenge.id).delete()
|
|
Hints.query.filter_by(challenge_id=challenge.id).delete()
|
|
Challenges.query.filter_by(id=challenge.id).delete()
|
|
db.session.commit()
|
|
|
|
@staticmethod
|
|
def attempt(challenge, request):
|
|
"""
|
|
This method is used to check whether a given input is right or wrong. It does not make any changes and should
|
|
return a boolean for correctness and a string to be shown to the user. It is also in charge of parsing the
|
|
user's input from the request itself.
|
|
|
|
:param challenge: The Challenge object from the database
|
|
:param request: The request the user submitted
|
|
:return: (boolean, string)
|
|
"""
|
|
data = request.form or request.get_json()
|
|
submission = data['submission'].strip()
|
|
flags = Flags.query.filter_by(challenge_id=challenge.id).all()
|
|
for flag in flags:
|
|
if get_flag_class(flag.type).compare(flag, submission):
|
|
return True, 'Correct'
|
|
return False, 'Incorrect'
|
|
|
|
@staticmethod
|
|
def solve(user, team, challenge, request):
|
|
"""
|
|
This method is used to insert Solves into the database in order to mark a challenge as solved.
|
|
|
|
:param team: The Team object from the database
|
|
:param chal: The Challenge object from the database
|
|
:param request: The request the user submitted
|
|
:return:
|
|
"""
|
|
data = request.form or request.get_json()
|
|
submission = data['submission'].strip()
|
|
solve = Solves(
|
|
user_id=user.id,
|
|
team_id=team.id if team else None,
|
|
challenge_id=challenge.id,
|
|
ip=get_ip(req=request),
|
|
provided=submission
|
|
)
|
|
db.session.add(solve)
|
|
db.session.commit()
|
|
db.session.close()
|
|
|
|
@staticmethod
|
|
def fail(user, team, challenge, request):
|
|
"""
|
|
This method is used to insert Fails into the database in order to mark an answer incorrect.
|
|
|
|
:param team: The Team object from the database
|
|
:param chal: The Challenge object from the database
|
|
:param request: The request the user submitted
|
|
:return:
|
|
"""
|
|
data = request.form or request.get_json()
|
|
submission = data['submission'].strip()
|
|
wrong = Fails(
|
|
user_id=user.id,
|
|
team_id=team.id if team else None,
|
|
challenge_id=challenge.id,
|
|
ip=get_ip(request),
|
|
provided=submission
|
|
)
|
|
db.session.add(wrong)
|
|
db.session.commit()
|
|
db.session.close()
|
|
|
|
|
|
def get_chal_class(class_id):
|
|
"""
|
|
Utility function used to get the corresponding class from a class ID.
|
|
|
|
:param class_id: String representing the class ID
|
|
:return: Challenge class
|
|
"""
|
|
cls = CHALLENGE_CLASSES.get(class_id)
|
|
if cls is None:
|
|
raise KeyError
|
|
return cls
|
|
|
|
|
|
"""
|
|
Global dictionary used to hold all the Challenge Type classes used by CTFd. Insert into this dictionary to register
|
|
your Challenge Type.
|
|
"""
|
|
CHALLENGE_CLASSES = {
|
|
"standard": CTFdStandardChallenge
|
|
}
|
|
|
|
|
|
def load(app):
|
|
register_plugin_assets_directory(app, base_path='/plugins/challenges/assets/')
|