Introduction
In an Attack & Defense style CTF, each team starts out with an identical set of vulnerable services. Their goal is to find vulnerabilities in these services, patch their own instances of them, and exploit the vulnerabilities they find in other teams's instances to steal flags.
You will have root access to the machine that's hosting your services, so you will be able to see their source code and modify it to patch the vulnerabilities.
Scoring
The game is split into basic units of time called "ticks" that will last approximately 180 seconds each. On every tick, competitors may lose or gain points based on 3 different scoring categories. The points for the offense and defense categories get added up to calculate the total (), with the winning team having the highest number of points.
Teams keep their uptime percentage () by keeping their services up and running properly. Every tick, the scoring engine will attempt to interact with the service (for example by adding a new flag, checking for existence of a previously added flag, interacting with the service as a general user, etc). If the service is up and functional, the teams will gain (or maintain) their uptime percentage. If the service is down or the functionality is not working as expected, the team will lose uptime percentage for that tick. Because the scoring engine is looking for specific expected responses to validate functionality, you should not modify the application UI or the application flow. Backup zips of the original source code can be found on the systems. In addition to uptime percentage, the teams will also gain 0.5 point per service per tick if their services are up during that tick.
The scoring engine will place a new flag in each of the team's services on every tick. Teams will gain offense () points by stealing those flags from other teams. The team will gain a number of points equivalent to N/<captures of that flag>. N is 1.0 for active teams and 0.2 for inactive teams. A team is "active" if that team has captured at least 1 flag. For example, if a flag placed on a service during tick 10 gets captured by 5 teams, each of those 5 teams will gain 1.0/5=0.20 points for that flag, capped at 15 points per tick, per service.
Teams will lose defense () points when their flags get stolen. If a team's flag for a tick gets stolen, they will lose 0.25 * <number of teams that stole that flag> points for that tick. No more than 2.50 points will be lost per tick.
Offense points will always be positive while defense points are always negative. Uptime score is a percentage of successful ticks over total number of ticks.
The teams will also receive a one-time point bonus for capturing their first flag based on submission time (the sooner you submit your first flag, the higher the bonus will be): 250 * exp(-0.025 * SUBMISSION_TIME_IN_SECONDS/180)
Every tick each of your service is up, you will get 0.5 points for each service. This is your uptime score.
In addition, each team's service's total score will be multiplied by their uptime percentage () to calculate their final score for the service. For example, if a team scores 1000 points in the Attack/Defense for Service A with an uptime percentage () of 75%, the team will end up with 750 points on the Attack/Defense scoreboard for Service A.
Flag Submissions
The scoring engine continuously puts new flags in each team's services (1 flag per service per tick) by using the functionality of that service. The flags are meant to be private, and the location of where they are stored is different for every service. Each flag is also associated with a unique "flag identifier" / "flag id", which corresponds to a different value for different services. Since each service contains many flags, these flag identifiers are made public to the attacking teams, so that they know which flags to go after.
For example, a blog service might give users the ability to create accounts and publish private blog posts. In this case, the flag would be stored in the blog post and the flag identifier might be the email address of the user that posted the flag or it might be the blog post ID. You can find the specific information about the services for this competition on the "Services" page.
Each flag has a certain time to live, after which it will no longer be accepted and can no longer be captured. For this competition, the flag lifetime is equivalent to 5 ticks, which equals roughly 15 minutes. Each flag will have its expiration time published.
A list of available flags can be accessed and captured flags can be submitted using the API. You can access the documentation here, but the following are the main endpoints you should be aware of:
API Documentation and Response Format
All endpoints are rate limited per team to about 60 requests per minute with the exception of the "/submit" endpoint, which is limited to 1000 submissions per minute.
You must send your private team-token in the header.
https://api.ad.mctf.io/endpoints (GET)
Send a request to this endpoint to get a JSON list of all of the available team machines. This list will get updated as more teams join.
https://api.ad.mctf.io/live_flags (GET)
Access this endpoint to see the list of flag identifiers for all available and unexpired flags for all teams except yours.
https://api.ad.mctf.io/submit (POST)
Send your flag (as flag_in in the body) to the scoring engine to be graded. You must send your private team-token in the header.
Here are some code examples you could use to interact with the API.
For other languages, please use a tool like https://curlconverter.com/
GET request
import requests
import json
url = "https://api.ad.mctf.io/live_flags"
headers = {"team-token": "abcd"}
response = requests.get(url, headers=headers)
result = json.loads(response.text)
print(result)
POST request
import requests
url = "https://api.ad.mctf.io/submit"
payload = { "flag_in": "META{XYZ123}" }
headers = { "team-token": "abcd" }
response = requests.post(url, data=payload, headers=headers)
print(response.text)
GET request
curl -X 'GET' 'https://api.ad.mctf.io/live_flags' -H 'team-token: abcd'
POST request
curl -X 'POST' 'https://api.ad.mctf.io/submit' -H 'accept: application/json' -H 'team-token: acdf' -H 'Content-Type: application/x-www-form-urlencoded' -d 'flag_in=META{XYZ}'
GET request
const options = {method: 'GET', headers: {'team-token': 'abcd'}};
fetch('https://api.ad.mctf.io/live_flags', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
POST request
const options = {
method: 'POST',
headers: {
'team-token': 'abcd',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({flag_in: 'META{XYZ123}'})
};
fetch('https://api.ad.mctf.io/submit', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
Packet Captures
Using the provided credentials, you can access a folder with PCAPs that continuously captures the network traffic going to and from your services. Use it to analyze the attacks against your services, so you can patch the exploited vulnerabilities and use the same exploits against the other teams.
Each file contains up to 10 minutes or 8MB of traffic, whichever one gets reached first. The packets get saved to the file continuously (although there's a 4096 byte buffer), so you don't have to wait for tcpdump to finish writing to the file before looking at it. Wireshark might give you a warning if you try to open one of these files, but you can typically ignore it. If you want to modify the packet capture system, see /etc/systemd/system/dumper-health.service and dumper-password.service.
Please note that "Team 1" is not a real team. You can try out attacks against this machine without worrying about leaking them in the .pcap files. Captured flags for this team will not be scored.
Network Design and Environment Access
Each team will be assigned a team number. Each team number corresponds to one box. In the MetaCTF platform, you will receive SSH credentials to your box. On this box, your services are running.
To prevent source-IP filtering, boxes cannot communicate between each other, and neither the players nor the scorebot can reach boxes directly.
Instead, all communication will occur through a central NAT system / TCP proxy (think port forwards) for each service. Our NAT machine at serviceX.ad.mctf.io is exposed to the Internet, and has four ports open for each team box.
Box services / open ports:
- The scored services. Intended for attacks. (IN SCOPE)
HTTP/TCP toserviceX.ad.mctf.ioon port 8000 + TeamNumber. - SSH, for box administration. Each team will receive unique SSH credentials through the MetaCTF platform. Not intended as an attack vector. (OUT OF SCOPE)
SSH toteams.ad.mctf.ioon port 2000 + TeamNumber. - Tulip, an Attack/Defense traffic visualization tool. Use your credentials to access this. Not intended as an attack vector. (OUT OF SCOPE)
HTTP toteams.ad.mctf.ioon port 3000 + TeamNumber. - TCPDump pcap listings, for forensics. Use your credentials to access this. Not intended as an attack vector. (OUT OF SCOPE)
HTTP toteams.ad.mctf.ioon port 4000 + TeamNumber. - VSCode, to view the code stored on the machine, search for exploits, or work on patches. (You will need to use ssh to actually push the patch in most cases). Use your credentials to access this. Not intended as an attack vector. (OUT OF SCOPE)
HTTP toteams.ad.mctf.ioon port 5000 + TeamNumber.
For example, if your team number was Team 1, then the first scored service for your team would be hosted on port 8000 + 1 = 8001, and you would run curl service1.ad.mctf.io 8001 to reach it.
If you wanted to attack Team 67's service #2, 8000 + 67 = 8067, you would send your traffic to service2.ad.mctf.io:8067.