From 5c801b2629e79ee300dfa19cc25102e623fd761e Mon Sep 17 00:00:00 2001 From: Gwyneth Pena-Siguenza Date: Thu, 24 Sep 2026 14:34:27 -0700 Subject: [PATCH] Clarify challenge 10 upload target and test with real scp Learners who scp to ~/ instead of ~/ctf_challenges/ never trigger the flag, while the hint pointed them to create a file locally instead. - README and hint now say to upload a new file from your own computer into ~/ctf_challenges/ (overwrites don't count) - Monitor ignores editor temp files and debounces to one banner per upload - Test harness triggers challenge 10 with scp from the local machine instead of touching a file on the VM Fixes #127 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8bedb36e-e2a4-4e1d-be3b-922b69a777a9 --- .github/skills/ctf-testing/deploy_and_test.sh | 24 +++++++++++++++++++ .../skills/ctf-testing/test_ctf_challenges.sh | 24 ++++++------------- README.md | 2 +- setup/challenges/ch10_remote_upload.py | 13 +++++++++- verify/src/verify/commands.py | 2 +- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/.github/skills/ctf-testing/deploy_and_test.sh b/.github/skills/ctf-testing/deploy_and_test.sh index f5f64ca..cfc5e1f 100755 --- a/.github/skills/ctf-testing/deploy_and_test.sh +++ b/.github/skills/ctf-testing/deploy_and_test.sh @@ -475,6 +475,29 @@ _copy_test_script() { _sshpass_cmd scp ${SSH_OPTS} "${TEST_SCRIPT}" "${SSH_USER}@${ip}:/tmp/test_ctf_challenges.sh" } +# Trigger challenge 10 the way learners do: scp a new file from this machine +# into ~/ctf_challenges. test_ctf_challenges.sh then checks the flag was set. +# Arguments: +# $1 - IP address of the VM +_upload_challenge_10_file() { + local ip="$1" + local upload_file + + _log INFO "Uploading challenge 10 test file via scp..." + # Monitor waits for the setup marker plus a delay before inotifywait starts + # shellcheck disable=SC2086 + _sshpass_cmd ssh ${SSH_OPTS} "${SSH_USER}@${ip}" \ + 'for _ in $(seq 1 30); do pgrep -x inotifywait >/dev/null && break; sleep 2; done + : > /tmp/.ctf_upload_triggered; rm -f ~/ctf_challenges/scp_upload_test' || true + + upload_file=$(mktemp) + echo "challenge 10 scp upload test" > "${upload_file}" + # shellcheck disable=SC2086 + _sshpass_cmd scp ${SSH_OPTS} "${upload_file}" "${SSH_USER}@${ip}:~/ctf_challenges/scp_upload_test" \ + || _log WARN "scp upload for challenge 10 failed" + rm -f "${upload_file}" +} + # Copy test script to VM and execute it # Arguments: # $1 - Cloud provider name @@ -491,6 +514,7 @@ _run_tests() { fi _copy_test_script "${provider}" "${ip}" + _upload_challenge_10_file "${ip}" _log INFO "Running tests on ${provider} VM (${ip})..." echo "" diff --git a/.github/skills/ctf-testing/test_ctf_challenges.sh b/.github/skills/ctf-testing/test_ctf_challenges.sh index dbd465d..a51c22c 100644 --- a/.github/skills/ctf-testing/test_ctf_challenges.sh +++ b/.github/skills/ctf-testing/test_ctf_challenges.sh @@ -408,37 +408,27 @@ else FLAGS[9]="" fi -# Challenge 10: File Monitoring -# Hint: "Try creating a file in ctf_challenges" -echo "Challenge 10: File Monitoring" +# Challenge 10: Remote Upload +# Hint: "Run scp from your own computer into ~/ctf_challenges/" +# deploy_and_test.sh uploads a file with scp from the local machine before this +# script runs, so the flag should already be in the trigger file. +echo "Challenge 10: Remote Upload" if ! systemctl is-active ctf-monitor-directory.service &>/dev/null; then _fail "Challenge 10: Monitor service not running - SETUP BUG" FLAGS[10]="" else - # Wait for inotifywait process to actually be running (service starts but has internal delay) - echo " Waiting for inotifywait to be ready..." - for _ in {1..15}; do - pgrep -f "inotifywait.*ctf_challenges" &>/dev/null && break - sleep 2 - done - - true > /tmp/.ctf_upload_triggered 2>/dev/null || true - TRIGGER="/home/ctf_user/ctf_challenges/test_$$" - touch "${TRIGGER}" - sleep 3 - FLAG_10="" for _ in {1..10}; do FLAG_10=$(grep -ao 'CTF{[^}]*}' /tmp/.ctf_upload_triggered 2>/dev/null | head -1) || true [[ -n "${FLAG_10}" ]] && break sleep 2 done - rm -f "${TRIGGER}" + rm -f /home/ctf_user/ctf_challenges/scp_upload_test if [[ -n "${FLAG_10}" ]]; then _verify_flag 10 "${FLAG_10}" "Solved challenge 10" "Challenge 10: Found flag but verify rejected it - SETUP BUG" else - _fail "Challenge 10: File monitoring did not trigger - SETUP BUG" + _fail "Challenge 10: scp upload did not trigger the flag - SETUP BUG" FLAGS[10]="" fi fi diff --git a/README.md b/README.md index b69d44b..f2a87a4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Test your Linux command line skills with 18 progressive Capture The Flag challen | 7 | The Encoded Secret | Find and decode an encoded flag in `ctf_challenges` | ⭐⭐ | Base64, encoding | | 8 | SSH Key Authentication | Configure SSH key authentication and find a hidden flag | ⭐⭐ | SSH configuration | | 9 | DNS Inspection | Inspect the system DNS configuration without changing live resolver files | ⭐⭐ | DNS, `systemd-resolved` | -| 10 | Remote Upload | Transfer any file to `ctf_challenges` to trigger the flag | ⭐⭐ | File transfer, SCP | +| 10 | Remote Upload | From your own computer, upload a new file into `~/ctf_challenges` on the VM to trigger the flag | ⭐⭐ | File transfer, SCP | | 11 | Web Configuration | The web server is running on a non-standard port. Find and fix it | ⭐⭐ | Nginx, services | | 12 | Network Traffic Analysis | Someone is sending secret messages via ping packets | ⭐⭐⭐ | Packet inspection, tcpdump | | 13 | Cron Job Hunter | A scheduled task contains a hidden flag. Find and read it | ⭐⭐ | Cron, scheduling | diff --git a/setup/challenges/ch10_remote_upload.py b/setup/challenges/ch10_remote_upload.py index 6f17d00..f10f141 100644 --- a/setup/challenges/ch10_remote_upload.py +++ b/setup/challenges/ch10_remote_upload.py @@ -16,8 +16,19 @@ def setup(flags: dict[int, str]) -> None: sleep 10 touch /tmp/.ctf_upload_triggered 2>/dev/null || true chmod 666 /tmp/.ctf_upload_triggered 2>/dev/null || true -inotifywait -m -e create --format '%f' "$DIRECTORY" | while read FILE +LAST_TRIGGER=0 +inotifywait -m -e create --format '%f' "$DIRECTORY" | while read -r FILE do + # Ignore editor temp files (vim swap/backup/write-test files) + case "$FILE" in + .*.sw? | *~ | 4913) continue ;; + esac + # Show one banner per upload burst (e.g. scp of several files) + NOW=$(date +%s) + if [ $((NOW - LAST_TRIGGER)) -lt 5 ]; then + continue + fi + LAST_TRIGGER=$NOW { printf '\\n========== CHALLENGE 10: REMOTE UPLOAD ==========' printf '\\nA new file was uploaded to %s.' "$DIRECTORY" diff --git a/verify/src/verify/commands.py b/verify/src/verify/commands.py index 287a7de..e0c2986 100644 --- a/verify/src/verify/commands.py +++ b/verify/src/verify/commands.py @@ -56,7 +56,7 @@ "The flag is encoded. Look for encoded files and use 'base64 -d' to decode.", "SSH configurations often hide secrets. Explore ~/.ssh directory thoroughly.", "Modern Ubuntu DNS is usually managed by systemd-resolved. Inspect /etc/resolv.conf, resolvectl status, and /etc/systemd/resolved.conf.d/.", - "Monitor file creation with tools like inotifywait, or try creating a file in ctf_challenges.", + "Run scp from your own computer, not the VM. The destination must be the ctf_challenges directory (e.g. user@ip:~/ctf_challenges/), and the file must be new - overwriting doesn't count.", "Web servers serve content from specific directories. Check what ports nginx is listening on.", "Network traffic can carry hidden messages. Look at ping patterns with tcpdump.", "Cron jobs run on schedules. Check /etc/cron.d/, /etc/crontab, and user crontabs with 'crontab -l'.",