From 9b94ac94de706620b5f998c556f7e56593220c08 Mon Sep 17 00:00:00 2001 From: Max Helbig Date: Wed, 12 Aug 2026 12:21:58 +0000 Subject: [PATCH 1/5] changes from old version --- Dockerfile | 10 +- docker-compose.yml | 5 +- .../AdminHandlers/AdminGameObjectHandlers.py | 25 +++ handlers/MissionsHandler.py | 16 +- locale/de.csv | 6 + models/Flag.py | 101 ++++++++++- rootthebox.py | 41 +++++ static/js/pages/missions/box.js | 12 ++ templates/admin/create/flag-remote.html | 126 ++++++++++++++ templates/admin/create/flag-remotestring.html | 157 ++++++++++++++++++ templates/admin/create/flag.html | 8 + templates/missions/box.html | 26 ++- 12 files changed, 525 insertions(+), 8 deletions(-) create mode 100644 templates/admin/create/flag-remote.html create mode 100644 templates/admin/create/flag-remotestring.html diff --git a/Dockerfile b/Dockerfile index d329911dd..068e0abd3 100755 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,9 @@ FROM python:3.8 RUN mkdir /opt/rtb -ADD . /opt/rtb +######################### +# temporary uncomment for dev +# ADD . /opt/rtb RUN apt-get update && apt-get install -y \ build-essential zlib1g-dev rustc \ @@ -17,5 +19,9 @@ RUN pip install --no-cache-dir -r requirements.txt --upgrade ENV SQL_DIALECT=sqlite -VOLUME ["/opt/rtb/files"] + +######################### +# temporary cahnge for dev +# VOLUME ["/opt/rtb/files"] -> VOLUME ["/opt/rtb"] +VOLUME ["/opt/rtb"] ENTRYPOINT ["python3", "/opt/rtb/rootthebox.py", "--setup=docker"] diff --git a/docker-compose.yml b/docker-compose.yml index 63680810d..453714c28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,9 @@ services: ports: - "8888:8888" volumes: - - ./files:/opt/rtb/files:rw + ######################### + # temporary cahnge for dev + # - ./files:/opt/rtb/files:rw -> - ./:/opt/rtb:rw + - ./:/opt/rtb:rw environment: - COMPOSE_CONVERT_WINDOWS_PATHS=1 diff --git a/handlers/AdminHandlers/AdminGameObjectHandlers.py b/handlers/AdminHandlers/AdminGameObjectHandlers.py index d72fcb60d..82fa25cea 100644 --- a/handlers/AdminHandlers/AdminGameObjectHandlers.py +++ b/handlers/AdminHandlers/AdminGameObjectHandlers.py @@ -48,6 +48,8 @@ FLAG_FILE, FLAG_REGEX, FLAG_STATIC, + FLAG_REMOTE, + FLAG_REMOTESTRING, Flag, ) from models.FlagAttachment import FlagAttachment @@ -80,6 +82,8 @@ def get(self, *args, **kwargs): "flag/static": "admin/create/flag-static.html", "flag/datetime": "admin/create/flag-datetime.html", "flag/choice": "admin/create/flag-choice.html", + "flag/remote": "admin/create/flag-remote.html", + "flag/remotestring": "admin/create/flag-remotestring.html", "game_level": "admin/create/game_level.html", "hint": "admin/create/hint.html", "team": "admin/create/team.html", @@ -107,6 +111,8 @@ def post(self, *args, **kwargs): "flag/static": self.create_flag_static, "flag/datetime": self.create_flag_datetime, "flag/choice": self.create_flag_choice, + "flag/remote": self.create_flag_remote, + "flag/remotestring": self.create_flag_remotestring, "game_level": self.create_game_level, "hint": self.create_hint, "team": self.create_team, @@ -270,6 +276,25 @@ def create_flag_datetime(self): "admin/create/flag-datetime.html", errors=[str(error)], box=None ) + def create_flag_remote(self): + """Create a regex flag""" + try: + self._mkflag(FLAG_REMOTE) + except ValidationError as error: + self.render( + "admin/create/flag-remote.html", errors=[str(error)], box=None + ) + + def create_flag_remotestring(self): + """Create a regex flag""" + try: + self._mkflag(FLAG_REMOTESTRING) + except ValidationError as error: + self.render( + "admin/create/flag-remote.html", errors=[str(error)], box=None + ) + + def create_game_level(self): """ Creates a new level in the database, the levels are basically a diff --git a/handlers/MissionsHandler.py b/handlers/MissionsHandler.py index ed051735a..d8a3a6b49 100644 --- a/handlers/MissionsHandler.py +++ b/handlers/MissionsHandler.py @@ -26,6 +26,9 @@ import json import logging + +from datetime import datetime + from builtins import next, str from past.utils import old_div @@ -189,6 +192,9 @@ def post(self, *args, **kwargs): if flag is not None and flag.is_file: if hasattr(self.request, "files") and "flag" in self.request.files: submission = self.request.files["flag"][0]["body"] + elif flag is not None and flag.is_remote: + now = datetime.now() + submission = "Remote Submission at: " + now.strftime("%Y-%m-%d %H:%M:%S") else: submission = self.get_argument("token", "").replace("__quote__", '"') if len(submission) == 0: @@ -224,6 +230,10 @@ def post(self, *args, **kwargs): self.render("public/404.html") def failed_attempt(self, flag, user, submission, box_id): + if flag is not None and (flag.is_remote or flag.is_remotestring) and flag.status == "error": + msg = f"Error: {flag.message} please inform the trainer" + self.render_page_by_flag(flag, info=[msg]) + return if flag is None or Penalty.by_token_count(flag, user.team, submission) == 0: if options.teams: teamval = "team's " @@ -252,6 +262,8 @@ def failed_attempt(self, flag, user, submission, box_id): + teamval + "score." ) + if flag is not None and flag.is_remote or flag.is_remotestring: + penalty_dialog = f"{penalty_dialog} Message: {flag.message}" if flag is None: self.render_page_by_box_id(box_id, errors=[penalty_dialog]) else: @@ -278,6 +290,8 @@ def success_capture(self, user, flag, old_reward=None): teamval = "" old_reward = flag.dynamic_value(user.team) if old_reward is None else old_reward reward_dialog = flag.name + " answered correctly. " + if flag is not None and flag.is_remote or flag.is_remotestring: + reward_dialog = f"{reward_dialog} Message: {flag.message}" if options.banking: reward_added_str_template = ( "${} has been added to your " + teamval + "account." @@ -413,7 +427,7 @@ def attempt_capture(self, flag, submission): "%s (%s) capture the flag '%s'" % (user.handle, team.name, flag.name) ) if submission is not None and flag not in team.flags: - if flag.capture(submission): + if flag.capture(submission, player_ip=self.request.remote_ip): flag_value = flag.dynamic_value(team) if ( options.dynamic_flag_value diff --git a/locale/de.csv b/locale/de.csv index 07db2b03f..b8631632c 100644 --- a/locale/de.csv +++ b/locale/de.csv @@ -163,6 +163,8 @@ Create New Paste,Neuen Paste erstellen Create Notification,Benachrichtigung erstellen Create Regex Flag,Regex-Flagge erstellen Create Static Flag,Statische Flagge erstellen +Create Remote Flag,Remote-Flagge erstellen +Create Remote String Flag,Remote-String-Flagge erstellen Create Team,Team erstellen Created,Erstellt Current algorithm,Aktueller Algorithmus @@ -258,6 +260,7 @@ Enter a Matching Flag,Geben Sie eine passende Flagge ein Enter Choice,Auswahl eingeben Enter the same password as before,Geben Sie dasselbe Passwort wie zuvor ein ERROR,ERROR +Error, Fehler Expire,Verlopen Export Game Objects,Spielobjekte exportieren Export of gameplay settings does not include the entire server and database configuration.,Der Export der Gameplay-Einstellungen umfasst nicht die gesamte Server- und Datenbankkonfiguration. @@ -282,6 +285,7 @@ Flag Decrease Value,Flaggenwert verringern Flag Details,Flaggendetails Flag File,Flaggendatei Flag Hints,Flaggenhinweise +Flag ID, Flaggen ID Flag Minimum Value,Vlag Minimumwaarde Flag Name,Flaggenname Flag Penalty,Flaggenstrafe @@ -338,6 +342,7 @@ In Progress,In Bearbeitung "In this case, the flag is a Date / Time string. This flag type will try to match on common datetime format variations and deal with differences like slashes, dashes, padding, 12hr, 24hr, etc. It can handle a date, time, or a datetime.","In diesem Fall ist die Flagge eine Datums- / Uhrzeitzeichenfolge. Dieser Flaggentyp versucht, mit gängigen Datums- / Uhrzeit-Formatvariationen übereinzustimmen und behandelt Unterschiede wie Schrägstriche, Bindestriche, Auffüllungen, 12 Stunden, 24 Stunden usw. Er kann ein Datum, eine Uhrzeit oder eine Datums- / Uhrzeitangabe verarbeiten." "In this case, the flag is a multiple choice question where the selected option is the token.","In diesem Fall ist die Flagge eine Multiple-Choice-Frage, bei der die ausgewählte Option das Token ist." "In this case, the flag is a regular expression. The user must submit the a string which matches the pattern to capture the flag. Matches can be insensitive. Please be sure to test your regex, and ensure its not too broad.","In diesem Fall ist die Flagge ein regulärer Ausdruck. Der Benutzer muss eine Zeichenfolge übermitteln, die mit dem Muster übereinstimmt, um die Flagge zu erobern. Übereinstimmungen können unempfindlich sein. Bitte stellen Sie sicher, dass Sie Ihren regulären Ausdruck testen, und stellen Sie sicher, dass er nicht zu breit angelegt ist." +"In this case, the flag is a remote Flag, the ID is submited to the flag-check-server.","In diesem Fall ist die Flagge eine Remote-Flagge, die ID wird an den Flaggen-Test-Server übermittelt" "In this case, the flag is a static string. The user must submit the exact token to capture the flag. Whitespace at the beginning and end are stripped.","In diesem Fall ist die Flagge eine statische Zeichenfolge. Der Benutzer muss das genaue Token senden, um die Flagge zu erobern. Leerzeichen am Anfang und Ende werden entfernt." Include gameplay settings (such as those under ,Das Einbeziehen von Gameplay-Einstellungen (z. B. beim Exportieren von Gameplay-Einstellungen) umfasst nicht die gesamte Server- und Datenbankkonfiguration. Income,Einkommen @@ -483,6 +488,7 @@ Player Arrested!,Spieler verhaftet! Player Email,Spieler E-Mail Player Name,Spielername "Player's bank account passwords are also available, allowing you to crack each other's passwords and steal the money.","Passwörter für das Bankkonto des Spielers sind ebenfalls verfügbar, sodass Sie die Passwörter des anderen knacken und das Geld stehlen können." +please inform your trainer ,Bitte informieren sie den Referenten. Players,Spieler Please do NOT enter your real bank account password.,Bitte geben Sie NICHT Ihr echtes Bankkonto-Passwort ein. points,Punkte diff --git a/models/Flag.py b/models/Flag.py index f03a6df51..82733136c 100644 --- a/models/Flag.py +++ b/models/Flag.py @@ -44,13 +44,17 @@ from models.Relationships import team_to_flag, user_to_flag from models.Team import Team +import requests + ### Constants FLAG_STATIC = "static" FLAG_REGEX = "regex" FLAG_FILE = "file" FLAG_DATETIME = "datetime" FLAG_CHOICE = "choice" -FLAG_TYPES = [FLAG_STATIC, FLAG_REGEX, FLAG_FILE, FLAG_DATETIME, FLAG_CHOICE] +FLAG_REMOTE = "remote" +FLAG_REMOTESTRING = "remotestring" +FLAG_TYPES = [FLAG_STATIC, FLAG_REGEX, FLAG_FILE, FLAG_DATETIME, FLAG_CHOICE, FLAG_REMOTE, FLAG_REMOTESTRING] class Flag(DatabaseObject): @@ -63,6 +67,8 @@ class Flag(DatabaseObject): -datetime -file -choice + -remote + -remotestring Depending on the cls._type value. For more information see the wiki. """ @@ -83,6 +89,9 @@ class Flag(DatabaseObject): _type = Column(Unicode(16), default=False) _locked = Column(Boolean, default=False, nullable=False) + status = "" + message ="Default Message" + flag_attachments = relationship( "FlagAttachment", backref=backref("flag", lazy="select"), @@ -107,7 +116,7 @@ class Flag(DatabaseObject): cascade="all,delete,delete-orphan", ) - FLAG_TYPES = [FLAG_FILE, FLAG_REGEX, FLAG_STATIC, FLAG_DATETIME, FLAG_CHOICE] + FLAG_TYPES = [FLAG_FILE, FLAG_REGEX, FLAG_STATIC, FLAG_DATETIME, FLAG_CHOICE, FLAG_REMOTE, FLAG_REMOTESTRING] @classmethod def all(cls): @@ -165,6 +174,8 @@ def create_flag(cls, _type, box, name, raw_token, description, value): FLAG_FILE: cls._create_flag_file, FLAG_DATETIME: cls._create_flag_datetime, FLAG_CHOICE: cls._create_flag_choice, + FLAG_REMOTE: cls._create_flag_remote, + FLAG_REMOTESTRING: cls._create_flag_remotestring } # TODO Don't understand why this is here - name is not unique value # and you could simply name questions per box, like "Question 1" - ElJefe 6/1/2018 @@ -235,6 +246,29 @@ def _create_flag_choice(cls, box, name, raw_token, description, value): value=value, ) + @classmethod + def _create_flag_remote(cls, box, name, raw_token, description, value): + """Check flag remote specific parameters""" + return cls( + box_id=box.id, + name=name, + token=raw_token, + description=description, + value=value, + ) + + @classmethod + def _create_flag_remotestring(cls, box, name, raw_token, description, value): + """Check flag remotestring specific parameters""" + return cls( + box_id=box.id, + name=name, + token=raw_token, + description=description, + value=value, + ) + + @classmethod def digest(self, data): """Token is SHA1 of data""" @@ -382,6 +416,14 @@ def is_static(self): def is_file(self): return self._type == FLAG_FILE + @property + def is_remote(self): + return self._type == FLAG_REMOTE + + @property + def is_remotestring(self): + return self._type == FLAG_REMOTESTRING + @property def box(self): return Box.by_id(self.box_id) @@ -425,7 +467,7 @@ def choicelist(self): choices.append(flagchoice.choice) return json.dumps(choices) - def capture(self, submission): + def capture(self, submission, **kwargs): if self._type == FLAG_STATIC: if self._case_sensitive == 0: return ( @@ -450,9 +492,62 @@ def capture(self, submission): return parse(self.token) == parse(submission) except: return False + elif self._type == FLAG_REMOTE or self._type == FLAG_REMOTESTRING: + return self.capture_remote_flag(submission, **kwargs) else: raise ValueError("Invalid flag type, cannot capture") + def capture_remote_flag(self, submission, **kwargs): + if self._type != FLAG_REMOTE and self._type != FLAG_REMOTESTRING: + self.status = "error" + self.message = "Wrong flagtype for remoteflag" + return False + if "player_ip" not in kwargs: + self.status = "error" + self.message = "No Player IP for Remote Flag" + return False + + data = {"player_ip": kwargs.get("player_ip"), "flag_token": self.token} + if self._type == FLAG_REMOTESTRING: + data["submission"] = submission + url = f"{options.remote_protocol}://{options.remote_domain}:{options.remote_port}{options.remote_path}" + try: + reply = requests.post(url=url, data=data, timeout=options.remote_timeout) + except requests.Timeout: + self.status = "error" + self.message = "Request to Flagserver timed out" + return False + except Exception as e: + self.status = "error" + self.message = "Exception during POST to Remote server" + return False + + if reply.status_code != 200: + self.status = "error" + self.message = "Request to Flagserver returned status " + str(reply.status_code) + return False + + try: + retval = json.loads(reply.text) + except json.JSONDecodeError as e: + self.status = "error" + self.message = "Reply from FlagCheckServer is not a valid Json" + return False + + if "status" not in retval: + self.status = "error" + self.message = "Reply from FlagCheckServer does not contain a Status" + return False + + self.status = retval["status"] + if "message" in retval: + self.message = retval["message"] + else: + self.message = "No message from FlagServer" + + return self.status == "success" + + def to_xml(self, parent): """Write attributes to XML doc""" flag_elem = ET.SubElement(parent, "flag") diff --git a/rootthebox.py b/rootthebox.py index 36fbdeb21..2cdba01e1 100644 --- a/rootthebox.py +++ b/rootthebox.py @@ -1143,6 +1143,47 @@ def help(): define("tests", default=False, help="runs the unit tests", type=bool) +# remote Flag server +define( + "remote_protocol", + default="http", + group="remote", + help="protocol to access the remote flag server" +) + +define( + "remote_domain", + default="127.0.0.1", + group="remote", + help="domain or IP to access the remote flag server" +) + +define( + "remote_path", + default="/UserFlagCheck", + group="remote", + help="path to check remote flags on the remote flag server" +) + +define( + "remote_port", + default= 8080, + group="remote", + help="port to access the remote flag server", + type=int +) + +define( + "remote_timeout", + default= 60, + group="remote", + help="time to wait for reply from the remote flag server in seconds", + type=int +) + + + + if __name__ == "__main__": # We need this to pull the --config option diff --git a/static/js/pages/missions/box.js b/static/js/pages/missions/box.js index 66a6d9910..9afa664de 100644 --- a/static/js/pages/missions/box.js +++ b/static/js/pages/missions/box.js @@ -34,6 +34,18 @@ $(document).ready(function() { $("#capture-text-flag-form").submit(); }); + $("#capture-remote-flag-modal").on('shown.bs.modal', function () { + $("#flag-token").focus() + }); + + $("a[id^=capture-remote-flag-button]").click(function() { + $("#capture-remote-flag-uuid").val($(this).data("uuid")); + }); + + $("#capture-remote-flag-submit").click(function() { + $("#capture-remote-flag-form").submit(); + }); + $("a[id^=capture-choice-flag-button]").click(function() { $("#capture-choice-flag-uuid").val($(this).data("uuid")); $("#choiceinput").empty(); diff --git a/templates/admin/create/flag-remote.html b/templates/admin/create/flag-remote.html new file mode 100644 index 000000000..539668f66 --- /dev/null +++ b/templates/admin/create/flag-remote.html @@ -0,0 +1,126 @@ +{% extends "../../main.html" %} + +{% block title %}{{_("Create Flag")}}{% end %} + +{% block header %} + + + + +{% end %} + +{% block content %} +{% from models.Box import Box %} +{% from tornado.options import options %} +
+

+ + {{_("Create Remote Flag")}} +

+
+ {% if errors is not None and len(errors) != 0 %} + {% for error in errors %} +
+ × +

{{_("ERROR")}}

+ {{ error }} +
+ {% end %} + {% end %} +
+
+
+
+ {% raw xsrf_form_html() %} + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+ {% if options.story_mode %} +
+ +
+ +
+
+ {% end %} +
+
+ +
+
+
+
+
+

+

+ + {{_("Tokens")}}: + {{_("In this case, the flag is a remote Flag, the ID is submited to the flag-check-server")}} + +
+

+
+
+
+
+{% end %} diff --git a/templates/admin/create/flag-remotestring.html b/templates/admin/create/flag-remotestring.html new file mode 100644 index 000000000..4ac3b4eac --- /dev/null +++ b/templates/admin/create/flag-remotestring.html @@ -0,0 +1,157 @@ +{% extends "../../main.html" %} + +{% block title %}{{_("Create Flag")}}{% end %} + +{% block header %} + + + + +{% end %} + +{% block content %} +{% from models.Box import Box %} +{% from tornado.options import options %} +
+

+ + {{_("Create Remote String Flag")}} +

+
+ {% if errors is not None and len(errors) != 0 %} + {% for error in errors %} +
+ × +

{{_("ERROR")}}

+ {{ error }} +
+ {% end %} + {% end %} +
+
+
+
+ {% raw xsrf_form_html() %} + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + +
+
+
+
+ +
+ +
+
+
+ +
+ + + + + +
+
+
+ +
+ +
+
+ {% if options.story_mode %} +
+ +
+ +
+
+ {% end %} +
+
+ +
+
+
+
+
+

+

+ + {{_("Tokens")}}: + {{_("In this case, the flag is a static string. The user must submit the exact token to capture the flag. Whitespace at the beginning and end are stripped.")}} + +
+

+
+
+
+
+{% end %} diff --git a/templates/admin/create/flag.html b/templates/admin/create/flag.html index efda358e7..d549c0f6f 100644 --- a/templates/admin/create/flag.html +++ b/templates/admin/create/flag.html @@ -30,6 +30,14 @@

{{_("Create Multiple Choice Flag")}} » + + + {{_("Create Remote Flag")}} » + + + + {{_("Create Remote String Flag")}} » +
{% end %} diff --git a/templates/missions/box.html b/templates/missions/box.html index e1e406fc4..bd08c00e1 100644 --- a/templates/missions/box.html +++ b/templates/missions/box.html @@ -17,6 +17,8 @@ {% from models.Flag import Flag %} {% from models.Flag import FLAG_FILE %} {% from models.Flag import FLAG_CHOICE %} +{% from models.Flag import FLAG_REMOTE %} +{% from models.Flag import FLAG_REMOTESTRING %} {% from models.FlagChoice import FlagChoice %} {% from models.Box import FlagsSubmissionType %} {% from handlers.MaterialsHandler import has_materials,has_box_materials %} @@ -96,6 +98,28 @@

{{ _("Submit Flag") }}

+ + + +