NCAE Mapping Hub
Overview Scoreboard Data Roles Exercised Checklists Lessons Skill Drills Practice Terminal Progress

First 15 minutes of competition

Do this in order. Each check either prevents the highest-impact scoring failure or sets up a defense that pays off for the rest of the competition. The ordering is driven by regional point-loss data, not tradition.

of complete. 100 percent.
Progress saves in this browser only.
  1. 01 Capture baseline: user list, listening ports, running services
    Without a baseline you cannot detect red-team changes. Most teams skip this and spend the rest of the competition unable to tell what belongs and what does not.
    Why this matters
    Every subsequent defensive decision (who to lock out, which port to close, which service to kill) requires knowing what was there at t=0. Ten minutes from now the state will have diverged and you will have nothing to diff against.
    Walkthrough
    1. 1.Confirm which host you are on and who you are
      whoami; hostname; id
      Expect root\nteam<N>-ubuntu\nuid=0(root) gid=0(root) groups=0(root)
      Interpret: Makes sure you are on the right VM as root before you start writing files to /root.
    2. 2.Record every listening port and the process that owns it
      ss -tlnp > /root/baseline-ports.txt && cat /root/baseline-ports.txt
      Expect Entries for :22 (ssh), :53 (bind), :80 (apache), :445 (smbd), :5432 (postgres), plus anything competition-specific
      Interpret: If an unfamiliar port appears later, diff against this file. A port with no matching process = kernel-level listener, worth investigating.
    3. 3.Snapshot every running process as a tree
      ps auxf > /root/baseline-ps.txt && wc -l /root/baseline-ps.txt
      Expect A line count in the 100-200 range depending on image
      Interpret: The tree shows parent/child relationships. A suspicious process re-spawned by an unexpected parent is a red-team persistence signal.
    4. 4.List every running systemd unit
      systemctl list-units --type=service --state=running > /root/baseline-services.txt && wc -l /root/baseline-services.txt
      Expect Around 20-40 running units
      Interpret: New services appearing during the game should be scrutinized. systemd can spawn backdoors that look legitimate.
    5. 5.Copy the user database
      cp /etc/passwd /root/baseline-passwd.txt && cp /etc/shadow /root/baseline-shadow.txt && chmod 600 /root/baseline-shadow.*
      Expect Two files in /root. chmod 600 keeps shadow secret.
      Interpret: Any new UID-0 entry added later = root compromise. Match against baseline via `diff`.
    DCWF work roles exercised by this step
  2. 02 Back up every critical config file into a dated tarball
    Golden snapshot is your recovery anchor. Use tar so the entire tree can be restored with a single command.
    Why this matters
    Red team wins by editing a config in a place you do not think to look. If you have a known-good tarball, `tar -xzf` puts it back in 200ms. Without one, you are re-learning your own stack under time pressure.
    Walkthrough
    1. 1.Create the tarball with a timestamp in the filename
      tar -czf /root/golden-configs-$(date +%Y%m%d-%H%M).tar.gz /etc/ssh /etc/samba /etc/bind /etc/apache2 /etc/postgresql /etc/passwd /etc/shadow /etc/group /etc/sudoers
      Expect No errors. tar may warn about absolute paths. that is fine.
      Interpret: Timestamped filename lets you take another snapshot after hardening without overwriting this one.
    2. 2.Verify the tarball opens and lists the expected config files
      tar -tzf /root/golden-configs-*.tar.gz | head -30
      Expect Lines under etc/ssh/, etc/samba/, etc/bind/, etc/apache2/, etc/postgresql/, plus the individual files
      Interpret: If sshd_config or smb.conf is missing, your path assumption was wrong. Re-run tar including the correct directory.
    3. 3.Copy the tarball off-host if you can
      scp /root/golden-configs-*.tar.gz admin@<your-laptop-IP>:/tmp/
      Expect One file transferred
      Interpret: If red team deletes /root, your off-host copy saves the round. Skip this step if scp is not reachable and rely on local.
    DCWF work roles exercised by this step
  3. 03 Verify MikroTik router NAT rules
    DNS EXT FWD cost teams 15,977 points at regional and root cause is almost always a missing NAT. Seven rules unblock every external service.
    Why this matters
    The scoring engine reaches you through the router's public IP. If a port is not forwarded, the service behind it could be perfect and still score zero. This is the single highest-leverage check in the first fifteen minutes.
    Walkthrough
    1. 1.SSH to the router
      ssh admin@<router-IP>
      Expect RouterOS banner and a `[admin@router]>` prompt
      Interpret: If the SSH fails, the router is either down or its SSH port is blocked. Fix connectivity before trying to read rules.
    2. 2.Print every dst-nat rule
      /ip firewall nat print where action=dst-nat
      Expect Rows for TCP 22, TCP 80, TCP 443, TCP 445, TCP 5432, TCP 53, UDP 53
      Interpret: Any missing port = that service scores zero from outside. UDP 53 and TCP 53 must both exist for DNS.
    3. 3.Add a missing rule (example: DNS UDP)
      /ip firewall nat add chain=dstnat protocol=udp dst-port=53 action=dst-nat to-addresses=192.168.<N>.12 to-ports=53
      Expect No error. Re-running the print command shows the new rule
      Interpret: Replace <N> with your team number and use the DNS VM's internal IP. Repeat for any other missing port.
    4. 4.Test from outside the network (laptop with cellular hotspot)
      dig @<router-public-IP> team<N>.ncaecybergames.org +short
      Expect The IP address your BIND returns
      Interpret: No output means the router is still blocking or BIND is not answering. Fix the router side first.
    DCWF work roles exercised by this step
  4. 04 Confirm SMB Login works for all scoring users (3x weight, highest-value service)
    Team 13 lost more points on SMB Login than any other service at regional. 4.17 pts/check times 360 checks = up to 1500 pts over six hours.
    Why this matters
    SMB Login's weight multiplier is 3. A single SMB outage is worth more than three DNS outages. Fixing login before any other SMB issue is strictly correct from a scoring standpoint.
    Walkthrough
    1. 1.Confirm smbd is running
      systemctl is-active smbd
      Expect active
      Interpret: If inactive, `systemctl start smbd`. If start fails, `journalctl -u smbd -n 50` to see why.
    2. 2.Verify each scoring user can log in locally
      for u in keons henri_cartan listo nills reisdro lisdn; do echo "=== $u ==="; smbclient -L //localhost -U $u%<password> -c 'exit' 2>&1 | head -3; done
      Expect Each user block shows 'Anonymous login successful' or a share listing without NT_STATUS errors
      Interpret: NT_STATUS_LOGON_FAILURE = password wrong. NT_STATUS_CONNECTION_REFUSED = smbd not listening. Fix whichever appears.
    3. 3.Rebuild any missing user's SMB password
      smbpasswd -a <username>
      Expect Prompts for new password twice, then 'Added user <username>'
      Interpret: Only needed if step 2 showed LOGON_FAILURE. Keep the competition-supplied password if you know it.
    DCWF work roles exercised by this step
  5. 05 Create the addict_with_a_pen.data file on the SMB share
    No team at regional scored any SMB Read uptime. This one-line fix is free points no one else captured.
    Why this matters
    SMB Read's check does one thing: open the share, stat this specific file. Missing file = 100% failure. Thirty seconds of typing buys you a service nobody else will have green.
    Walkthrough
    1. 1.Resolve the share's filesystem path
      testparm -s 2>/dev/null | awk '/^\[files\]/,/^\[/' | awk '/path = /{print $3; exit}'
      Expect A filesystem path like /srv/samba/files or /home/samba/share
      Interpret: If blank, the share is named differently. `testparm -s` with no filter shows every share section.
    2. 2.Create the file with world-readable permissions
      SHARE=$(testparm -s 2>/dev/null | awk '/^\[files\]/,/^\[/' | awk '/path = /{print $3; exit}'); touch "$SHARE/addict_with_a_pen.data" && chmod 644 "$SHARE/addict_with_a_pen.data" && chown nobody:nogroup "$SHARE/addict_with_a_pen.data"
      Expect No output
      Interpret: World-readable so any Samba user can stat the file. Owner nobody:nogroup matches the typical anonymous share fallback.
    3. 3.Verify via smbclient
      smbclient //localhost/files -U keons%<password> -c 'ls addict_with_a_pen.data'
      Expect A line showing the file with size and a timestamp
      Interpret: NT_STATUS_OBJECT_NAME_NOT_FOUND means wrong share path. NT_STATUS_ACCESS_DENIED means permissions are too tight.
    DCWF work roles exercised by this step
  6. 06 Preserve Postgres torch_bearer credentials (do NOT rotate)
    Scoring engine uses the literal password 'torch_of_the_light!'. Rotating it breaks Postgres Access for the rest of the round.
    Why this matters
    This is the one password you must NOT change during hardening. Every other user rotation is safe. torch_bearer is scoring-engine-facing and the literal credential is baked into the check.
    Walkthrough
    1. 1.Set the canonical password (idempotent)
      sudo -u postgres psql -c "ALTER USER torch_bearer WITH PASSWORD 'torch_of_the_light!';"
      Expect ALTER ROLE
      Interpret: Safe to run even if it is already correct. Prevents accidental mid-game rotation.
    2. 2.List roles to confirm the user exists
      sudo -u postgres psql -c '\du'
      Expect A row for torch_bearer
      Interpret: If missing, CREATE ROLE torch_bearer WITH LOGIN PASSWORD 'torch_of_the_light!'; then re-run.
    3. 3.Confirm the 'db' database exists and torch_bearer can access it
      sudo -u postgres psql -c '\l' | grep -E 'db|torch_bearer'
      Expect A row showing database 'db' with torch_bearer as owner or with GRANT
      Interpret: If 'db' is absent: `sudo -u postgres createdb -O torch_bearer db`.
    DCWF work roles exercised by this step
  7. 07 Verify web app admin:admin123 login succeeds
    Scoring engine logs in as admin/admin123. If that fails, WWW Content fails. 949 failure events at regional traced to this.
    Why this matters
    Web teams default-harden by changing admin passwords. If the scoring engine's credential is one of the rotated passwords, the service scores red for the whole round. Check the exact credential before hardening the user table.
    Walkthrough
    1. 1.Find the login endpoint
      curl -sI http://localhost/login http://localhost/admin http://localhost/wp-login.php 2>&1 | grep -E 'HTTP|Location'
      Expect At least one non-404 response
      Interpret: Whichever URL returns 200 or 302 is the real login path. 404 on all means check the app's routes file.
    2. 2.Submit the expected credentials
      curl -s -c /tmp/c.txt -d 'username=admin&password=admin123' http://localhost/login -o /dev/null -w 'HTTP %{http_code}\n'
      Expect HTTP 200 or HTTP 302
      Interpret: HTTP 401/403 = credentials rejected. HTTP 500 = app crash on login. 302 with Location header is normal login success.
    3. 3.If login failed, reset the admin password in-place
      # For WordPress: wp user update admin --user_pass=admin123 --path=/var/www/html --allow-root # For raw SQL-backed app: UPDATE users SET password_hash = crypt('admin123', gen_salt('bf')) WHERE username='admin';
      Expect Update returns success (Success / UPDATE 1)
      Interpret: Only reset if step 2 failed. Do not rotate away from admin123 at any point during the competition.
    DCWF work roles exercised by this step
  8. 08 Regenerate expired or mismatched SSL certificate
    501 'SSL record layer failure' plus 255 'CERTIFICATE_VERIFY_FAILED' events at regional. A self-signed cert with the correct CN is sufficient.
    Why this matters
    The scoring engine does not require a real CA-signed chain. It just needs a TLS handshake that completes and a certificate CN that matches the hostname the scorer is asking for.
    Walkthrough
    1. 1.Generate a self-signed cert for your team hostname
      openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/server.key -out /etc/ssl/certs/server.crt -subj "/CN=team${TEAM_NUM}.ncaecybergames.org" -addext "subjectAltName=DNS:team${TEAM_NUM}.ncaecybergames.org"
      Expect '-----' output indicating key generation then 'Writing new private key' then no errors
      Interpret: Export TEAM_NUM first (e.g. export TEAM_NUM=10). The SAN extension is what modern TLS clients actually validate; CN alone is often rejected.
    2. 2.Restrict the private key
      chmod 600 /etc/ssl/private/server.key
      Expect No output
      Interpret: Apache runs as a non-root user and needs to read this. If Apache refuses to start, add group access: chmod 640 and chgrp ssl-cert.
    3. 3.Reload Apache and verify the cert is live
      systemctl reload apache2 && echo | openssl s_client -connect localhost:443 -servername team${TEAM_NUM}.ncaecybergames.org 2>/dev/null | openssl x509 -noout -subject -dates
      Expect subject= /CN=team<N>.ncaecybergames.org and a notAfter ~1 year in the future
      Interpret: Subject mismatch = wrong cert is being served. Check the VirtualHost's SSLCertificateFile path.
    DCWF work roles exercised by this step
  9. 09 Hunt for pre-planted persistence mechanisms on the competition image
    Competition images ship with planted backdoors. Sweep the standard hiding places before red team starts adding new ones.
    Why this matters
    Any UID-0 user you miss is root access for red team without them spending an exploit. Any cron entry you miss is a persistence channel that survives reboots. This sweep is twelve minutes that pays back the whole round.
    Walkthrough
    1. 1.Find every UID-0 account
      awk -F: '$3==0 {print}' /etc/passwd
      Expect Only 'root:x:0:0:root:/root:/bin/bash'
      Interpret: Any other line is a root-equivalent user. Remove with `userdel -r <name>` after confirming it is not scoring-required.
    2. 2.Find sudoers entries beyond the standard
      cat /etc/sudoers /etc/sudoers.d/* 2>/dev/null | grep -vE '^\s*(#|$)|^Defaults'
      Expect Typically just 'root ALL=(ALL:ALL) ALL' and the %sudo group line
      Interpret: Extra NOPASSWD rules are the most common persistence. Remove suspicious rules and re-run the scan.
    3. 3.Scan every user's cron for suspicious entries
      for u in $(cut -d: -f1 /etc/passwd); do crontab -u $u -l 2>/dev/null | grep -vE '^\s*(#|$)' | sed "s/^/[$u] /"; done; cat /etc/cron.d/* /etc/cron.daily/* 2>/dev/null
      Expect Empty or only known-legitimate entries
      Interpret: Any unknown job pulling from a network URL is a callback. Remove the line, note the source URL for later analysis.
    4. 4.Check every SSH authorized_keys for extra keys
      for h in /root /home/*; do echo "=== $h ==="; cat $h/.ssh/authorized_keys 2>/dev/null; done
      Expect Only expected scoring-engine keys and your team's keys
      Interpret: An unfamiliar key = someone can SSH as that user. Remove it only if you are sure it is not a scoring key.
    5. 5.Inspect systemd for new unit files
      systemctl list-unit-files --state=enabled | head -40
      Expect A familiar list from baseline
      Interpret: Unknown enabled units are persistence. Inspect with `systemctl cat <unit>` before disabling.
    6. 6.List every package-unowned SUID binary
      find / -xdev -perm -4000 -type f 2>/dev/null | xargs -I{} sh -c 'dpkg -S {} 2>/dev/null || echo UNOWNED: {}'
      Expect Every SUID binary is dpkg-owned
      Interpret: UNOWNED binaries are most likely hand-placed. Inspect, then remove or chmod -s.
    DCWF work roles exercised by this step
  10. 10 Install the 60-second auto-restore cron
    Red team edits a config. 60 seconds later, your golden tarball restores it. Most red-team ops have a shorter window of actual impact than they expect.
    Why this matters
    Restore-every-minute converts one-shot red-team edits into time-limited noise. If they want to persist, they have to either disable your restore or survive getting overwritten repeatedly. Both are expensive for them and cheap for you.
    Walkthrough
    1. 1.Write the restore script to /root/restore.sh
      cat > /root/restore.sh <<'EOF' #!/bin/bash tar -xzf /root/golden-configs-*.tar.gz -C / 2>/dev/null systemctl reload-or-restart sshd smbd named apache2 postgresql 2>/dev/null EOF
      Expect No output
      Interpret: Uses reload-or-restart so services only fully restart if reload is not supported. Minimizes visible downtime.
    2. 2.Make it executable
      chmod +x /root/restore.sh
      Expect No output
      Interpret: Without +x, cron will silently skip. Verify with `ls -l /root/restore.sh` showing -rwx.
    3. 3.Install the cron entry (every minute)
      (crontab -l 2>/dev/null; echo '* * * * * /root/restore.sh') | crontab -
      Expect No output
      Interpret: The subshell preserves any existing entries. Rerunning is idempotent only if you de-dupe afterward.
    4. 4.Verify the cron is registered
      crontab -l
      Expect The line '* * * * * /root/restore.sh' is present
      Interpret: Absent? Re-run step 3. Duplicate lines? Clean with `crontab -e` and remove dupes.
    5. 5.Prove the restore loop works by breaking a config and waiting
      echo '# red team edit' >> /etc/ssh/sshd_config; sleep 65; tail -3 /etc/ssh/sshd_config
      Expect The '# red team edit' line is gone after 65 seconds
      Interpret: Still present? Either the cron did not fire (check `grep CRON /var/log/syslog`) or the tar did not include sshd_config.
    DCWF work roles exercised by this step
  11. 11 Rotate all user passwords except scoring-engine accounts
    Red team has the competition image and knows every default credential. Rotate them. carefully. Do NOT change torch_bearer's Postgres password.
    Why this matters
    Default passwords are the cheapest possible foothold. Rotating them costs you five minutes. Not rotating them lets red team log in without spending any skill.
    Walkthrough
    1. 1.List non-system users with login shells
      awk -F: '$3>=1000 && $7 ~ /(bash|sh|zsh)/ {print $1}' /etc/passwd
      Expect Your real user accounts (excluding nobody, daemon, etc.)
      Interpret: Scoring users (simone_weil, nills, todd_k, vetomo, claude_chevalley, keons, henri_cartan, listo, reisdro, lisdn) must stay. Everyone else is a rotation candidate.
    2. 2.Rotate one user (interactive)
      passwd <username>
      Expect Prompts twice for new password. 'passwd: password updated successfully'
      Interpret: Repeat for each non-scoring user. Pick a strong password and write it down on team paper, not digitally.
    3. 3.Rotate SMB passwords where they are separate
      smbpasswd <username>
      Expect Prompts for old and new password
      Interpret: Only necessary for users the scoring engine authenticates via Samba. Keep Samba users' passwords matching their Linux users for sanity.
    DCWF work roles exercised by this step
  12. 12 Verify scoring-engine SSH keys are preserved in authorized_keys
    If you wiped authorized_keys during hardening, SSH scoring is permanently broken until you restore. Compare against your backup first.
    Why this matters
    SSH is a scored service. The scoring engine authenticates with a public key baked into the competition image. Removing that key without restoring it scores zero for the rest of the round.
    Walkthrough
    1. 1.Inspect each scoring user's authorized_keys
      for u in simone_weil nills todd_k vetomo claude_chevalley; do echo "--- $u ---"; cat /home/$u/.ssh/authorized_keys 2>/dev/null | head -5; done
      Expect For each user, at least one ssh-rsa or ssh-ed25519 key
      Interpret: Empty or missing = SSH will fail for that user. Restore from the golden tarball if it contained /home (it usually does not).
    2. 2.Confirm file permissions are sane
      for u in simone_weil nills todd_k vetomo claude_chevalley; do stat -c '%a %U:%G %n' /home/$u/.ssh/authorized_keys 2>/dev/null; done
      Expect Mode 600, owned by the user
      Interpret: SSH refuses to use a mode-644 authorized_keys. `chmod 600 /home/$u/.ssh/authorized_keys && chown $u:$u /home/$u/.ssh/authorized_keys`.
    3. 3.Test key-based login from localhost as the scoring user's key
      ssh -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa simone_weil@localhost 'whoami'
      Expect simone_weil
      Interpret: Permission denied = key not accepted. Fallback: Password? step. Key-based login failing is the clearest signal that authorized_keys drift happened.
    DCWF work roles exercised by this step