feat: Add PersonalFlag flag with automatic cheat reporting

This commit is contained in:
2025-11-07 14:04:01 +01:00
parent 0609c6cb61
commit 3139c08c91
5 changed files with 91 additions and 3 deletions

View File

@@ -1,12 +1,14 @@
import os import os
from CTFd.models import db from CTFd.plugins import register_admin_plugin_menu_bar, register_plugin_assets_directory
from CTFd.plugins import register_admin_plugin_menu_bar
from CTFd.plugins.migrations import upgrade from CTFd.plugins.migrations import upgrade
from CTFd.plugins.flags import FLAG_CLASSES
from CTFd.utils.decorators import admins_only from CTFd.utils.decorators import admins_only
from CTFd.models import Flags, db
from flask import Blueprint, render_template, request from flask import Blueprint, render_template, request
from .models import CheaterTeams from .models import CheaterTeams
from .flags import PersonalFlag
PLUGIN_PATH = os.path.dirname(__file__) 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() 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"]) @bp.route("/admin/cheaters", methods=["GET"])
@admins_only @admins_only
def show_cheaters(): def show_cheaters():
@@ -33,6 +52,9 @@ def load(app):
app.db.create_all() app.db.create_all()
upgrade(plugin_name="cheaters") 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") register_admin_plugin_menu_bar(title="Cheaters", route="/admin/cheaters")
app.register_blueprint(bp) app.register_blueprint(bp)

11
assets/create.html Normal file
View File

@@ -0,0 +1,11 @@
<label>
Personal<br>
<small>Enter personal flag data</small>
</label>
<div class="form-group">
<input type="text" class="form-control" name="content" value="{{ content }}">
</div>
<div class="form-group">
<input type="text" class="form-control" name="user id" value="{{ data }}">
</div>
<input type="hidden" name="type" value="personal">

16
assets/edit.html Normal file
View File

@@ -0,0 +1,16 @@
<label>
Personal<br>
<small>Enter personal flag data</small>
</label>
<div class="form-group">
<input type="text" class="form-control" name="content" value="{{ content }}">
</div>
<div class="form-group">
<input type="text" class="form-control" name="user id" value="{{ data }}">
</div>
<input type="hidden" name="type" value="personal">
<input type="hidden" name="id" value="{{ id }}">
<hr>
<div class="form-group">
<button class="btn btn-success float-right">Update</button>
</div>

39
flags.py Normal file
View File

@@ -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

View File

@@ -28,7 +28,7 @@ class CheaterTeams(db.Model):
) )
def __init__( 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.challenge_id = challenge_id
self.cheater_id = cheater_id self.cheater_id = cheater_id