The web SSH terminal and the credential API reported `ctfuser` for all 16
challenges, but only the 6 native GEMASTIK XVIII images provision ctfuser.
Every imported XVI/XVII image does `RUN echo root:${PASSWORD} | chpasswd`,
so 10 of 16 participant logins were refused with "Permission denied".
Root causes (all the same class of bug - login hardcoded in the wrong layer):
- main.py websocket ssh handler read st["ssh_user"], a single team-wide value
defaulting to ctfuser, instead of the per-challenge registry field
- /api/credential proxied the global receiver on :18080, which only knows the
6 native challenges, so the other 10 returned "Invalid challenge"
- team.html hardcoded the challenge picker to those same 6 challenges, making
the other 10 unreachable from the terminal entirely
- index.html rendered `<b>ctfuser</b>` and a stale hardcoded SSH port table
Fixes:
- orch.challenge_credential()/all_teams() read the TEAM's state.json, which
holds the same per-challenge password the panel chpasswds
- gen_receiver_services.py injects SSH_USER_<port> from the registry so the
receiver's /credential endpoint agrees with the panel
- receiver Challenge.credentials() honours SSH_USER_<port> (ctfuser fallback)
- new /api/team/{idx}/own-challenges feeds the picker; targets now carry
challenge + ssh_user
- UI takes user and port from the server instead of hardcoding them
Verified: 32/32 credential payloads correct across teams 1-2, and 32/32 real
paramiko SSH logins succeed with whoami confirming the expected account.
Also adds bulk team delete: POST /api/teams/bulk-delete runs one background
thread and is polled via GET /api/teams/bulk-delete/{job_id}, plus per-team
checkboxes with select-all/clear in the UI. Deletion must stay sequential
because delete_team() regenerates shared artifacts at the end.
59 lines
2.3 KiB
Bash
59 lines
2.3 KiB
Bash
#!/usr/bin/env bash
|
|
# Bulk-delete regression test: create two scratch teams, then delete BOTH in a
|
|
# single API call and poll the job until it settles.
|
|
#
|
|
# Proves the endpoint exists, validates input, runs teams sequentially inside
|
|
# one background job, and leaves the real teams untouched.
|
|
set -uo pipefail
|
|
BASE=/opt/gemastik18-final
|
|
cd "$BASE"
|
|
CJ=/tmp/bulk_cj
|
|
|
|
U=$(grep -oP '^PANEL_ADMIN_USER=\K.*' panel/.env)
|
|
P=$(grep -oP '^PANEL_ADMIN_PASS=\K.*' panel/.env)
|
|
curl -sS -c "$CJ" -X POST -H 'Content-Type: application/json' \
|
|
-d "{\"user\":\"$U\",\"pass\":\"$P\"}" http://127.0.0.1:18081/api/login -o /dev/null
|
|
|
|
echo "=== validation ==="
|
|
printf " empty list -> "
|
|
curl -sS -b "$CJ" -X POST -H 'Content-Type: application/json' -d '{"indices":[]}' \
|
|
http://127.0.0.1:18081/api/teams/bulk-delete -w ' [%{http_code}]\n'
|
|
printf " missing tms -> "
|
|
curl -sS -b "$CJ" -X POST -H 'Content-Type: application/json' -d '{"indices":[99,98]}' \
|
|
http://127.0.0.1:18081/api/teams/bulk-delete -w ' [%{http_code}]\n'
|
|
|
|
echo "=== BEFORE ==="
|
|
for i in 5 6; do
|
|
printf " team%s dir=%s\n" "$i" "$([ -d "teams/team$i" ] && echo present || echo GONE)"
|
|
done
|
|
|
|
echo "=== BULK DELETE [5,6] ==="
|
|
S=$(date +%s)
|
|
curl -sS -b "$CJ" -X POST -H 'Content-Type: application/json' \
|
|
-d '{"indices":[5,6],"purge_scores":true}' \
|
|
http://127.0.0.1:18081/api/teams/bulk-delete -o /tmp/bulk.json -w ' HTTP %{http_code}\n'
|
|
cat /tmp/bulk.json; echo
|
|
JOB=$(python3 -c "import json;print(json.load(open('/tmp/bulk.json'))['job'])")
|
|
echo " job: $JOB"
|
|
|
|
while :; do
|
|
curl -sS -b "$CJ" "http://127.0.0.1:18081/api/teams/bulk-delete/$JOB" -o /tmp/bulkjob.json
|
|
read -r ST DET <<<"$(python3 -c "
|
|
import json;d=json.load(open('/tmp/bulkjob.json'));print(d['state'],d.get('detail',''))")"
|
|
echo " [$(( $(date +%s) - S ))s] $ST — $DET"
|
|
[ "$ST" != "running" ] && break
|
|
sleep 15
|
|
done
|
|
echo " elapsed: $(( $(date +%s) - S ))s"
|
|
python3 -c "
|
|
import json;d=json.load(open('/tmp/bulkjob.json'))
|
|
print(' state:',d['state'],' errors:',d.get('errors'))
|
|
for x in d.get('done',[]): print(' deleted team',x['team'],x['label'],'->',x['steps'])"
|
|
|
|
echo "=== AFTER ==="
|
|
for i in 5 6; do
|
|
printf " team%s dir=%s\n" "$i" "$([ -d "teams/team$i" ] && echo present || echo GONE)"
|
|
done
|
|
echo " real teams: $(curl -sS -b "$CJ" http://127.0.0.1:18081/api/teams \
|
|
| python3 -c "import json,sys;print([t['index'] for t in json.load(sys.stdin)['teams']])")"
|