Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - eldstal/CTF: CTF Terminal Frontend · GitHub
Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - eldstal/CTF: CTF Terminal Frontend · GitHub
Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - eldstal/CTF: CTF Terminal Frontend · GitHub
Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - eldstal/CTF: CTF Terminal Frontend · GitHub
Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - eldstal/CTF: CTF Terminal Frontend · GitHub
Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - eldstal/CTF: CTF Terminal Frontend · GitHub
Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - eldstal/CTF: CTF Terminal Frontend · GitHub
Skip to content

Repository files navigation

CTF Terminal Frontend

Fetches and displays live scores from Capture The Flag tournaments in your terminal. With pep and zing!

Supported CTF systems:

Shared, open-source systems:

Custom, one-off or otherwise difficult customers:

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.

Example

asciicast

(Click for an animated preview!)

Usage

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

Internals

Backend

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?").

Middle-end

Keeps a running copy of the CTF state and identifies changes. Sends events to the front-end when something interesting changes.

Front-end

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!

Configuration

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" ]
}

Protocols

Back -> Middle

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 -> Front

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": { sameformatasscoreboardsnapshot }
"challenges": { sameformataschallengessnapshot }
}
)
(
"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", { Sameformatasateamonthescoreboard }
)

TODO

Cool features

  • Additional info
    • Global solve rate
    • Solves over time graph?!

Useful features

  • 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.

Backends

  • JustCTF
  • picoCTF
  • MITRE
  • google
  • 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

Frontend

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.

Releases

Used by

Contributors

Languages