From 2eefb47803acb20876edf8c065d6477687d7933a Mon Sep 17 00:00:00 2001 From: Dustin Martin Date: Thu, 25 Jan 2024 23:17:45 -0500 Subject: [PATCH] Allow {{url_for}} and {{lookup}} templates in Description The values of these templates can be determined as soon as a build of a challenge has been created, and differ from connection-related templates like {{port}} which require a running instance to have meaningful values. The advantage of allowing these tags in the Description section is that for on-demand challenges containing both artifacts and a server component (for example, binary exploitation challenges that provide both a source file and a remote server containing the flag), players no longer need to start an instances just to gain access to the artifact downloads and begin working on the challenge. This way, they do not need to start an instance of the challenge until they have developed an exploit locally and are ready to test it against the remote server. This is both more convenient for players, who historically have been confused as to why they need to start an instance just to download artifacts, and also reduces load on the Docker server. This change does mean that the Description field loses the initially intended property of being truly static across all challenge instances. In theory, it might be cleaner to instead separate descriptions into three sections: static, build-level templatable, and instance-level templatable. However, this is a much less intrusive change. Note that technically {{server}} could also be resolved by clients while only having build-level information available. However, I can't think of any situation where it is useful on its own without {{port}}, and it would be confusing to split the permitted connection-related templates between the two sections. --- cmgr/loader.go | 40 +++++++++++++++++++++++++++++++-- examples/markdown_challenges.md | 19 ++++++++-------- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/cmgr/loader.go b/cmgr/loader.go index f16e34e..91864b8 100644 --- a/cmgr/loader.go +++ b/cmgr/loader.go @@ -141,9 +141,45 @@ func (m *Manager) validateMetadata(md *ChallengeMetadata) error { } // Validate Description - templates := templateRe.FindAllString(md.Description, -1) + // picoCTF fork customization: allow template strings not involving connection information in the description section + templates := httpBaseRe.FindAllString(md.Description, -1) if len(templates) > 0 { - lastErr = fmt.Errorf("template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + lastErr = fmt.Errorf("'http_base' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + m.log.error(lastErr) + } + templates = shortHttpBaseRe.FindAllString(md.Description, -1) + if len(templates) > 0 { + lastErr = fmt.Errorf("'http_base' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + m.log.error(lastErr) + } + templates = portRe.FindAllString(md.Description, -1) + if len(templates) > 0 { + lastErr = fmt.Errorf("'port' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + m.log.error(lastErr) + } + templates = shortPortRe.FindAllString(md.Description, -1) + if len(templates) > 0 { + lastErr = fmt.Errorf("'port' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + m.log.error(lastErr) + } + templates = serverRe.FindAllString(md.Description, -1) + if len(templates) > 0 { + lastErr = fmt.Errorf("'server' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + m.log.error(lastErr) + } + templates = shortServerRe.FindAllString(md.Description, -1) + if len(templates) > 0 { + lastErr = fmt.Errorf("'server' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + m.log.error(lastErr) + } + templates = linkRe.FindAllString(md.Description, -1) + if len(templates) > 0 { + lastErr = fmt.Errorf("'link' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) + m.log.error(lastErr) + } + templates = shortLinkRe.FindAllString(md.Description, -1) + if len(templates) > 0 { + lastErr = fmt.Errorf("'link' template strings not allowed in the 'description' field, but found: %s", strings.Join(templates, ", ")) m.log.error(lastErr) } diff --git a/examples/markdown_challenges.md b/examples/markdown_challenges.md index ae0f802..d17842f 100644 --- a/examples/markdown_challenges.md +++ b/examples/markdown_challenges.md @@ -9,22 +9,23 @@ ## Description -This is a static description of the challenge that is intended to be shown to -the user and will be the same across all instances of the challenge. +This portion of the challenge description is displayed to users regardless of whether an instance of the challenge is currently running. It may include static text, as well as the following templates: + +- `{{url_for("file", "display text")}}` (link to an artifact file published in a build) +- `{{lookup("key")}}` ("key" must have been published in `metadata.json` when creating a build) ## Details -This is templated information for the challenge that can use additional -build-specific information to present information. In particular, the following -templates are allowed (anything else is invalid): -- `{{url_for("file", "display text")}}` +This portion of the challenge description is displayed to users when an instance of a challenge is +running. It may include any content permitted in the "Description" section, as well as the following +instance-specific templates: + - `{{http_base("port_name")}}` (URL prefix for HTTP requests to the named port) +- `{{server("port_name")}}` (hostname which hosts for connecting to the +associated port for the challenge) - `{{port("port_name")}}` (The specific port number competitors will see which may not be the same number as exposed by Docker if the front-end is proxying connections.) -- `{{server("port_name")}}` (hostname which hosts for connecting to the -associated port for the challenge) -- `{{lookup("key")}}` ("key" must have been published in `metadata.json` when creating a build) - `{{link("port_name", "/url/in/challenge")}}` (convenience wrapper for generating an HTML link) - `{{link_as("port_name", "/url/in/challenge", "display text")}}` (convenience wrapper for generating an HTML link with text different from the URL)