r8 API Reference

class r8.Challenge(cid: str)

Challenge Display

title : str = "Hello World"

The challenge name visible to the user.

tags : list[str] = []

Tags for the challenge. This can be used to signal task category of difficulty.

flag : str = "__flag__{...}"

If set, a static flag with the given value will be created on startup.

points: Optional[int] = None

Number of (hardcoded) points awarded for this challenge. If unset, points are automatically adjusted by the number of solves.

await description(user: str, solved: bool) → str

Challenge description visible to the user. Supports full HTML. There is no additional security layer, XSS is entirely possible.

await visible(user: str) → bool

Determine if the challenge is visible for a given user. Defaults to True.

Challenge Lifecycle

active : bool

True if the challenge is currently active, False otherwise (read-only).

args : str

The raw string passed to the challenge as an argument between parentheses. For example, given a cid of “Challenge(foo bar)”, this would be “foo bar”.

await start() → None

Called when the challenge is started, can be used to start additional services for example.

Note that challenge instances are always started immediately when running r8, independent of when the challenge will be active. This makes sure that there are no surprising startup errors.

await stop() → None

Called when the challenge is stopped.

Note that challenge instances will not be stopped on the challenge deadline, only flag generation and submission will be halted. This allows in-class demonstrations after the deadline.

Logging and Flag Creation

echo(message: str, err: bool = False) → None

Print to console with the challenge’s namespace added in front.

log(ip: THasIP, type: str, data: Optional[str] = None, *, uid: Optional[str] = None) → None

Log an event for the current challenge. See r8.log().

log_and_create_flag(ip: THasIP, user: Optional[str] = None, *, max_submissions: int = 1, flag: Optional[str] = None, challenge: Optional[str] = None) → str

Create a new flag that can be redeemed for this challenge and log its creation.

If the challenge is currently inactive, __flag__{challenge inactive} will be returned instead.

If flag creation should not be logged (e.g. because it’s done by the challenge automatically on startup), use r8.util.create_flag() directly.

Parameters
  • ip – IP address which caused this flag to be created. Used for logging only.

  • user – User who caused this flag to be created. Used for logging only.

  • challenge – If given, override the challenge for which this flag is valid.

HTTP API

Challenges can expose an HTTP API. This is for example used to serve static files (such as challenge icons) that accompany the challenge.

static_dir = "<challenge file directory>/static"

Directory that includes static files for the challenge. Will be served from handle_get_request().

api_url(path: str, absolute: bool = False, user: Optional[str] = None) → str

Construct a URL pointing to this challenge’s API.

Parameters
  • path – The request path relative to the API endpoint.

  • absolute – If True, an absolute URL is constructed.

  • user – If given, an authentication token will be included in the URL, making it possible to access the resource without additional authentication.

await handle_get_request(user: str, request: aiohttp.web_request.Request) → Union[str, aiohttp.web_response.StreamResponse]

HTTP GET requests to /api/challenges/cid/* land here. Serves static resources from static_dir by default.

The request path can be accessed using request.match_info[“path”].

await handle_post_request(user: str, request: aiohttp.web_request.Request) → Union[str, aiohttp.web_response.StreamResponse]

HTTP POST requests to /api/challenges/cid/* land here. Serves 404s by default.

The request path can be accessed using request.match_info[“path”].

Key-Value Storage

Challenges can store additional data in a persistent key value JSON storage in the database.

get_data(key: str, *, cid: Optional[str] = None) → Any

Get persistent challenge data for a specific key.

Parameters

cid – If given, override the challenge for which data should be accessed.

set_data(key: str, value: Any, *, cid: Optional[str] = None)

Set persistent challenge data for a specific key.

Parameters

cid – If given, override the challenge for which data should be modified.

Utilities

r8.util.get_team(user: str) → Optional[str]

Get a given user’s team.

r8.util.has_solved(user: str, challenge: str) → bool

Check if a user has solved a challenge.

Challenge Description Helpers

r8.util.media(src: Optional[str], desc: str, visible: bool = True)

HTML boilerplate for a bootstrap media element. Commonly used to display challenge icons.

Parameters
  • src – Path to image.

  • desc – Media body.

  • visible – If False, a generic challenge icon will be shown instead.

r8.util.spoiler(help_text: str, button_text='🕵️ Show Hint') → str

HTML boilerplate for spoiler element in challenge descriptions.

r8.util.challenge_form_js(cid: str) → str

JS Boilerplate for simple interactive form submissions in the challenge description.

r8.util.challenge_invoke_button(cid: str, button_text: str) → str

“Trigger” button for challenges. Clicking it invokes the challenge’s HTTP POST handler.

r8.util.url_for(path: str, absolute: bool = False, user: Optional[str] = None) → str

Construct a URL for the CTF System. If absolute is true, construct an absolute URL including the origin. If user is passed, add an authentication token to the URL.

r8.util.get_host() → str

Return the hostname of the CTF system.

TCP Server Challenge Helpers

r8.util.connection_timeout(f)

Decorator to timeout an asyncio TCP connection handler after 60 seconds.

r8.util.tolerate_connection_error(f)

Decorator to silently catch all ConnectionErrors for asyncio TCP connections.

r8.util.format_address(address: tuple) → str

Format an (ip, port) address tuple.

Low-Level Helpers

See also

For challenge development, it is recommended to use the equivalent methods exposed by the challenge class instead: r8.Challenge.echo(), r8.Challenge.log() and r8.Challenge.log_and_create_flag().

r8.echo(namespace: str, message: str, err: bool = False) → None

Print to console with a namespace added in front.

Parameters
  • namespace – The message ‘category’, e.g. the challenge name.

  • message – The message.

  • err – If True, print to stderr.

For quick and dirty challenge development, it is completely okay to just print() instead.

r8.log(ip: THasIP, type: str, data: Optional[str] = None, *, cid: Optional[str] = None, uid: Optional[str] = None) → int

Create a log entry.

Parameters
  • ip – IP address which caused this log entry to be created.

  • type – Event type, for example “submission attempt”

  • data – Additional event data, for example the actually submitted value.

  • cid – Challenge this log entry relates to.

  • uid – User this log entry relates to.

r8.util.create_flag(challenge: str, max_submissions: int = 1, flag: str = None) → str

Create a new flag for an existing challenge. When creating flags from challenges, see also r8.Challenge.log_and_create_flag().

Parameters
  • challenge – Challenge for which the flag is valid.

  • max_submissions – Maximum number of times the flag can be redeemed.

  • flag – If given, use this as the flag string. Otherwise, generate random flag.

class r8.util.THasIP

An object from which we can derive an IP address,. e.g. a web.Request, an asyncio.StreamWriter, a str or an (ip, port) tuple.