For an introduction to attack-defense CTFs, we recommend skimming the fantastic introduction provided
by the CTF team FAUST: you can find it
here.
Teams participating in the 2021 iCTF will be required to host their own vulnboxes,
and thus teams will have root access in their vulnbox.
As we get closer to the competition date, we'll be releasing an encrypted
.ova image of the vulnbox.
When the iCTF begins, we will release the decryption key for the vulnbox via our
twitter account,
Discord,
and on this website.
You'll be able to decrypt this file with the command
$ gpg --batch --passphrase-file <keyfile> --output <filename.ova> --decrypt <filename>.ova.gpg
Once you have decrypted the
.ova file, you'll need to run it somewhere.
Here we'll cover two potential methods of running it: either via virtualbox on your local machine or via AWS.
Running the Vulnbox Locally
If you're playing by yourself or with a small team, it's easiest to run the vulnbox locally on
your local machine.
To do so, you'll need to install VirtualBox and import the ova (File→Import Appliance) and
then select the decrypted
.ova file.
If you want to share files between your host system and the vulnbox, you can install the virtualbox
guest additions.
Your services live in
/opt/ictf/services. Make sure to run
docker-compose up -d as the user
ctf.
Hosting the Vulnbox on AWS
If you're playing on a larger team, it may be more helpful to host the vulnbox remotely.
Fortunately, AWS is capable of running a OVA on an EC2 instance.
Detailed instructions on how to do this can be found
here.
For your convenience, the decrypted OVA will be available in AWS S3 at
https://ictf-teamvm-decrypted.s3.us-west-1.amazonaws.com/teamvm.ova.
You should use this in the
containers.json file.
Connecting to the Game Network
Once your vulnbox is up and running, you'll need to log in and connect to the game VPN.
The username is ubuntu with password ubuntu.
To do so, follow these instructions:
- You'll receive a teamNN.ovpn file - put this in your VM at /etc/openvpn/client.conf
- Make sure systemd knows about your new VPN connection: systemctl daemon-reload
- Restart OpenVPN: systemctl restart openvpn
- If everything went well, you'll see a new tun0 interface and a 10.9.0.0/16 route through it
- Test your connection by pinging the router: ping 10.9.1.1
- WARNING: OpenVPN servers are running via UDP in port range 1100-1140.
To play the iCTF, you'll also need to be able to interact with the iCTF API, which is done via a python module which you can install by running
pip install swpag-client.
Interactions with the game infrastructure will take place through this client. You'll use it to get the game status, find potential target services, and submit flags.
The first thing you'll do with the swpag-client is create a new Team object using the client and the
FLAG_TOKEN you received via email:
>>> from swpag_client import Team
>>> t = Team("http://teaminterface.ictf.gg/", "your_flag_token_here")
The Team lets you query a variety of information from the game infrastructure. Some important examples are:
Once your vulnbox is running and connected to the VPN, it's time to start playing the iCTF!
Your first task will be to explore the services that you must attack and defend,
which you can find in your vulnbox under /opt/ictf/services.
All the challenges are running inside a docker container, and the challenges directories are mounted
in the containers, so you should be able to patch them by modifying the files on disk unless noted
otherwise in the challenge description.
We'll be providing a docker-compose.yml file for all the challenges,
so this should be relatively easy.
In each service, you'll find three directories:
The purpose of the
append directory is typically to store flags or other
flag-related data: by only allowing the creation of new files, opponents that get code execution through
the service won't be able to bring your service down by deleting the flags stored in it.
Once you have reverse-engineered a service and developed your new l33t exploit, you'll need a list of
teams to attack!
Inside each instance of each service there are multiple flags; to prove your exploit works, you'll need
to find the correct flag, which is determined by the current flag id for that team and service.
This current flag id will be changed every tick.
To get a list of targets and their flag ids, run t.get_targets(service_id).
The flag regex is FLG[0-9A-Za-z]{13}
Once you've gotten some flags, you can submit them with:
>>> t.submit_flag(["FLGxxxxxxxxxxxxx"])
>>> t.submit_flag(["FLGxxxxxxxxxxxxx", "FLGyyyyyyyyyyyyy", ...]) # submit multiple flags
Also, note that you the list of flags you're submitting can't be longer than 100! If you need to submit more
flags, you'll need to make a second call to the API.
Make sure to submit any flags within a couple rounds or they won't be scored!
The scoring system is relatively simple. For a single team-service (i.e. pair of a team and service),
there are three cases to consider:
-
Service is up and unexploited: the team receives 50 points
-
Service is up and exploited: every team that exploits this team-service gets 50 points
divided by the number of teams who exploited this team
-
Service is down: every team that has this service up gets 50 points divided by the number
of teams who have this service up.
Thus, the same number of points are given out per round:
50 * num_teams * num_services
In Python, this translates to:
def update_score_for_tick():
for chall in challenges:
update_score_for_tick_for_challenge(chall)
def update_score_for_tick_for_challenge(chall):
teams_up = [team for team in teams if team.status[chall] == 'up']
teams_down = [team for team in teams if team.status[chall] == 'down']
point_income_from_down_teams = (len(teams_down) * 50) / teams_up
for team in teams_up:
team.score += point_income_from_down_teams
for team in teams_up:
if len(team.exploited_by[chall]) == 0:
team.score += 50
else:
point_income_from_exploit = 50 / len(team.exploited_by[chall])
for exploiter in team.exploited_by['chall']:
exploiter.score += point_income_from_exploit