ECSC 2025 A/D
The European Cybersecurity Challenge 2025 will take place from the 6th to 10th of October 2025 at the COS Torwar Arena in Warsaw. The Attack-Defense CTF will take place on 8th of October, starting at 10:00 CEST and lasting 8 hours until 18:00 CEST.
Attack-Defense
Attack-Defense CTFs are a type of cybersecurity competition in which participating teams host services and attempt to exploit each other over a shared, private network. The goal of the game is to earn points by stealing secrets stored in your opponents' service instances, and to avoid losing points, by preventing your own secrets from being stolen and submitted, all the while keeping the services available and functioning. The team with the most points by the end wins.
CTF Schedule
The schedule for the day of the Attack-Defense CTF:
| Time1 | Event / State change |
|---|---|
| earlier | Players receive credentials to the platform via the ECSC 2025 Discord |
| 09:00 | Players can download wireguard configuration to access the game network |
| 09:30 | Players can start their vulnbox using the platform |
| 10:00 | The Attack-Defense CTF officially begins |
| - | SSH access to vulnboxes is unblocked |
| - | API endpoints are available to test connectivity |
| - | Flag submission at 10.42.251.2:1337 accepts connections |
| - | Scoreboard at 10.42.251.2 is available (empty) |
| - | Team router and scoreboard respond to pings |
| - | VPN Connection works within but not between teams |
| 11:00 | Network opens and teams can communicate with other vulnboxes |
| 17:00 | The scoreboard scores are frozen |
| 18:00 | The Attack-Defense CTF officially ends |
1 all times CEST
Game Overview
Each team is given root access to a cloud-hosted Linux-based virtual machine that exposes vulnerable services to other teams over a private virtual network.
Over the course of every round, lasting 60 seconds, so-called checkers store text snippets called flags in the services on each team's vulnbox and test their functionality to make sure they are working as intended. Extracting these flags from other teams' services and submitting them to a central flag submission each round to earn ATK-points is the primary goal of the game.
flag stores
A checker may store multiple unique flags each round in distinct areas of a service, and there may be more than one intended vulnerability to reach each one.
To incentivize teams to keep their services available to other teams to exploit, a series of checks is performed each round against every service of every team by the organizers' checkers. These tests define the so-called Service-Level Agreement (SLA); the functionality required for a team to earn SLA-points each round.
attack info
Checkers may provide hints for successfully stored flags to help guide exploits. In some cases, this info is crucial to exploiting the vulnerability at all. It can be retrieved via the attack api.
Each round a team receives DEF-points for every service. The amount of points earned is highest when the service is unexploited and decreases with the amount of other teams exploiting it.
These points combine to calculate the team score using the scoring formula.
Flags
Each flag is matched by the regular expression /^ECSC\{[A-Za-z0-9-_]{32}\}$/
Checkers retrieve flags from the previous 5 rounds in addition to the current round to enable exploits which take longer than a single round to complete. The SLA penalty for missing any of the flags in this retention period incentivizes teams to keep them available for capture.
Flags will award points upon submission for 5 rounds including the round they were deployed in.
Each flag consists of a prefix and suffix, that wrap a base64-encoded1 payload with the following format:
2 bytes: round id2 bytes: team id2 bytes: service id2 bytes: flagstore id16 bytes: SHA256-HMAC (of first 8 bytes)
Flag Submission
Players can submit stolen flags by sending them line-delimited in a plain TCP
connection to 10.42.251.2 on port 1337. This must be done via the game
network, since the source ip is used to determine the submitting team.
For each line, in the order that they are received, the flag submission will return one of the following results on a new line:
[OK]: The flag is valid and was accepted[ERR] Invalid format: The flag is malformed[ERR] Invalid flag: The signature of the flag is incorrect[ERR] Expired: The flag was submitted after the retention period[ERR] Already submitted: The flag has already been submitted by this team[ERR] Can't submit flag from NOP team: The flag submitted is from NOP[ERR] This is your own flag: The flag is from the submitting team
-
This payload is encoded using the base64url charset ↩
Team VMs
Cloud-hosted vulnboxes and exploiter VMs are provided by the
organizers to all teams.
The team router provides access to the
internet via a public IPV4 and IPv6 address, as well as to the game
network through WireGuard. The vulnbox and exploiter are not
reachable from the internet via their public IPv4 or IPv6 address.
Setup
Vulnboxes and exploiters run on OVHCloud infrastructure as C3-32 instances in Warsaw's WAW1 data center. With 16 cores, 32 GB of ram and 400 GB of storage each, the vulnbox and exploiter should have enough resources to run the services, exploit their vulnerabilities, and handle exploit as well as checker traffic.
Self-hosting services is not officially supported, and carries a latency and bandwidth penalty. The Wireguard connection from the vulnbox to the router is established inside a cloud-provider network, which allows for a faster connection than the public interface a self-hosted vulnbox would have to proxy traffic over.
Each vulnbox is configured to accept SSH keys by the organizers in addition to those configured by the teams via the platform. Teams are free to remove these SSH keys, however doing so limits the amount of support and automated fixes the organizers can provide.
Vulnboxes and exploiters are assigned an IPv4 address in the Game Network and the Team Cloud Network.
Firewall
Vulnboxes are provisioned with an iptables firewall that drops all traffic except:
- Ingress connections from the game network to ports exposed by docker
- Ingress connections to SSH (port 22)
- Established connections
- Egress connections
Additionally, the vulnbox is firewall'd at the cloud-provider level from external access to ensure all game-related traffic runs through the router and misconduct can be accounted for.
Players may start their vulnbox 30 minutes before the CTF begins through the platform, but SSH access will be prevented through the team router until the CTF has officially started.
Game Network
The A/D CTF takes place in the game network. Players connect to this network through a WireGuard tunnel. WireGuard configuartion files will be made available one hour before the game starts via the platform. They can be used with standard WireGuard tooling such as wg-quick.
VPN Access
Every WireGuard configuration file allows exactly one host to connect to the game network. Trying to use the same configuration on multiple hosts simultaneously will likely make the connection unstable for all hosts using that configuration. We provide three configuration files to every player, and 10 additional configs per team, which are only accessible to captains and players promoted to Technician on the platform.
The VPN endpoint will be accessible via IPv4 for the infrastructure demo on 28.09.2025, and most likely only via IPv6 for the final CTF.
The configuration files contain credentials and information about the VPN endpoint. Do not share any of this information with anyone outside your team. This includes the endpoint information - it is different for every team. Modifying the configuration files should not be necessary.
The VPN connection is only used to access the game network. Most importantly, your vulnbox, your team members, other teams' vulnboxes, the flag submission, attack info, and the scoreboard. Your devices can not use the VPN connection to access the internet.
Game Network IPs
The game network uses the address range: 10.42.0.0/16.
Every team has its own subnet, the team network: 10.42.<TEAM>.0/24
Each team's vulnbox gets the ip 10.42.<TEAM>.2.
Every team network has a gateway 10.42.<TEAM>.254, controlled by the infrastructure.
Every host connected to the game network has an ip in its team's subnet and is reachable from other hosts in the team subnet.
The NOP (non-playing) team is assigned the team id 1.
Therefore, the NOP vulnbox is available at the ip 10.42.1.2.
The gameserver has the ip address 10.42.251.2, and hosts the scoreboard (port 80),
the flag submission (port 1337) and the Attack API (port 8080).
Access to the vulnbox
Connecting to vulnboxes is only possible through the VPN, including your own team's vulnbox. Vulnboxes can access the internet, but they do not have a designated public ip. They essentially sit behind a NAT. This is done to ensure all interactions can be properly observed by the organizers and any misconduct can be accounted for. It also minimizes opportunities for "opsec fails" and attacks that are likely against the rules of conduct.
Note that the overall bandwidth for each team's in- and outbound VPN traffic is capped at 1.8gbit/s. Keep this bandwidth budget in mind if you intend to extract pcaps from your vulnbox to another host.
Team Cloud Network
Each team's router, exploiter and vulnbox are also connected to an internal cloud network without WireGuard. Connectivity within this network is mostly unrestricted.
This network has the address range: 10.43.<TEAM>.0/24
The vulnbox gets the ip 10.43.<TEAM>.2
The exploiter gets the ip 10.43.<TEAM>.3
The gateway gets the ip 10.43.<TEAM>.254
These addresses of other teams are not reachable via the game network.
Traffic Anonymization
Connections originating from outside your teams network are anonymized to prevent checker fingerprinting.
All connections from checkers and other teams will appear to originate from
10.42.<TEAM>.254 - your team's gateway.
You should consider the following when encountering network issues:
- Packets with IP header options are rejected since they are likely used
unintentionally and are easily fingerprintable. The game network will reply with ICMP type
Destination unreachable(3) and codeAdministratively Prohibited(13). - TCP headers are normalized to prevent teams from telling apart checkers from exploiters via patterns in TCP options / flags usage.
- TTLs of packets entering team networks are capped at 32 such that traffic to vulnboxes arrives with the same TTL regardless from where it was sent.
- TTLs are not decremented in our router mesh to prevent
traceroute-ing of router topology. - We artificially introduce latency and degrade performance to prevent fingerprinting based on network conditions / response time of external exploiters.
- MTU negotiations are dropped to prevent using cached PMTU values to keep a single host fingerprinted.
- TCP MSS is set to a fixed value (MTU - 40) to prevent fingerprinting and potentially causing high packet rates.1
Despite all of this, it might still be advisable to run exploits from the vulnbox if you suspect your traffic is being fingerprinted.
Game Firewall
While traffic within one team is almost unrestricted, traffic between teams is heavily restricted. The only allowed traffic is:
- TCP connections, but only to vulnboxes
- ICMP traffic of type
Echo Request(8),Echo Reply(0) andDestination unreachable(3) with codes 0, 1, 2, 3, 10, and 13.
Note that attacking other teams hosts other than the vulnbox is generally prohibited.
Additionally, the game firewall blocks connections to other teams' vulnboxes in the port range 8000 to 9000 (exclusive). Using this port range for tooling deployed on the vulnbox ensures that it can not be easily accessed by other teams - even if the host firewall would allow it.
Finally, the network drops malformed packets, packets with invalid checksums, and packets that can't be attributed to connections. These firewall rules are designed to protect teams and infrastructure from malicious traffic that does not target the services. You generally won't receive ICMP messages for prohibited traffic, it will just be discarded.
Bandwidth Limits
We impose a bandwidth limit on the sum of traffic between any two distinct teams.
This limit is at least 20mbit/s.
Note that the bandwidth achievable for a single connection between teams may be significantly less than 20mbit/s due to how our traffic anonymization policies affect TCP throughput.
We also impose a bandwidth limit of at least 20mbit/s on connections to the gameserver per team.
Teams should monitor the traffic generated by their exploits to avoid becoming fingerprintable based on latency introduced by hitting the bandwidth limit.
-
The router MTU and MSS values for the final CTF will be announced at a later date, since the onsite connection needs to be tested for this. The infrastructure demo on 28.09.2025 will use an MTU of 1340 and a MSS of 1300. ↩
Platform
The platform at platform.ad.ecsc2025.pl is used for distributing WireGuard configs, collecting player SSH keys, and giving teams control over their cloud-hosted vulnbox instance.
All players are registered ahead of time on the platform, WireGuard configs may be downloaded 1 hour before the CTF begins.
With their accounts, players may submit SSH keys, which will later be deployed onto the vulnbox, as well as download WireGuard configs for accessing the game network through their team-specific router.
Attack API
We provide a documented API on the gameserver (at 10.42.251.2 on port 8080),
which returns filterable game-related information for use by team infrastructure.
Endpoints
You may try the api live at https://api.demo.ad.ecsc2025.pl.
/api/v1/services : Fine-grained service info
This endpoint returns service information, by default for all services.
Data for a specific service may be queried through the
use of URL parameters./api/v1/teams : Fine-grained team info
This endpoint returns team information, by default for all teams.
Data for a specific team may be queried through the
use of URL parameters./api/v1/score : Fine-grained scoring info
This endpoint returns scoring related information, by default for the
current round. Data for a specific round, team and
service may be queried through
the use of URL parameters.An example of a valid response:
/api/v1/attack_info : Fine-grained attack info
This endpoint returns attack info for services, by default for the
current round. Data for a specific round, team,
service or flagstore may be queried through
the use of URL parameters./api/v1/current_round : current round time and id
This endpoint returns the current round id and start time as an ISO-8601
UTC timestamp with second precision.
If the game has not started, this endpoint will return round 0.
/api/v1/next_round : next round time and id
This endpoint waits for the current round to complete before returning the
new round start time as an ISO-8601 UTC timestamp with second precision.
The connection may timeout if the new round has not started after two rounds worth of time./api/faustctf2024/teams.json : Attack Info (FaustCTF 2024)
This endpoint returns attack info in the FaustCTF 2024 /teams.json format.{
"teams": [
# team ids
123, 456, 789,
..
],
"flag_ids": {
# service name : service info
"service1": {
# team id : attack infos for validity period
"123": ["abc123", "def456"],
"124": ["xxx", "yyy"],
..
},
..
}
}
/api/saarctf2024/attack.json : Attack Info (SaarCTF 2024)
This endpoint returns attack info in the SaarCTF 2024 /attack.json format.
{
"teams": [
# team infos
{
"id": 1,
"name": "NOP",
"ip": "10.42.1.2"
},
..
],
"flag_ids": {
# service name : service info
"fooserv": {
# team vulnbox ip : team info
"10.42.1.2": {
# round id : attack info
"123": ["info_flag1", "info_flag2"]
..
},
..
},
"barserv": {
"10.42.1.2": {
"123": "info_single"
},
..
}
}
}
API Details
The API returns attack info generated at the start of the round specified by the request. Scoring data returned by the api is the state of team scores at the start of the round specified in the request.
Round schedule drift
Even though successful rounds are guaranteed to stay aligned with the
round interval of 60 seconds, single rounds may be cancelled in rare cases,
such as when the gameserver needs to be restarted to address an infrastructure
issue. To keep players in sync with the round schedule despite this albeit
rare possibility, please use the /api/v1/next_round endpoint.
Parameters
The first played round of the CTF has the id 1.
Player team ids start at 2, since id 1 is reserved for the NOP Team.
Service ids are indexed starting at 1.
Flagstore ids are indexed starting at 0.
Scoreboard
Any APIs made available through the scoreboard host at 10.42.251.2 on port
80 exist solely for enabling the client-side functionality of the scoreboard.
No guarantees are made for the availability or contents of these APIs.
Scoring Formula
In Jeopardy CTFs, dynamic scoring is scoring is used to infer the difficulty of a challenge by the number of teams who are able to solve it. This scoring formula applies this concept to A/D.
In effect, each round is treated as a Jeopardy CTF with the following challenges:
- For each flag you capture, you receive ATK points based on the number of teams that capture that flag.
- For each service and each flagstore, you receive DEF points for each actively exploiting team that you did not get your flag captured by, proportional to the amount of teams whose flag that team did capture.
Additionally, you gain a fixed amount of SLA points for each flag available
from the retention period, as long as the checker status is SUCCESS or RECOVERING.
Checker Status
The checker returns one of the following results for each service:
SUCCESSif all flags could be successfully deployed and retrieved, and functionality checks were successful.MUMBLEif any checks for the current round failed.RECOVERINGif checks for the current round succeed, but flag from the retention period is missing.OFFLINEif it failed to establish a connection to the service.INTERNAL_ERRORif an internal error occurred. Please notify us with context in a ticket.
Implementation
The following is an edited snippet which calculates the final scoreboard from a series of rounds.
The full implementation can be play-tested using our simulator (PRs welcome!).
SCALING = 10
def jeopardy(x):
return int(SCALING * (30 / (29 + max(x, 1))) ** 3)
# Pre-compute victims for each service/flagstore/attacker,
# attributed back to the round in which the stolen flag was deployed
attacked_teams: typing.MutableMapping[
tuple[RoundId, ServiceName, FlagStoreId],
typing.MutableMapping[TeamName, set[TeamName]]
] = collections.defaultdict(lambda: collections.defaultdict(set))
for round_id, round_data in ctf.enumerate():
for team, team_data in round_data.items():
for flag_id in team_data.flags_captured:
flag = ctf.flags[flag_id]
if flag.owner == team or flag.owner == self.nop_team:
continue
attacked_teams[(flag.round_id, flag.service, flag.flagstore)][team].add(ctf.flags[flag_id].owner)
scoreboard: Scoreboard = collections.defaultdict(Score.default)
for round_id, round_data in ctf.enumerate():
# SLA flags:
# You gain a fixed SLA score for each flag available from the
# retention period, as long as the status is SUCCESS or RECOVERING.
for team, team_data in round_data.items():
sla = 0.0
for service, state in team_data.service_states.items():
flagstores = len(ctf.services[service].flagstores)
max_flags = ctf.config.flag_retention * flagstores
if state == ServiceState.SUCCESS:
present = max_flags
elif state == ServiceState.RECOVERING:
present = len(team_data.flags_retrieved[service])
else:
present = 0
flagstores = len(ctf.services[service].flagstores)
sla += SCALING * present / max_flags * flagstores
scoreboard[team] += Score.default(sla=sla)
# Attack flags:
# For each flag that is still valid, if you capture that flag
# you get points scaled by how many teams captured that flag.
for team, team_data in round_data.items():
attack = 0.0
for flag_id in team_data.flags_captured:
flag = ctf.flags[flag_id]
if flag.owner == team:
continue
attack += jeopardy(ctf.flag_captures[flag_id].count)
scoreboard[team] += Score.default(attack=attack)
# Estimate the number of playing teams
online = set()
for team, team_data in round_data.items():
if team != self.nop_team and any(s != ServiceState.OFFLINE for s in team_data.service_states):
online.add(team)
# Defense flags:
# For each flag that is still valid, for each attacking team,
# if you did not get exploited by that team,
# you get points scaled by the number of teams that that team did not exploit.
for service, flagstore in ctf.flagstores:
victims_of = attacked_teams[(round_id, service, flagstore)]
attackers = [team for team in ctf.teams if len(victims_of[team]) > 0]
for attacker in attackers:
if attacker == self.nop_team:
continue
max_victims = len(online - {attacker,})
not_exploited = max_victims - len(victims_of[attacker])
value = jeopardy(not_exploited) * max_victims / len(attackers)
for other, other_data in round_data.items():
if other == attacker or other in victims_of[attacker]:
continue
if other_data.service_states[service] not in (ServiceState.SUCCESS, ServiceState.RECOVERING):
continue
scoreboard[other] += Score.default(defense=value)
Review
- Since the capture count of each stored flag determines its worth, attackers are rewarded based on how difficult it is to exploit each specific team.
- The same goes for defense; a patch is rewarded based on the amount of other teams which were not able to defend against the exploiting team.
- Not attacking a team effectively gives that team defense points, thus there exists an additional incentive to attack as many teams as possible, beyond attack points. Teams will need to decide if the points gained from not attacking a team offset the expected loss of having the exploit stolen.
- Crucially, even though defending is more highly weighted than usual formulas,
it never earns a team more points than the exploiting team
receives from attacking (see
https://www.desmos.com/calculator/d6mceqjxa0 with variables (
v: victims,a: attackers,n: active teams).
Rules of Conduct
Be nice to each other. ECSC is a competitive event, but also an opportunity for teams to learn from each other and have fun. Players should help foster that environment.
The following rules outline behavior prohibited during the competition, beyond those defined by common law (and common sense):
- Sharing flags, challenge details or solutions to anyone outside your own team before the end of the competition is strictly prohibited.
- Giving or accepting assistance from anyone outside the own team and organizers for issues related to the competition is strictly prohibited.
- Gathering and / or taking advantage of insider information relating to the competition is prohibited.
- Attempting to elicit unintended behavior in any devices or services not designated as challenges for the competition running in the game network is strictly forbidden.
- Physically interacting or digitally tampering with the physical infrastructure provided by the organizers without permission is prohibited.
- Any action with the effect of creating excessive load for the contest or team infrastructure is prohibited, even if such actions are in the interest of the competition.
- If you are not sure if something is allowed or not, use the ticketing system to ask before doing it.
- Any unfair behavior with respect to the competition or the other players is forbidden, even if not explicitly described in the rules above; the organizers, the jury and any other relevant authority reserve the right to evaluate each case independently.
- Any action with the effect of intentionally making a service unavailable or non-functioning when interacted with by players or the checkers is prohibited.
- The deployment of fake flags is prohibited.
Please be aware that the entire game network traffic is logged, and will be accessed in case of a suspected rule violation.
Cheatsheet
A quick-reference for the most important information during the CTF.
| Key | Value |
|---|---|
| Platform | https://platform.ad.ecsc2025.pl |
| Public Scoreboard | https://scoreboard.ad.ecsc2025.pl |
| Internal Scoreboard | http://10.42.251.2 |
| Attack API | http://10.42.251.2:8080 |
| Game Network | 10.42.0.0/16 |
| Team Network | 10.42.X.0/24 |
| Team Cloud Network | 10.43.X.0/24 |
| Vulnbox IP | 10.42.X.2 (game network), 10.43.X.2 (team cloud network) |
| Gateway IP | 10.42.X.254 (game network), 10.43.X.254 (team cloud network) |
| Flag submission | nc 10.42.251.2 1337 |
Contact
Responsible Disclosure
Please send information about vulnerabilities you find in the platform directly to the organizers via the following address: ecsc2025+bugs@attacking-lab.com
If possible, encrypt your message with the key corresponding to the following fingerprint:1B83 6E52 A23C 80B1 28CB 53BF 69BD EDEF 0AEC 0D6C
Bug Bounty
We want to reward players that find and report bugs in our platform.
If you report a bug before the competition and help us triage, we will do our best to reward that show of sportsmanship. Prizes TBD.
Support
For support during the CTF, please open a ticket via the ticket bot in the ECSC 2025 Discord server.
In the unlikely case that Discord is unavailable, please send an email to the following address: ecsc2025+support@attacking-lab.com