Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!
Shared, open-source systems:
Custom, one-off or otherwise difficult customers:
- angstrom
- hxp
- Midnightsun
- zer0pts
- ctf4hire (via ctfd backend)
Support for these one-off systems is based on reverse-engineering of non-public APIs or page scraping. As a result, they may stop working or require some tweaking for future CTFs by the same organizers.
(Click for an animated preview!)
usage: ctfront.py [-h] [--frontend [FRONTEND [FRONTEND ...]]] [--list-frontends]
[--backend BACKEND] [--list-backends] [--poll-interval POLL_INTERVAL]
[--config CONFIG] [--url URL] [--auth AUTH] [--username USERNAME]
[--password PASSWORD] [--focus-teams [FOCUS_TEAMS [FOCUS_TEAMS ...]]]
[--max-length MAX_LENGTH]
Fetch and display a live CTF scoreboard
optional arguments:
-h, --help show this help message and exit
--frontend [FRONTEND [FRONTEND ...]], -f [FRONTEND [FRONTEND ...]]
Add a frontend
--list-frontends, -F List known frontends
--backend BACKEND, -b BACKEND
Specify a CTF backend
--list-backends, -B List known frontends
--poll-interval POLL_INTERVAL, -i POLL_INTERVAL
Seconds between server polling. Don't set this too low!
--config CONFIG, -c CONFIG
Load a configuration file.
--url URL, -u URL URL to scoreboard. See backend list for specifics.
--auth AUTH, -a AUTH Auth token for scoreboard. See backend list for specifics.
--username USERNAME, -U USERNAME
Username for scoreboard. See backend list for specifics.
--password PASSWORD, -P PASSWORD
Password for scoreboard. See backend list for specifics.
--focus-teams [FOCUS_TEAMS [FOCUS_TEAMS ...]], -t [FOCUS_TEAMS [FOCUS_TEAMS ...]]
One or more team names (regex) to always show
--max-length MAX_LENGTH
Max length of shown scoreboard
Fetches scoreboard and team stats and stuff from a CTF server. Tailor one to whatever score system the event is using.
Optionally implements autodetection ("Does this URL point to a CTF system I can handle?").
Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.
Receives specific updates from the Middle-end and renders it.
Obviously, the front-end is free to ignore events as needed. You could make a front-end which only flashes your keyboard LEDs when p4
scores points!
Reads ~/.ctfront/config.json
which may look like this (everything in there is optional)
{
"focus-teams": [ ".uftens.jaltar", "SmygHalloumi" ],
"max-length": 10,
"poll-interval": 60,
"url": "https://ctf.dicega.ng/scores",
"backend": "auto",
"frontends": [ "fancy" ]
}
Backend sends a snapshot message periodically (poll the server or whatever).
A message is a tuple of ("message_type", { "data" : 1234 })
as specified below:
Most fields may be omitted if unknown. Middle-end will treat this as "No change".
The top-level list (scores
and challenges
) must be a complete listing.
team_id
and challenge_id
are mandatory and decided by the backend. These must remain stable throughout the tournament. If the server doesn't provide something suitable, hash the team name or something.
(
"scoreboard",
{
"scores": [
{
"team_id": "team_id_1",
"name": "LuftensHjaltar",
"place": 69,
"score": 31337
},
...
]
}
)
(
"challenges",
{
"challenges" = [
{
"challenge_id" : "challenge_x",
"solves": [ "team_1", ... ],
"name": "S4n1ty Ch3ck",
"points": 25,
"categories": [ "pwn", "re" ]
},
...
]
}
)
Middle-end sends individual events to the frontend when something changes.
Omitted fields indicate that data is not available, so frontend should format accordingly (i.e. if no team has a "score" field, don't show a score column).
(
"boot",
{
"scoreboard": { same format as scoreboard snapshot }
"challenges": { same format as challenges snapshot }
}
)
(
"solve",
{
"team_id": "team_1",
"challenge_id": "challenge_x",
"first": True
}
)
(
"place",
{
"team_id": "team_1",
"old_place": 69,
"place": 65
}
)
(
"score",
{
"team_id": "team_1",
"old_score": 1335,
"score": 2001
}
)
(
"new_challenge",
{
"challenge_id": "challenge_x",
"name": "Grognar's Revenge",
"points": 500,
"solves": [ "team_id_a", "team_id_b" ],
"categories": [ "pwn", "re" ]
}
)
(
"new_team", { Same format as a team on the scoreboard }
)
- Additional info
- Global solve rate
- Solves over time graph?!
- Some sort of log sink. We can't just
print()
to the terminal because the frontend owns the terminal. Ideally, each frontend should be able to provide that sink, so that it can display text as appropriate.
- JustCTF
- picoCTF
- MITRE
- Mellivora
- Some customizable "Scrape and parse a table" backend?
- Maybe that can make it easier to rig a custom one-use backend on demand?
- For now, take a look at
backends/hxp.py
for an example of beautifulsoup
- ...
A global (persistent) cookie jar to keep from having to authenticate new sessions on every restart
Support is in place for multiple frontends active at the same time, so maybe one for sound, one for video, one for external lighting effects, etc.