From 3139c08c919b259c46de11e895580ece5daa59e4 Mon Sep 17 00:00:00 2001 From: Matte23 Date: Fri, 7 Nov 2025 14:04:01 +0100 Subject: [PATCH] feat: Add PersonalFlag flag with automatic cheat reporting --- __init__.py | 26 ++++++++++++++++++++++++-- assets/create.html | 11 +++++++++++ assets/edit.html | 16 ++++++++++++++++ flags.py | 39 +++++++++++++++++++++++++++++++++++++++ models.py | 2 +- 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 assets/create.html create mode 100644 assets/edit.html create mode 100644 flags.py diff --git a/__init__.py b/__init__.py index 04073b4..448ea60 100644 --- a/__init__.py +++ b/__init__.py @@ -1,12 +1,14 @@ import os -from CTFd.models import db -from CTFd.plugins import register_admin_plugin_menu_bar +from CTFd.plugins import register_admin_plugin_menu_bar, register_plugin_assets_directory from CTFd.plugins.migrations import upgrade +from CTFd.plugins.flags import FLAG_CLASSES from CTFd.utils.decorators import admins_only +from CTFd.models import Flags, db from flask import Blueprint, render_template, request from .models import CheaterTeams +from .flags import PersonalFlag PLUGIN_PATH = os.path.dirname(__file__) @@ -21,6 +23,23 @@ def report_cheater(challenge_id: int, cheater_id: int, helper_id: int, flag_id: db.session.commit() +def create_flag_if_missing(challenge_id: int, user_id: int, flag_content: str): + flags = Flags.query.filter_by(id=challenge_id, data=user_id).all() + + if len(flags) == 0: + new_flag = Flags( + challenge_id=challenge_id, + type="personal", + content=flag_content, + data=user_id, + ) + db.session.add(new_flag) + db.session.commit() + return new_flag.id + + return flags[0].id + + @bp.route("/admin/cheaters", methods=["GET"]) @admins_only def show_cheaters(): @@ -33,6 +52,9 @@ def load(app): app.db.create_all() upgrade(plugin_name="cheaters") + FLAG_CLASSES["personal"] = PersonalFlag + + register_plugin_assets_directory(app, base_path="/plugins/ctfd_cheaters/assets/") register_admin_plugin_menu_bar(title="Cheaters", route="/admin/cheaters") app.register_blueprint(bp) diff --git a/assets/create.html b/assets/create.html new file mode 100644 index 0000000..248b8fe --- /dev/null +++ b/assets/create.html @@ -0,0 +1,11 @@ + +
+ +
+
+ +
+ diff --git a/assets/edit.html b/assets/edit.html new file mode 100644 index 0000000..1cb3f5b --- /dev/null +++ b/assets/edit.html @@ -0,0 +1,16 @@ + +
+ +
+
+ +
+ + +
+
+ +
diff --git a/flags.py b/flags.py new file mode 100644 index 0000000..2da7b94 --- /dev/null +++ b/flags.py @@ -0,0 +1,39 @@ +from CTFd.plugins.flags import BaseFlag +from CTFd.utils.user import get_current_user + +from . import report_cheater + + +class PersonalFlag(BaseFlag): + name: str = "personal" + templates = { # Nunjucks templates used for key editing & viewing + "create": "/plugins/ctfd_cheaters/assets/create.html", + "update": "/plugins/ctfd_cheaters/assets/edit.html", + } + + @staticmethod + def compare(chal_key_obj, provided): + saved = chal_key_obj.content + user_id = chal_key_obj.data + + if len(saved) != len(provided): + return False + result = 0 + + for x, y in zip(saved, provided): + result |= ord(x) ^ ord(y) + + if result == 0: + # If the flag is correct, we need to check if the team is the one associated with the flag + curr_user_id = get_current_user().id + + if int(user_id) == int(curr_user_id): + # User is correct + return True + + # Caught a cheater! + report_cheater( + chal_key_obj.challenge_id, curr_user_id, user_id, chal_key_obj.id + ) + + return False diff --git a/models.py b/models.py index 4b60bdc..f7115cf 100644 --- a/models.py +++ b/models.py @@ -28,7 +28,7 @@ class CheaterTeams(db.Model): ) def __init__( - self, challenge_id: int, cheater_id: int, helper_id: int, flag_id: str + self, challenge_id: int, cheater_id: int, helper_id: int, flag_id: int ): self.challenge_id = challenge_id self.cheater_id = cheater_id