Skip to content

Commit

Permalink
Merge branch 'master' into kdctf-2023
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkusOstermayer committed Feb 10, 2024
2 parents c9e2e03 + 98383a0 commit 2a70062
Show file tree
Hide file tree
Showing 115 changed files with 3,341 additions and 1,845 deletions.
6 changes: 6 additions & 0 deletions .devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"image": "python:3.9-alpine",
"updateContentCommand": "apk --no-cache add git curl build-base jpeg-dev zlib-dev iputils-ping",
"postCreateCommand": "pip3 install --editable .[dev] && make dev",
"extensions": ["ms-python.python"]
}
37 changes: 24 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,50 @@ on:
- pull_request

jobs:
lint:
name: Lint soure code
runs-on: ubuntu-latest
container: python:3.11-bullseye
steps:
- uses: actions/checkout@v3
- run: pip install -e .[dev]
- run: make lint

# Test with Tox, a recent Python version and libraries from PyPI
test_tox:
name: Test with Tox
runs-on: ubuntu-latest
container: python:3.10-bullseye
container: python:3.11-bullseye
permissions:
# Required for "EnricoMi/publish-unit-test-result-action"
checks: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup dependencies
run: |
pip install tox
# Make sure we have our dependencies, which are not required for Tox but for `make build`
pip install -e .
# Ping is required for VPNStatusTest
apt-get --yes update
apt-get --yes install iputils-ping
- run: make build
- run: tox -e py310 -- --junitxml=.tox/py310/log/results.xml
- run: find .
- run: tox -e py311 -- --junitxml=.tox/py311/log/results.xml
- name: Publish unit test results
uses: EnricoMi/publish-unit-test-result-action@v1
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: .tox/py*/log/results.xml
comment_mode: "off"
- name: Archive unit test results
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: always()
with:
name: tox-test-results
path: .tox/py*/log/results.xml
if-no-files-found: error
- name: Archive code coverage results
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: always()
with:
name: tox-code-coverage-report
Expand All @@ -49,7 +60,7 @@ jobs:
runs-on: ubuntu-latest
container: debian:bullseye
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: echo 'deb http://deb.debian.org/debian/ bullseye-backports main' >> /etc/apt/sources.list
- run: apt-get --yes update
- run: apt-get --yes install --no-install-recommends devscripts dpkg-dev equivs
Expand All @@ -60,7 +71,7 @@ jobs:
- run: dpkg-buildpackage --unsigned-changes --unsigned-buildinfo
- run: mv ../ctf-gameserver_*.deb .
- name: Store Debian package
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: deb-package
path: ctf-gameserver_*.deb
Expand All @@ -73,8 +84,8 @@ jobs:
container: debian:bullseye
needs: build_deb_package
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: deb-package
- run: echo 'deb http://deb.debian.org/debian/ bullseye-backports main' >> /etc/apt/sources.list
Expand All @@ -87,14 +98,14 @@ jobs:
- run: make build
- run: pytest-3 --junitxml=results.xml --cov=src --cov-report=term --cov-report=html tests
- name: Archive unit test results
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: always()
with:
name: debian-test-results
path: results.xml
if-no-files-found: error
- name: Archive code coverage results
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: always()
with:
name: debian-code-coverage-report
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ __pycache__/

# Web component
/src/ctf_gameserver/web/dev-db.sqlite3
/src/ctf_gameserver/web/team_downloads/
/src/ctf_gameserver/web/uploads/
/src/ctf_gameserver/web/static/ext/
/src/ctf_gameserver/web/registration/countries.csv
Expand Down
7 changes: 4 additions & 3 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Copyright (c) 2015 FAUST -- FAU Security Team
Copyright (c) 2015 Christoph Egger
Copyright (c) 2015 Felix Dreissig
Copyright (c) FAUST -- FAU Security Team
Copyright (c) Christoph Egger
Copyright (c) Felix Dreissig
Copyright (c) Simon Ruderich

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
Expand Down
19 changes: 12 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ EXT_DIR ?= $(WEB_DIR)/static/ext
DEV_MANAGE ?= src/dev_manage.py
TESTS_DIR ?= tests

.PHONY: dev build ext migrations test lint clean
.PHONY: dev build ext migrations run_web test lint run_docs clean
.INTERMEDIATE: bootstrap.zip

dev: $(WEB_DIR)/dev-db.sqlite3 ext
Expand All @@ -13,11 +13,11 @@ ext: $(EXT_DIR)/jquery.min.js $(EXT_DIR)/bootstrap $(WEB_DIR)/registration/count


migrations: $(WEB_DIR)/registration/countries.csv
$(DEV_MANAGE) makemigrations templatetags registration scoring flatpages
$(DEV_MANAGE) makemigrations templatetags registration scoring flatpages vpnstatus

$(WEB_DIR)/dev-db.sqlite3: migrations $(WEB_DIR)/registration/countries.csv
$(DEV_MANAGE) migrate
$(DEV_MANAGE) createsuperuser --username admin --email ''
DJANGO_SUPERUSER_PASSWORD=password $(DEV_MANAGE) createsuperuser --no-input --username admin --email 'admin@example.org'

$(EXT_DIR)/jquery.min.js:
mkdir -p $(EXT_DIR)
Expand All @@ -36,16 +36,21 @@ $(WEB_DIR)/registration/countries.csv:
curl https://raw.githubusercontent.com/datasets/country-list/master/data.csv -o $@


run_web:
$(DEV_MANAGE) runserver

test:
pytest --cov $(SOURCE_DIR) $(TESTS_DIR)

lint:
# Run Pylint, pycodestyle and Bandit to check the code for potential errors, style guideline violations
# and security issues
# REVISIT: Respect exit codes when linter issues are fixed
-pylint --rcfile $(SOURCE_DIR)/pylintrc $(SOURCE_DIR) $(TESTS_DIR)/test_*.py
-pycodestyle $(SOURCE_DIR) $(TESTS_DIR)
-bandit --ini bandit.ini -r $(SOURCE_DIR)
pylint --rcfile $(SOURCE_DIR)/pylintrc $(SOURCE_DIR) $(TESTS_DIR)
pycodestyle $(SOURCE_DIR) $(TESTS_DIR)
bandit --ini bandit.ini -r $(SOURCE_DIR)

run_docs:
mkdocs serve

docs_site: mkdocs.yml $(wildcard docs/* docs/*/*)
mkdocs build --strict
Expand Down
100 changes: 53 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,69 @@
CTF Gameserver
==============

This is a gameserver for [attack-defense (IT security) CTFs](https://ctftime.org/ctf-wtf/). It was originally
written for [FAUST CTF 2015](https://www.faustctf.net/2015/), but is designed to be re-usable for other
This is a Gameserver for [attack-defense (IT security) CTFs](https://ctftime.org/ctf-wtf/). It is used for
hosting [FAUST CTF](https://www.faustctf.net), but designed to be re-usable for other competitions. It is
scalable to large online CTFs, battle-tested in many editions of FAUST CTF, and customizable for other
competitions.

What's included
For documentation on architecture, installation, etc., head to [ctf-gameserver.org](https://ctf-gameserver.org/).

What's Included
---------------
The gameserver consists of multiple components. They may be deployed separately of each other as their only
means of communication is a shared database.
The Gameserver consists of multiple components:

* Web: A [Django](https://www.djangoproject.com/)-based web application for team registration and
scoreboards. It also contains the model files, which define the database structure.
* Web: A [Django](https://www.djangoproject.com/)-based web application for team registration, scoreboards,
and simple hosting of informational pages. It also contains the model files, which define the database
structure.
* Controller: Coordinates the progress of the competition, e.g. the current tick and flags to be placed.
* Checker: Offers an interface for checker scripts, which place and retrieve flags and test the status of
services.
* Checker: Place and retrieve flags and test the service status on all teams' Vulnboxes. The Checker Master
launches Checker Scripts, which are individual to each service.
* Checkerlib: Libraries to assist in developing Checker Scripts. Currently, Python and Go are supported.
* Submission: Server to submit captured flags to.
* Lib: Some code that is shared between the components.

For deployment instructions and details on the implementations, see the `README`s of the individual
components.
* VPN Status: Optional helper that collects statistics about network connectivity to teams.

Related projects
Related Projects
----------------
There are several alternatives out there, although none of them could really convince us. Your mileage may
vary at this point.

* ucsb-seclab/ictf-framework from the team behind iCTF, one of the most well-known
attack-defense CTFs. In addition to a gameserver, it includes utilities for VM creation and network setup.
We had trouble to get it running and documentation is generally rather rare.
* HackerDom/checksystem is the gameserver powering the RuCTF. The first impression wasn't too bad, but it
didn't look quite feature-complete to us. However, we didn't really grasp the Perl code, so we might have
overlooked something.
* isislab/CTFd appears to be de-facto standard for [jeopardy-based CTFs](https://ctftime.org/ctf-wtf/). It
is, however, not suitable for an attack-defense CTF.
There are several alternatives out there, although none of them could really convince us when we started the
project in 2015. Your mileage may vary.

* [ictf-framework](https://github.com/shellphish/ictf-framework) from the team behind iCTF, one of the most
well-known attack-defense CTFs. In addition to a gameserver, it includes utilities for VM creation and
network setup. We had trouble to get it running and documentation is generally rather scarce.
* [HackerDom checksystem](https://github.com/HackerDom/checksystem) is the Gameserver powering RuCTF. The
first impression wasn't too bad, but it didn't look quite feature-complete to us. However, we didn't really
grasp the Perl code, so we might have overlooked something.
* [saarctf-gameserver](https://github.com/MarkusBauer/saarctf-gameserver) from our friends at saarsec is
younger than our Gameserver. It contains a nice scoreboard and infrastructure for VPN/network setup.
* [EnoEngine](https://github.com/enowars/EnoEngine) by our other friends at ENOFLAG is also younger than
our solution.
* [CTFd](https://ctfd.io/) is the de-facto standard for [jeopardy-based CTFs](https://ctftime.org/ctf-wtf/).
It is, however, not suitable for an attack-defense CTF.

Another factor for the creation of our own system was that we didn't want to build a large CTF on top of a
system which we don't entirely understand.

Design principles
-----------------
The software will probably only be used once a year for severals hours, but it has to work reliably then. It
will hopefully continue to be used by future generations. These requirements led to the incorporation of
some principles:

* Non-complex solutions: Keep the amount of code low and chose the less fancy path. That's why we use the
built-in Django admin interface instead of writing a custom admin dashboard – it'll be good enough for the
few people using it.
* Few external dependencies: Of course one shouldn't re-invent the wheel all over again, but every external
dependency means another moving part. Some libraries you always have to keep up with, others will become
unmaintained. We therefore focus on few, mature, well-chosen external dependencies. That's why we use a
plain old Makefile instead of [Bower](http://bower.io/) for JavaScript dependencies and Django's built-in
PBKDF2 instead of fancy [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) for password hashing.
* Extensive documentation: This should be a no-brainer for any project, although it is easier said than done.
* Re-usability: The gameserver should be adjustable to your needs with some additional lines of code. An
example for such customizations can be found in the `faustctf-2015` branch of this repository.
* Scalability: We couldn't really estimate the load beforehand, nor could we easily do realistic
load-testing. That's why the components are loosely coupled and can be run on different machines.

Licensing
Development
-----------
For a local development environment, set up a [Python venv](https://docs.python.org/3/library/venv.html) or
use our [dev container](https://code.visualstudio.com/docs/devcontainers/containers) from
`.devcontainer.json`.

Then, run `make dev`. Tests can be executed through `make test` and a development instance of the Web
component can be launched with `make run_web`.

We always aim to keep our Python dependencies compatible with the versions packaged in Debian stable.
Debian-based distributions are our primary target, but the Python code should generally be
platform-independent.

Security
--------
Should you encounter any security vulnerabilities in the Gameserver, please report them to us privately.
Use GitHub vulnerability reporting or contact Felix Dreissig or Simon Ruderich directly.

Copyright
---------
The whole gameserver is released under the MIT (expat) license. Contributions are welcome!
The Gameserver was initially created by Christoph Egger and Felix Dreissig. It is currently maintained by
Felix Dreissig and Simon Ruderich with contributions from others.

It is released under the ISC License.
7 changes: 2 additions & 5 deletions conf/controller/controller.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
CTF_LOGLEVEL="INFO"
CTF_DBHOST="localhost"
CTF_DBNAME="ctf_gameserver"
CTF_DBUSER="ctf_controller"
CTF_DBPASSWORD="PASSWORD"
CTF_DBNAME="DUMMY"
CTF_DBUSER="DUMMY"
5 changes: 1 addition & 4 deletions doc/controller/scoring.sql → conf/controller/scoring.sql
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ WITH
)
SELECT team_id,
service_id,
coalesce(attack, 0)::double precision as attack,
(coalesce(attack, 0)+coalesce(bonus, 0))::double precision as attack,
coalesce(bonus, 0) as bonus,
coalesce(defense, 0)::double precision as defense,
coalesce(sla, 0) as sla,
Expand All @@ -74,6 +74,3 @@ NATURAL FULL OUTER JOIN defense
NATURAL FULL OUTER JOIN sla
NATURAL INNER JOIN fill
ORDER BY team_id, service_id;

ALTER MATERIALIZED VIEW scoring_scoreboard OWNER TO gameserver_controller;
GRANT SELECT on TABLE scoring_scoreboard TO gameserver_web;
28 changes: 23 additions & 5 deletions conf/submission/ctf-submission@.service
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
[Unit]
Description=CTF Flag-Submission Service
Wants=postgresql.service
After=postgresql.service

[Service]
Type=notify
DynamicUser=yes
# Python breaks without HOME environment variable and with `DynamicUser`
Environment=HOME=/tmp
EnvironmentFile=/etc/ctf-gameserver/submission.env
ExecStart=/usr/bin/ctf-submission --port %i
User=nobody
Group=nogroup
RestartSec=10
EnvironmentFile=-/etc/ctf-gameserver/submission-%i.env
ExecStart=/usr/bin/ctf-submission
Restart=on-failure
RestartSec=5

# Security options
CapabilityBoundingSet=
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateDevices=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=strict
RestrictNamespaces=yes
RestrictRealtime=yes
SystemCallArchitectures=native

[Install]
WantedBy=multi-user.target
3 changes: 2 additions & 1 deletion conf/submission/submission.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
CTF_DBNAME="DUMMY"
CTF_DBUSER="DUMMY"
CTF_SECRET="DUMMY"

CTF_FLAGSECRET="RFVNTVlTRUNSRVQ="
CTF_TEAMREGEX="^0\.0\.(\d+)\.\d+$"
10 changes: 10 additions & 0 deletions conf/vpnstatus/ctf-vpnstatus.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CTF_DBNAME="DUMMY"
CTF_DBUSER="DUMMY"

CTF_WIREGUARD_IFPATTERN="wg%d"

CTF_GATEWAY_IPPATTERN="0.0.%s.1"
CTF_DEMO_IPPATTERN="0.0.%s.3"
CTF_DEMO_SERVICEPORT="80"
CTF_VULNBOX_IPPATTERN="0.0.%s.2"
CTF_VULNBOX_SERVICEPORT="80"
20 changes: 20 additions & 0 deletions conf/vpnstatus/ctf-vpnstatus.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[Unit]
Description=CTF Gameserver Controller
After=postgresql.service

[Service]
Type=notify
User=ctf-vpnstatus
EnvironmentFile=/etc/ctf-gameserver/vpnstatus.env
ExecStart=/usr/bin/ctf-vpnstatus
Restart=on-failure
RestartSec=5

# Security options, cannot use any which imply `NoNewPrivileges` because checks can get executed using sudo
PrivateTmp=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectSystem=strict

[Install]
WantedBy=multi-user.target
Loading

0 comments on commit 2a70062

Please sign in to comment.