From db53a232190d0de8c8591243aa3e2ab2d16b4648 Mon Sep 17 00:00:00 2001 From: Marc Olivier Bergeron Date: Tue, 14 Oct 2025 22:35:40 -0400 Subject: [PATCH] Added CFSS support and validation. --- challenges/mock-track-apache-php/track.yaml | 1 + challenges/mock-track-files-only/track.yaml | 3 +- .../mock-track-python-service/track.yaml | 2 + ctf/schemas/track.yaml.json | 5 ++ ctf/templates/init/schemas/track.yaml.json | 7 +- ctf/templates/new/common/track.yaml.j2 | 2 + ctf/validators.py | 64 +++++++++++++++++-- pyproject.toml | 2 +- 8 files changed, 79 insertions(+), 7 deletions(-) diff --git a/challenges/mock-track-apache-php/track.yaml b/challenges/mock-track-apache-php/track.yaml index b174f5b..0996448 100644 --- a/challenges/mock-track-apache-php/track.yaml +++ b/challenges/mock-track-apache-php/track.yaml @@ -13,6 +13,7 @@ flags: value: 5 description: Free flag in source of index.php return_string: '[mock-track-apache-php] 1/1 Good job! Track completed.' + cfss: "CFSS:0.3/TS:B/E:M/HSFC:N=4-7" tags: discourse: mock_track_apache_php_flag_1 services: diff --git a/challenges/mock-track-files-only/track.yaml b/challenges/mock-track-files-only/track.yaml index 8e2c5f8..7440563 100644 --- a/challenges/mock-track-files-only/track.yaml +++ b/challenges/mock-track-files-only/track.yaml @@ -10,9 +10,10 @@ contacts: - user_support flags: - flag: FLAG-32c335e18b6c0eb6ddbd6366ade11069 - value: 5 + value: 7 description: Free flag in downloadable content return_string: '[mock-track-files-only] 1/1 Good job! Track completed.' + cfss: "CFSS:0.3/TS:L/E:H/HSFC:Y=7-11" tags: discourse: mock_track_files_only_flag_1 services: [] diff --git a/challenges/mock-track-python-service/track.yaml b/challenges/mock-track-python-service/track.yaml index 1328bca..b05dcb5 100644 --- a/challenges/mock-track-python-service/track.yaml +++ b/challenges/mock-track-python-service/track.yaml @@ -13,12 +13,14 @@ flags: value: 2 description: Free flag in source of web application return_string: '[mock-track-python-service] 1/2 Great!' + cfss: "CFSS:0.3/TS:L/E:L/HSFC:N=1-2" tags: discourse: mock_track_python_service_flag_1 - flag: FLAG-20f645f09e6989741a39759209aa047d value: 10 description: RCE!! return_string: '[mock-track-python-service] 2/2 Good job! Track completed.' + cfss: "CFSS:0.3/TS:I/E:M/HSFC:Y=7-12" tags: discourse: mock_track_python_service_flag_2 services: diff --git a/ctf/schemas/track.yaml.json b/ctf/schemas/track.yaml.json index 05913b5..16c0443 100644 --- a/ctf/schemas/track.yaml.json +++ b/ctf/schemas/track.yaml.json @@ -131,6 +131,11 @@ "description": "The text the participants see AFTER they submit the flag. Example: [mytrackname] 1/1 Good job! Track completed.", "minLength": 1 }, + "cfss": { + "type": "string", + "description": "The CFSS string based on https://github.com/res260/cfss", + "pattern": "^CFSS:[0-9]\\.[0-9][0-9]?/TS:[LBIA]/E:[LMH]/HSFC:[NY]=[0-9][0-9]?-[0-9][0-9]?$" + }, "tags": { "type": "object", "description": "Askgod tags for this flag. Use tag `discourse: sometriggername` to define triggers for posts in the posts/ directory.", diff --git a/ctf/templates/init/schemas/track.yaml.json b/ctf/templates/init/schemas/track.yaml.json index 65267b2..16c0443 100644 --- a/ctf/templates/init/schemas/track.yaml.json +++ b/ctf/templates/init/schemas/track.yaml.json @@ -131,6 +131,11 @@ "description": "The text the participants see AFTER they submit the flag. Example: [mytrackname] 1/1 Good job! Track completed.", "minLength": 1 }, + "cfss": { + "type": "string", + "description": "The CFSS string based on https://github.com/res260/cfss", + "pattern": "^CFSS:[0-9]\\.[0-9][0-9]?/TS:[LBIA]/E:[LMH]/HSFC:[NY]=[0-9][0-9]?-[0-9][0-9]?$" + }, "tags": { "type": "object", "description": "Askgod tags for this flag. Use tag `discourse: sometriggername` to define triggers for posts in the posts/ directory.", @@ -165,4 +170,4 @@ "services", "flags" ] - } \ No newline at end of file + } diff --git a/ctf/templates/new/common/track.yaml.j2 b/ctf/templates/new/common/track.yaml.j2 index ca521d1..9aa9916 100644 --- a/ctf/templates/new/common/track.yaml.j2 +++ b/ctf/templates/new/common/track.yaml.j2 @@ -26,6 +26,8 @@ flags: description: Free flag in source of index.php CHANGE_ME # The text the participants see AFTER they submit the flag. return_string: '[{{ data.name }}] 1/1 Good job! Track completed. CHANGE_ME' + # CFSS string based on https://github.com/res260/cfss + cfss: "CFSS:0.3/TS:B/E:M/HSFC:N=4-7" tags: # Name of the discourse trigger for this flag. If a discourse post in the posts/ directory has this trigger, it will be posted when this flag is submitted. # This value can also be used to reference flags in Ansible playbooks. See the "Load Flags" task in deploy.yaml. diff --git a/ctf/validators.py b/ctf/validators.py index 4a696e6..faf3795 100644 --- a/ctf/validators.py +++ b/ctf/validators.py @@ -449,13 +449,69 @@ def validate(self, track_name: str) -> list[ValidationError]: return errors +class CFSSStringValidator(Validator): + """Validate the CFSS string of each flag in the track.yaml.""" + + CFSS_VALUE_REGEX = re.compile( + r"^CFSS:[0-9]\.[0-9][0-9]?/TS:[LBIA]/E:[LMH]/HSFC:[NY]=([0-9][0-9]?-[0-9][0-9]?)$" + ) + + def validate(self, track_name: str) -> list[ValidationError]: + errors: list[ValidationError] = [] + + track_yaml = parse_track_yaml(track_name=track_name) + for flag in track_yaml["flags"]: + if "cfss" not in flag: + errors.append( + ValidationError( + error_name="CFSS string not found", + error_description="CFSS string was not present in the track.yaml.", + track_name=track_name, + details={ + "CFSS string": "Not found", + "Flag value": str(flag.get("value")), + }, + ) + ) + continue + + cfss: str = flag.get("cfss") + value: int = flag.get("value") + + # Should never happen since schemas/track.yaml.json is validated first. + if not (m := self.CFSS_VALUE_REGEX.match(cfss)): + errors.append( + ValidationError( + error_name="CFSS string did not match REGEX", + error_description='CFSS string did not match "^CFSS:[0-9]\\.[0-9][0-9]?/TS:[LBIA]/E:[LMH]/HSFC:[NY]=([0-9][0-9]?-[0-9][0-9]?)$".', + track_name=track_name, + details={"CFSS string": cfss, "Flag value": str(value)}, + ) + ) + continue + + low, high = m.group(1).split("-") + if value < int(low) or value > int(high): + errors.append( + ValidationError( + error_name="Flag value not in CFSS range", + error_description="Flag value did not correspond to CFSS string's value.", + track_name=track_name, + details={"CFSS string": cfss, "Flag value": str(value)}, + ) + ) + continue + return errors + + validators_list = [ + CFSSStringValidator, + DiscourseFileNamesValidator, + DiscoursePostsAskGodTagValidator, FilesValidator, - FlagsValidator, FireworksAskGodTagValidator, - DiscoursePostsAskGodTagValidator, + FlagsValidator, + OrphanServicesValidator, PlaceholderValuesValidator, - DiscourseFileNamesValidator, ServicesValidator, - OrphanServicesValidator, ] diff --git a/pyproject.toml b/pyproject.toml index 27adece..8f3a85d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "typer==0.16.0", "pydantic" ] -version = "4.0.0" +version = "4.1.0" classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent",