ctf-agent 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/AGENTS.md +131 -0
  2. package/Dockerfile +192 -0
  3. package/README.md +522 -0
  4. package/agents/ctf-analyzer.md +58 -0
  5. package/agents/ctf-controller.md +151 -0
  6. package/agents/ctf-speedrun.md +74 -0
  7. package/bin/ctf-agent.js +119 -0
  8. package/docker-compose.yml +13 -0
  9. package/mcp_config.json +19 -0
  10. package/package.json +44 -0
  11. package/references/ctf-triage-ladder.md +68 -0
  12. package/references/exploit-databases.md +575 -0
  13. package/references/llm-safety-and-policy-compliance.md +173 -0
  14. package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
  15. package/references/security-events-and-intelligence.md +566 -0
  16. package/references/version-matrix.md +59 -0
  17. package/rules/ctf-execution-rules.md +44 -0
  18. package/rules/ctf-safety-framing-rules.md +140 -0
  19. package/rules/flag-validation-rules.md +25 -0
  20. package/scripts/__init__.py +1 -0
  21. package/scripts/ctf-tools.lock +205 -0
  22. package/scripts/ctf_init.py +1026 -0
  23. package/scripts/ctf_update.py +583 -0
  24. package/scripts/ctfd_client.py +81 -0
  25. package/scripts/cve_lookup.py +453 -0
  26. package/scripts/extract_flags.py +155 -0
  27. package/scripts/install_as_agent.py +278 -0
  28. package/scripts/install_ctf_tools.sh +603 -0
  29. package/scripts/parallel_triage.py +395 -0
  30. package/scripts/prompt_policy_sanitizer.py +283 -0
  31. package/scripts/scope_guard.py +444 -0
  32. package/scripts/skill_validator.py +504 -0
  33. package/scripts/workspace_cleaner.py +141 -0
  34. package/skills/ctf-ai-ml/SKILL.md +120 -0
  35. package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
  36. package/skills/ctf-ai-ml/llm-attacks.md +487 -0
  37. package/skills/ctf-ai-ml/model-attacks.md +422 -0
  38. package/skills/ctf-crypto/SKILL.md +320 -0
  39. package/skills/ctf-crypto/advanced-math.md +798 -0
  40. package/skills/ctf-crypto/classic-ciphers.md +651 -0
  41. package/skills/ctf-crypto/ecc-attacks.md +347 -0
  42. package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
  43. package/skills/ctf-crypto/exotic-crypto.md +528 -0
  44. package/skills/ctf-crypto/historical.md +113 -0
  45. package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
  46. package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
  47. package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
  48. package/skills/ctf-crypto/modern-ciphers.md +649 -0
  49. package/skills/ctf-crypto/prng-attacks.md +257 -0
  50. package/skills/ctf-crypto/prng.md +664 -0
  51. package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
  52. package/skills/ctf-crypto/rsa-attacks.md +487 -0
  53. package/skills/ctf-crypto/stream-ciphers.md +390 -0
  54. package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
  55. package/skills/ctf-forensics/3d-printing.md +121 -0
  56. package/skills/ctf-forensics/SKILL.md +379 -0
  57. package/skills/ctf-forensics/disk-advanced.md +497 -0
  58. package/skills/ctf-forensics/disk-and-memory.md +491 -0
  59. package/skills/ctf-forensics/disk-recovery.md +699 -0
  60. package/skills/ctf-forensics/linux-forensics.md +511 -0
  61. package/skills/ctf-forensics/network-advanced.md +583 -0
  62. package/skills/ctf-forensics/network.md +645 -0
  63. package/skills/ctf-forensics/peripheral-capture.md +287 -0
  64. package/skills/ctf-forensics/signals-and-hardware.md +713 -0
  65. package/skills/ctf-forensics/steganography.md +694 -0
  66. package/skills/ctf-forensics/stego-advanced-2.md +475 -0
  67. package/skills/ctf-forensics/stego-advanced.md +481 -0
  68. package/skills/ctf-forensics/stego-image.md +691 -0
  69. package/skills/ctf-forensics/windows.md +625 -0
  70. package/skills/ctf-malware/SKILL.md +181 -0
  71. package/skills/ctf-malware/c2-and-protocols.md +274 -0
  72. package/skills/ctf-malware/pe-and-dotnet.md +108 -0
  73. package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
  74. package/skills/ctf-misc/SKILL.md +498 -0
  75. package/skills/ctf-misc/bashjails.md +323 -0
  76. package/skills/ctf-misc/ctfd-navigation.md +465 -0
  77. package/skills/ctf-misc/dns.md +255 -0
  78. package/skills/ctf-misc/encodings-advanced.md +504 -0
  79. package/skills/ctf-misc/encodings.md +431 -0
  80. package/skills/ctf-misc/games-and-vms-2.md +254 -0
  81. package/skills/ctf-misc/games-and-vms-3.md +690 -0
  82. package/skills/ctf-misc/games-and-vms-4.md +229 -0
  83. package/skills/ctf-misc/games-and-vms.md +529 -0
  84. package/skills/ctf-misc/linux-privesc.md +333 -0
  85. package/skills/ctf-misc/pyjails.md +671 -0
  86. package/skills/ctf-misc/rf-sdr.md +91 -0
  87. package/skills/ctf-osint/SKILL.md +198 -0
  88. package/skills/ctf-osint/geolocation-and-media.md +464 -0
  89. package/skills/ctf-osint/social-media.md +312 -0
  90. package/skills/ctf-osint/web-and-dns.md +341 -0
  91. package/skills/ctf-pwn/SKILL.md +214 -0
  92. package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
  93. package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
  94. package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
  95. package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
  96. package/skills/ctf-pwn/advanced-exploits.md +773 -0
  97. package/skills/ctf-pwn/advanced.md +326 -0
  98. package/skills/ctf-pwn/field-notes.md +245 -0
  99. package/skills/ctf-pwn/format-string.md +694 -0
  100. package/skills/ctf-pwn/heap-fsop.md +285 -0
  101. package/skills/ctf-pwn/heap-techniques-2.md +333 -0
  102. package/skills/ctf-pwn/heap-techniques.md +513 -0
  103. package/skills/ctf-pwn/kernel-bypass.md +421 -0
  104. package/skills/ctf-pwn/kernel-techniques.md +366 -0
  105. package/skills/ctf-pwn/kernel.md +636 -0
  106. package/skills/ctf-pwn/overflow-basics.md +611 -0
  107. package/skills/ctf-pwn/rop-advanced.md +725 -0
  108. package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
  109. package/skills/ctf-pwn/sandbox-escape.md +313 -0
  110. package/skills/ctf-reverse/SKILL.md +163 -0
  111. package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
  112. package/skills/ctf-reverse/anti-analysis.md +693 -0
  113. package/skills/ctf-reverse/field-notes.md +376 -0
  114. package/skills/ctf-reverse/languages-compiled.md +666 -0
  115. package/skills/ctf-reverse/languages-platforms.md +592 -0
  116. package/skills/ctf-reverse/languages.md +553 -0
  117. package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
  118. package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
  119. package/skills/ctf-reverse/patterns-ctf.md +670 -0
  120. package/skills/ctf-reverse/patterns-runtime.md +274 -0
  121. package/skills/ctf-reverse/patterns.md +572 -0
  122. package/skills/ctf-reverse/platforms-hardware.md +387 -0
  123. package/skills/ctf-reverse/platforms.md +664 -0
  124. package/skills/ctf-reverse/tools-advanced-2.md +421 -0
  125. package/skills/ctf-reverse/tools-advanced.md +407 -0
  126. package/skills/ctf-reverse/tools-dynamic.md +679 -0
  127. package/skills/ctf-reverse/tools-emulation.md +319 -0
  128. package/skills/ctf-reverse/tools.md +573 -0
  129. package/skills/ctf-web/SKILL.md +153 -0
  130. package/skills/ctf-web/auth-and-access-2.md +82 -0
  131. package/skills/ctf-web/auth-and-access.md +783 -0
  132. package/skills/ctf-web/auth-infra.md +321 -0
  133. package/skills/ctf-web/auth-jwt.md +186 -0
  134. package/skills/ctf-web/client-side-advanced.md +739 -0
  135. package/skills/ctf-web/client-side.md +529 -0
  136. package/skills/ctf-web/cves.md +373 -0
  137. package/skills/ctf-web/field-notes.md +482 -0
  138. package/skills/ctf-web/node-and-prototype.md +200 -0
  139. package/skills/ctf-web/server-side-2.md +337 -0
  140. package/skills/ctf-web/server-side-advanced-2.md +559 -0
  141. package/skills/ctf-web/server-side-advanced-3.md +125 -0
  142. package/skills/ctf-web/server-side-advanced-4.md +480 -0
  143. package/skills/ctf-web/server-side-advanced.md +378 -0
  144. package/skills/ctf-web/server-side-deser.md +443 -0
  145. package/skills/ctf-web/server-side-exec-2.md +799 -0
  146. package/skills/ctf-web/server-side-exec.md +457 -0
  147. package/skills/ctf-web/server-side.md +629 -0
  148. package/skills/ctf-web/sql-injection.md +790 -0
  149. package/skills/ctf-web/web3.md +374 -0
  150. package/skills/ctf-writeup/SKILL.md +90 -0
  151. package/skills/solve-challenge/SKILL.md +269 -0
  152. package/skills.json +16 -0
@@ -0,0 +1,465 @@
1
+ # CTFd Platform Navigation (No Browser)
2
+
3
+ Programmatic interaction with CTFd-based CTF platforms via REST API. Eliminates browser dependency during competitions.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Detect CTFd](#detect-ctfd)
8
+ - [Authentication](#authentication)
9
+ - [List Challenges](#list-challenges)
10
+ - [Challenge Details](#challenge-details)
11
+ - [Download Challenge Files](#download-challenge-files)
12
+ - [Submit Flags](#submit-flags)
13
+ - [Scoreboard](#scoreboard)
14
+ - [Hints and Unlocks](#hints-and-unlocks)
15
+ - [Notifications](#notifications)
16
+ - [User and Team Info](#user-and-team-info)
17
+ - [Full Competition Workflow](#full-competition-workflow)
18
+ - [Python CTFd Client](#python-ctfd-client)
19
+ - [Troubleshooting](#troubleshooting)
20
+
21
+ ---
22
+
23
+ ## Detect CTFd
24
+
25
+ CTFd fingerprints in HTTP responses:
26
+
27
+ ```bash
28
+ # Check for CTFd signatures in response headers and body
29
+ curl -sI "$CTF_URL" | grep -i 'ctfd\|powered-by'
30
+
31
+ # Check for CTFd API endpoint (returns Swagger UI or JSON)
32
+ curl -s "$CTF_URL/api/v1/" | head -20
33
+
34
+ # Check for CTFd static assets
35
+ curl -s "$CTF_URL" | grep -oE '(ctfd|CTFd|/themes/core)'
36
+
37
+ # Check for CTFd login page structure
38
+ curl -s "$CTF_URL/login" | grep -oE 'name="nonce"'
39
+ ```
40
+
41
+ **Key indicators:**
42
+ - `/api/v1/` returns Swagger/RESTX documentation
43
+ - HTML contains `/themes/core/` asset paths
44
+ - Login form includes a `nonce` hidden field
45
+ - Response headers may include `CTFd` in `Server` or `X-Powered-By`
46
+
47
+ ---
48
+
49
+ ## Authentication
50
+
51
+ CTFd supports two auth methods: session cookies (login flow) and API tokens (recommended).
52
+
53
+ **Important:** When CTFd is detected, **ask the user for their API token**. Tokens are not provided by default — the user must generate one from the CTFd web UI (Settings > Access Tokens) before API access works. If the user doesn't have a token yet, guide them: log in to CTFd in a browser, go to Settings > Access Tokens, create a token, and paste it back.
54
+
55
+ ### Method 1: API Token (Recommended)
56
+
57
+ Generate a token from the CTFd web UI (Settings > Access Tokens), or if you already have session cookies:
58
+
59
+ ```bash
60
+ # Generate token via API (requires session auth first)
61
+ curl -s -X POST "$CTF_URL/api/v1/tokens" \
62
+ -H "Content-Type: application/json" \
63
+ -b cookies.txt \
64
+ -d '{"expiration": "2026-12-31", "description": "CLI access"}' | jq .
65
+ ```
66
+
67
+ Use the token for all subsequent requests:
68
+
69
+ ```bash
70
+ export CTF_URL="https://ctf.example.com"
71
+ export CTF_TOKEN="ctfd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
72
+
73
+ # Test authentication
74
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/users/me" | jq .
75
+ ```
76
+
77
+ ### Method 2: Session Login (Cookie-Based)
78
+
79
+ ```bash
80
+ # Step 1: Get CSRF nonce from login page
81
+ NONCE=$(curl -sc cookies.txt "$CTF_URL/login" | grep 'name="nonce"' | grep -oE 'value="[^"]*"' | cut -d'"' -f2)
82
+
83
+ # Step 2: Login with credentials
84
+ curl -sb cookies.txt -c cookies.txt -X POST "$CTF_URL/login" \
85
+ -d "name=username&password=password&nonce=$NONCE" \
86
+ -L -o /dev/null -w '%{http_code}'
87
+
88
+ # Step 3: Use cookies for API calls
89
+ curl -s -b cookies.txt "$CTF_URL/api/v1/users/me" | jq .
90
+ ```
91
+
92
+ **Key insight:** The nonce is a CSRF token required for form-based login. API token auth bypasses this entirely — always prefer tokens when available.
93
+
94
+ ---
95
+
96
+ ## List Challenges
97
+
98
+ ```bash
99
+ # All visible challenges
100
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges" | jq .
101
+
102
+ # Filter by category
103
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges?category=web" | jq .
104
+
105
+ # Compact listing: id, name, category, value, solves
106
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges" | \
107
+ jq -r '.data[] | "\(.id)\t\(.value)pts\t\(.category)\t\(.name)\t(\(.solves) solves)"' | \
108
+ sort -t$'\t' -k3,3 -k2,2rn | column -t -s$'\t'
109
+ ```
110
+
111
+ **Response structure:**
112
+ ```json
113
+ {
114
+ "success": true,
115
+ "data": [
116
+ {
117
+ "id": 1,
118
+ "type": "standard",
119
+ "name": "Challenge Name",
120
+ "value": 100,
121
+ "solves": 42,
122
+ "solved_by_me": false,
123
+ "category": "web",
124
+ "tags": [],
125
+ "template": "...",
126
+ "script": "..."
127
+ }
128
+ ]
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Challenge Details
135
+
136
+ ```bash
137
+ # Full challenge details (description, files, hints, tags)
138
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CHALL_ID" | jq .
139
+
140
+ # Extract just the description (HTML)
141
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CHALL_ID" | \
142
+ jq -r '.data.description'
143
+
144
+ # Strip HTML tags for readable description
145
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CHALL_ID" | \
146
+ jq -r '.data.description' | sed 's/<[^>]*>//g'
147
+
148
+ # List files attached to challenge
149
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CHALL_ID" | \
150
+ jq -r '.data.files[]'
151
+
152
+ # Get connection info (if present in description)
153
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CHALL_ID" | \
154
+ jq -r '.data.description' | grep -oE '(nc |ssh |https?://)[^ <"]+' | head -5
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Download Challenge Files
160
+
161
+ CTFd serves files with token-signed URLs. Extract them from challenge details and download:
162
+
163
+ ```bash
164
+ # Get file URLs from challenge
165
+ FILES=$(curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CHALL_ID" | \
166
+ jq -r '.data.files[]')
167
+
168
+ # Download all challenge files
169
+ mkdir -p "chall_$CHALL_ID"
170
+ for f in $FILES; do
171
+ # File paths are relative — prepend base URL
172
+ URL="${CTF_URL}${f}"
173
+ FILENAME=$(basename "$f" | sed 's/?.*//')
174
+ curl -s -H "Authorization: Token $CTF_TOKEN" -o "chall_$CHALL_ID/$FILENAME" "$URL"
175
+ echo "Downloaded: $FILENAME"
176
+ done
177
+ ```
178
+
179
+ **Key insight:** File URLs include a query-string token (`?token=...`) that authenticates the download. The token is time-limited — re-fetch the challenge details if downloads return 403.
180
+
181
+ ---
182
+
183
+ ## Submit Flags
184
+
185
+ ```bash
186
+ # Submit a flag
187
+ curl -s -X POST -H "Authorization: Token $CTF_TOKEN" \
188
+ -H "Content-Type: application/json" \
189
+ "$CTF_URL/api/v1/challenges/attempt" \
190
+ -d "{\"challenge_id\": $CHALL_ID, \"submission\": \"flag{example}\"}" | jq .
191
+ ```
192
+
193
+ **Response statuses:**
194
+ | Status | Meaning |
195
+ |--------|---------|
196
+ | `correct` | Flag accepted |
197
+ | `incorrect` | Wrong flag |
198
+ | `already_solved` | Previously solved by you/team |
199
+ | `ratelimited` | Too many attempts (default: 10/min) |
200
+ | `paused` | CTF is paused |
201
+
202
+ **Key insight:** Rate limit is 10 incorrect submissions per minute per user. Space out brute-force attempts or you get locked out temporarily.
203
+
204
+ ---
205
+
206
+ ## Scoreboard
207
+
208
+ ```bash
209
+ # Full scoreboard
210
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/scoreboard" | jq .
211
+
212
+ # Top 10
213
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/scoreboard/top/10" | jq .
214
+
215
+ # Compact scoreboard
216
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/scoreboard/top/20" | \
217
+ jq -r '.data | to_entries[] | "\(.value.pos)\t\(.value.name)\t\(.value.score)pts"' | \
218
+ column -t -s$'\t'
219
+ ```
220
+
221
+ **Note:** Scoreboard is cached server-side for 60 seconds.
222
+
223
+ ---
224
+
225
+ ## Hints and Unlocks
226
+
227
+ ```bash
228
+ # List hints for a challenge (from challenge details)
229
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CHALL_ID" | \
230
+ jq '.data.hints'
231
+
232
+ # Get hint content (if free or already unlocked)
233
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/hints/$HINT_ID" | jq .
234
+
235
+ # Unlock a paid hint (costs points)
236
+ curl -s -X POST -H "Authorization: Token $CTF_TOKEN" \
237
+ -H "Content-Type: application/json" \
238
+ "$CTF_URL/api/v1/unlocks" \
239
+ -d "{\"target\": $HINT_ID, \"type\": \"hints\"}" | jq .
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Notifications
245
+
246
+ ```bash
247
+ # Get all notifications (announcements from organizers)
248
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/notifications" | jq .
249
+
250
+ # Get notification count (HEAD request)
251
+ curl -sI -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/notifications" | \
252
+ grep -i 'x-total'
253
+
254
+ # Poll for new notifications since last seen
255
+ curl -s -H "Authorization: Token $CTF_TOKEN" \
256
+ "$CTF_URL/api/v1/notifications?since_id=$LAST_ID" | jq .
257
+ ```
258
+
259
+ ---
260
+
261
+ ## User and Team Info
262
+
263
+ ```bash
264
+ # Current user profile
265
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/users/me" | jq .
266
+
267
+ # My solves
268
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/users/me/solves" | \
269
+ jq -r '.data[] | "\(.challenge.name)\t\(.challenge.value)pts\t\(.date)"'
270
+
271
+ # My failed attempts
272
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/users/me/fails" | jq .
273
+
274
+ # Current team (teams mode)
275
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/teams/me" | jq .
276
+
277
+ # Team solves
278
+ TEAM_ID=$(curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/teams/me" | jq '.data.id')
279
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/teams/$TEAM_ID/solves" | jq .
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Full Competition Workflow
285
+
286
+ End-to-end CTFd interaction from the terminal:
287
+
288
+ ```bash
289
+ #!/usr/bin/env bash
290
+ # CTFd CLI workflow — set these two variables and go
291
+ export CTF_URL="https://ctf.example.com"
292
+ export CTF_TOKEN="ctfd_your_token_here"
293
+ AUTH="-H 'Authorization: Token $CTF_TOKEN'"
294
+
295
+ # 1. Verify auth
296
+ echo "=== Logged in as ==="
297
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/users/me" | jq -r '.data | "\(.name) (id: \(.id))"'
298
+
299
+ # 2. List all challenges grouped by category
300
+ echo -e "\n=== Challenges ==="
301
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges" | \
302
+ jq -r '.data | sort_by(.category, -.value) | .[] |
303
+ "\(.solved_by_me | if . then "[X]" else " " end) \(.id)\t\(.value)pts\t\(.category)\t\(.name)"' | \
304
+ column -t -s$'\t'
305
+
306
+ # 3. Read a specific challenge
307
+ read -p "Challenge ID: " CID
308
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CID" | \
309
+ jq -r '.data | "Name: \(.name)\nCategory: \(.category)\nValue: \(.value)\nSolves: \(.solves)\n\nDescription:\n\(.description)"' | \
310
+ sed 's/<[^>]*>//g'
311
+
312
+ # 4. Download files
313
+ mkdir -p "chall_$CID"
314
+ curl -s -H "Authorization: Token $CTF_TOKEN" "$CTF_URL/api/v1/challenges/$CID" | \
315
+ jq -r '.data.files[]' | while read -r f; do
316
+ curl -s -H "Authorization: Token $CTF_TOKEN" -o "chall_$CID/$(basename "$f" | sed 's/?.*//')" "${CTF_URL}${f}"
317
+ done
318
+ echo "Files downloaded to chall_$CID/"
319
+
320
+ # 5. Submit flag
321
+ read -p "Flag: " FLAG
322
+ curl -s -X POST -H "Authorization: Token $CTF_TOKEN" \
323
+ -H "Content-Type: application/json" \
324
+ "$CTF_URL/api/v1/challenges/attempt" \
325
+ -d "{\"challenge_id\": $CID, \"submission\": \"$FLAG\"}" | \
326
+ jq -r '.data | "\(.status): \(.message)"'
327
+ ```
328
+
329
+ ---
330
+
331
+ ## Python CTFd Client
332
+
333
+ Reusable class for scripted interaction:
334
+
335
+ ```python
336
+ import requests
337
+ import os
338
+ import re
339
+ from pathlib import Path
340
+
341
+
342
+ class CTFdClient:
343
+ """Minimal CTFd API client for competition use."""
344
+
345
+ def __init__(self, url, token):
346
+ self.url = url.rstrip('/')
347
+ self.s = requests.Session()
348
+ self.s.headers['Authorization'] = f'Token {token}'
349
+
350
+ def _get(self, path, **kwargs):
351
+ r = self.s.get(f'{self.url}/api/v1{path}', **kwargs)
352
+ r.raise_for_status()
353
+ return r.json()
354
+
355
+ def _post(self, path, json=None):
356
+ r = self.s.post(f'{self.url}/api/v1{path}', json=json)
357
+ r.raise_for_status()
358
+ return r.json()
359
+
360
+ # --- Challenges ---
361
+
362
+ def challenges(self, category=None):
363
+ """List all visible challenges."""
364
+ params = {'category': category} if category else {}
365
+ return self._get('/challenges', params=params)['data']
366
+
367
+ def challenge(self, cid):
368
+ """Get full challenge details."""
369
+ return self._get(f'/challenges/{cid}')['data']
370
+
371
+ def unsolved(self):
372
+ """List challenges not yet solved by current user."""
373
+ return [c for c in self.challenges() if not c.get('solved_by_me')]
374
+
375
+ # --- Files ---
376
+
377
+ def download_files(self, cid, dest='.'):
378
+ """Download all files for a challenge."""
379
+ info = self.challenge(cid)
380
+ dest = Path(dest)
381
+ dest.mkdir(parents=True, exist_ok=True)
382
+ paths = []
383
+ for f in info.get('files', []):
384
+ url = f'{self.url}{f}' if f.startswith('/') else f
385
+ fname = re.sub(r'\?.*', '', f.split('/')[-1])
386
+ out = dest / fname
387
+ r = self.s.get(url)
388
+ r.raise_for_status()
389
+ out.write_bytes(r.content)
390
+ paths.append(str(out))
391
+ return paths
392
+
393
+ # --- Flag Submission ---
394
+
395
+ def submit(self, cid, flag):
396
+ """Submit a flag. Returns (status, message)."""
397
+ resp = self._post('/challenges/attempt',
398
+ json={'challenge_id': cid, 'submission': flag})
399
+ d = resp['data']
400
+ return d['status'], d['message']
401
+
402
+ # --- Scoreboard ---
403
+
404
+ def scoreboard(self, top=10):
405
+ """Get top N scoreboard entries."""
406
+ return self._get(f'/scoreboard/top/{top}')['data']
407
+
408
+ # --- User/Team ---
409
+
410
+ def me(self):
411
+ """Current user info."""
412
+ return self._get('/users/me')['data']
413
+
414
+ def my_solves(self):
415
+ """Challenges solved by current user."""
416
+ return self._get('/users/me/solves')['data']
417
+
418
+ # --- Hints ---
419
+
420
+ def hint(self, hint_id):
421
+ """Get hint content (if unlocked or free)."""
422
+ return self._get(f'/hints/{hint_id}')['data']
423
+
424
+ def unlock_hint(self, hint_id):
425
+ """Unlock a hint (costs points)."""
426
+ return self._post('/unlocks', json={'target': hint_id, 'type': 'hints'})
427
+
428
+ # --- Notifications ---
429
+
430
+ def notifications(self, since_id=None):
431
+ """Get announcements. Optionally filter since a notification ID."""
432
+ params = {'since_id': since_id} if since_id else {}
433
+ return self._get('/notifications', params=params)['data']
434
+
435
+
436
+ # --- Usage ---
437
+
438
+ if __name__ == '__main__':
439
+ c = CTFdClient(os.environ['CTF_URL'], os.environ['CTF_TOKEN'])
440
+
441
+ # Dashboard
442
+ print(f"Logged in as: {c.me()['name']}")
443
+ print(f"\nUnsolved challenges:")
444
+ for ch in c.unsolved():
445
+ print(f" [{ch['id']}] {ch['category']}/{ch['name']} ({ch['value']}pts, {ch['solves']} solves)")
446
+
447
+ # Download and submit workflow
448
+ # files = c.download_files(1, dest='chall_1')
449
+ # status, msg = c.submit(1, 'flag{...}')
450
+ # print(f"{status}: {msg}")
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Troubleshooting
456
+
457
+ | Symptom | Cause | Fix |
458
+ |---------|-------|-----|
459
+ | 401 Unauthorized | Token expired or invalid | Re-generate token via web UI or session login |
460
+ | 403 on file download | File token expired | Re-fetch challenge details to get fresh file URLs |
461
+ | 403 on challenges | CTF not started or email unverified | Check `/api/v1/users/me` for `verified` field |
462
+ | 429 Rate Limited | Too many wrong flag submissions | Wait 60 seconds; default is 10 incorrect/min |
463
+ | Empty challenge list | CTF hasn't started | Check CTF start time in notifications or config |
464
+ | `nonce` missing | Login page changed or anti-bot | Try API token auth instead of session login |
465
+ | Connection info not in API | Some CTFs use dynamic instances | Check for challenge-specific instance API or Docker endpoints |