Skip to content

UMassCybersecurity/UMassCTF26-Instancer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

75 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UMassCTF26 Instancer

Prerequisites

1. Run Traefik

sudo docker network create -d bridge traefik-network

sudo docker run -d -p 80:80 -p 8080:8080 -v $PWD/traefik.yaml:/etc/traefik/traefik.yml -v /var/run/docker.sock:/var/run/docker.sock --name traefik --network traefik-network traefik:v3.6

2. Configure Docker Subnet Allocation

Each challenge instance creates multiple Docker networks, and Docker's default /16 allocation quickly runs out of address space. Configure the Docker daemon to use smaller subnets by adding the following to /etc/docker/daemon.json (create the file if it doesn't exist):

{
  "default-address-pools": [
    { "base": "10.0.0.0/8", "size": 24 }
  ]
}

Then restart the Docker daemon:

sudo systemctl restart docker

This carves 10.0.0.0/8 into /24 subnets (65,536 available), supporting ~32,000+ simultaneous team instances.

3. Open Firewall Ports on GCP

Create firewall rules to allow external traffic to the VM:

  • Port 80 (and 443 if using HTTPS): Traefik — routes traffic to challenge containers
  • Port 5000: Instancer web app — login, deploy UI, websockets
gcloud compute firewall-rules create allow-traefik \
  --allow tcp:80,tcp:443 \
  --target-tags=instancer \
  --description="Allow HTTP/HTTPS traffic to Traefik"

gcloud compute firewall-rules create allow-instancer \
  --allow tcp:5000 \
  --target-tags=instancer \
  --description="Allow traffic to instancer web app"

Make sure your VM has the instancer network tag applied.

4. Create DNS Records

Create two DNS A records pointing to the VM's static external IP:

  • <BASE_CHALLENGE_DOMAIN> (e.g. bonk4cash.test.umasscybersec.org)
  • *.<BASE_CHALLENGE_DOMAIN> (e.g. *.bonk4cash.test.umasscybersec.org)

The wildcard record is required so that each team's instance subdomain (e.g. <team_uuid>.bonk4cash.test.umasscybersec.org) resolves to the VM where Traefik routes it to the correct container.

Spin Up Challenge

1. Install uv and tmux

2. Clone and enter the repo

cd UMassCTF26-Instancer

3. Configure environment variables

touch .env

Specify environment variables in .env:

# EXAMPLE
SECRET_KEY=your_secret_key_here
CTFD_BASE_URL=http://34.28.81.490
CTFD_BASE_URL_2=http://34.28.81.491
TRAEFIK_CONTAINER_NAME=traefik
CHALLENGE_NAME=bonk4cash
BASE_CHALLENGE_DOMAIN=bonk4cash.test.umasscybersec.org
CHALLENGE_TEMPLATE_DIR=/home/umasscybersec_gmail_com/UMass26-Instancer/tests/umassctf25-test/bonk4cash

LOG_LEVEL=INFO                              # optional, default: INFO
COLOR_THEME=orange                          # optional, default: orange (options: orange, blue, green, purple)
INSTANCE_DURATION=30                        # optional, default: 30 (minutes)
INSTANCER_BACKEND=traefik                   # optional, default: test (options: test, traefik)
INSTANCES_DIR=~/instances                   # optional, default: ~/instances
INSTANCE_URL_SCHEME=https                   # optional, default: http (set to https if Traefik terminates TLS)
SIMULATE_COMMAND_DELAY_FOR_TESTING=0        # optional, default: 0 (seconds, for testing only)

Generate a secret key:

python3 -c "import secrets; print(secrets.token_hex(32))"

4. Modify challenge's docker compose file for Traefik

The instancer validates the compose file on every deploy. Your challenge's docker-compose.yml must satisfy all of the following:

  1. No external port mappings. Services must not have ports with a published value. Traefik handles all external routing.

  2. Traefik router label. At least one service must have a label matching:

    traefik.http.routers.${TEAM_UUID}.rule=Host(`${TEAM_UUID}.${BASE_CHALLENGE_DOMAIN}`)
    
  3. Traefik port label. The same service must also have:

    traefik.http.services.${TEAM_UUID}.loadbalancer.server.port=<your_app_port>
    
  4. Router and port labels must appear together. A service cannot have one without the other.

  5. Multi-network services need traefik.docker.network. If a service with Traefik labels is on multiple networks, it must specify which network Traefik should use via the traefik.docker.network label.

  6. Bind mounts must be inside the team instance directory. Any bind type volume must have a source path within the team's instance directory. Named volumes are fine (they are scoped to the compose project).

TEAM_UUID is a unique identifier generated per deploy. Use the ${TEAM_UUID} environment variable in your compose file — the instancer passes it automatically.

Important: Labels must use list format, not map format. Docker compose only substitutes variables in YAML values, not in map keys. Use list format so ${TEAM_UUID} in label keys gets resolved:

# WRONG — map format, ${TEAM_UUID} in keys won't be substituted
labels:
  traefik.http.routers.${TEAM_UUID}.rule: "Host(...)"

# CORRECT — list format, entire string is a value, variables are substituted everywhere
labels:
  - "traefik.http.routers.${TEAM_UUID}.rule=Host(...)"

Example docker-compose.yml:

services:
  web:
    image: my-challenge:latest
    networks:
      - public
      - internal
    labels:
      traefik.enable: "true"
      traefik.http.routers.${TEAM_UUID}.rule: "Host(`${TEAM_UUID}.${BASE_CHALLENGE_DOMAIN}`)"
      traefik.http.services.${TEAM_UUID}.loadbalancer.server.port: "8080"
      traefik.docker.network: public   # required when service is on multiple networks
    # no 'ports:' — traefik handles external routing

  db:
    image: postgres:16
    networks:
      - internal
    volumes:
      - db-data:/var/lib/postgresql/data   # named volume (ok)

networks:
  public:
  internal:
    internal: true   # no external access, only inter-service communication

volumes:
  db-data:

5. Pre-build challenge images

The instancer does not build images at deploy time. Pre-build them:

cd /path/to/challenge-template
sudo docker compose up --no-start

6. Start the instancer

Start the instancer inside a tmux session so you can detach, reattach to view live logs at any time, and the process keeps running even if you disconnect from SSH:

tmux new -s instancer
./run.sh 2>&1 | tee instancer.log
  • ./run.sh 2>&1 — runs the instancer, merging stderr into stdout
  • | tee instancer.log — prints output to the terminal AND saves it to instancer.log
  • Ctrl+B, D — detach from the tmux session (process keeps running)
  • tmux attach -t instancer — reattach to view live output
  • instancer.log — always available for reviewing past logs, even outside tmux

7. Run the solve script

Verify that Traefik is not making unintended modifications to the request.

Traefik routes requests like this:

client request -> EntryPoint -> Router -> Middleware -> Service -> backend server

Traefik components:

  • Entrypoint: accepts incoming traffic on specific ports (e.g. :80)
  • Router: inspects incoming requests and decides which service to send them to using rules to match patterns (host, path, headers, etc.)
  • Middleware: transforms or filters requests/responses before they reach the app (e.g. authentication, rate limiting, redirects, header modification)
  • Service: final destination (tells Traefik where to forward the request), also handles load balancing

EntryPoint request modifications (based on Traefik docs):

Path Modifications:

  • sanitizePath — normalizes path traversal sequences (/./, /../, //) into clean paths. On by default.
  • encodeQuerySemicolons — encodes semicolons in query strings so they aren't treated as parameter separators
  • Encoded characters — optionally blocks or allows encoded characters like %2f, %00, %5c, etc. in the path

Header Modifications:

  • Connection header cleanup — per RFC 7230, strips any headers listed in the Connection: header, and removes the Connection header itself when empty. This happens before middleware sees the request.
  • forwardedHeaders — either trusts, rewrites, or strips X-Forwarded-* headers depending on whether the source IP is in trustedIPs

Protocol Upgrades / Rewrites:

  • TLS termination — if http.tls is configured, decrypts the incoming TLS connection
  • HTTP/3 upgrade — handles the TCP to UDP upgrade for HTTP/3 connections
  • http.redirections — issues an HTTP redirect (e.g. HTTP to HTTPS) before the request reaches a router

Connection-level:

  • PROXY protocol — if enabled, reads the real client IP from the PROXY protocol header and replaces the connection's remote address
  • Timeout enforcement — applies readTimeout, writeTimeout, idleTimeout at the connection level
  • Max header size — enforces maxHeaderBytes, rejecting requests with oversized headers

Middleware injection:

  • http.middlewares — prepends a list of middlewares to every router on this EntryPoint, running globally before any router-specific middleware

Modify traefik.yaml as necessary so that these modifications do not interfere with the challenge.

Notes

  • The validate_compose_file function checks that any bind mount volumes are inside the team's instance directory; however, named volumes are not restricted (they are managed by Docker and scoped to the compose project). Check this manually per challenge.

Manual Clean Up

If you stop the instancer during the middle of the competition:

TraefikInstancer.__init__ automatically wipes INSTANCES_DIR on startup.

1. Kill all team containers + networks (disconnect them from traefik)

sudo docker compose ls --filter "name=team" -q | xargs -I{} sudo docker compose -p {} down -v
docker network ls --filter "name=team" -q | xargs -I{} sh -c 'docker network inspect {} -f "{{range .Containers}}{{.Name}} {{end}}" | xargs -n1 docker network disconnect {} 2>/dev/null; docker network rm {}'


for net in $(sudo docker network ls --filter name=-external -q); do
  for c in $(sudo docker network inspect -f '{{range .Containers}}{{.Name}} {{end}}' "$net"); do
    sudo docker network disconnect -f "$net" "$c"
  done
  sudo docker network rm "$net"
done

2. Delete the database

It will be recreated on startup:

rm instance/instancer.sqlite

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages