Spaces:
Sleeping
Sleeping
Hussien Haider commited on
Commit ·
a095c00
1
Parent(s): 69d7753
This view is limited to 50 files because it contains too many changes. See raw diff
- CHALLENGE_FORMAT_SPEC.md +263 -0
- Dockerfile +14 -0
- apply_migration_005.py +169 -0
- apply_migration_009.py +208 -0
- challenge_files/blue_android-ios_1780374909_app_logs.txt +3 -0
- challenge_files/blue_binary-analysis_1780074195_MalwareX.exe +1 -0
- challenge_files/blue_binary-analysis_1780439585_MalwareX.exe +16 -0
- challenge_files/blue_cloud-config_1780424632_docker-compose.yml +1 -0
- challenge_files/blue_csrf_1780074134_index.html +1 -0
- challenge_files/blue_dns-poisoning_1780073949_dns-logs.txt +3 -0
- challenge_files/blue_encryption-basics_1780073989_encrypted_message.txt +1 -0
- challenge_files/blue_encryption-basics_1780074215_hashes.txt +1 -0
- challenge_files/blue_encryption-basics_1780430362_encryption_logs.txt +3 -0
- challenge_files/blue_encryption-basics_1780439473_file.enc +1 -0
- challenge_files/blue_firewall_1780074071_firewall.conf +3 -0
- challenge_files/blue_hash-cracking_1780074009_hashes.txt +1 -0
- challenge_files/blue_idor_1780074051_users.json +1 -0
- challenge_files/blue_idor_1780435338_user_data.json +1 -0
- challenge_files/blue_packet-analysis_1780073929_capture.pcap +1 -0
- challenge_files/blue_rsa-aes_1780074174_public_key.txt +4 -0
- challenge_files/blue_scanning_1780074319_network_log.txt +4 -0
- challenge_files/blue_sql-injection_1780438865_index.html +1 -0
- challenge_files/blue_ssrf_1780074277_index.html +1 -0
- challenge_files/blue_steganography_1780073889_image.png +1 -0
- challenge_files/blue_steganography_1780073908_image.png +1 -0
- challenge_files/blue_steganography_1780073969_image.png +1 -0
- challenge_files/blue_web-security_1780671884_database.js +13 -0
- challenge_files/blue_xxe_1780074360_example.xml +1 -0
- challenge_files/red_active-directory_1780077323_passwords.txt +3 -0
- challenge_files/red_active-directory_1780374994_active_directory_dump.txt +3 -0
- challenge_files/red_android-ios_1780374952_config.xml +1 -0
- challenge_files/red_binary-analysis_1780074402_encrypted_file.bin +3 -0
- challenge_files/red_cloud-config_1780374930_Dockerfile +5 -0
- challenge_files/red_command-injection_1780096653_output.txt +1 -0
- challenge_files/red_command-injection_1780106333_users.txt +2 -0
- challenge_files/red_csrf_1780120899_csrfExploit.html +1 -0
- challenge_files/red_encryption-basics_1780514019_secret.enc +1 -0
- challenge_files/red_encryption-basics_1780604677_hieroglyph.txt +1 -0
- challenge_files/red_hash-cracking_1780150004_hash.txt +3 -0
- challenge_files/red_hash-cracking_1780528922_shadow.txt +1 -0
- challenge_files/red_idor_1780082059_idor_payload.txt +1 -0
- challenge_files/red_idor_1780116014_user_profiles.txt +3 -0
- challenge_files/red_idor_1780125717_example.txt +1 -0
- challenge_files/red_idor_1780140240_idor_payload.txt +1 -0
- challenge_files/red_idor_1780375037_config.txt +2 -0
- challenge_files/red_idor_1780438930_user.json +1 -0
- challenge_files/red_lfi-rfi_1780154838_secret.txt +1 -0
- challenge_files/red_linux-privesc_1780439793_script.sh +5 -0
- challenge_files/red_memory-forensics_1780074423_memory_dump.bin +3 -0
- challenge_files/red_packet-analysis_1780374973_capture.pcap +1 -0
CHALLENGE_FORMAT_SPEC.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Challenge Format Specification — Crypto/Forensics Track
|
| 2 |
+
|
| 3 |
+
**Version:** 0.1 (Draft)
|
| 4 |
+
**Last updated:** 2026-06-03
|
| 5 |
+
**Applies to:** `public.crypto_challenges` table (new)
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 1. Philosophy
|
| 10 |
+
|
| 11 |
+
التحدي الحقيقي يحاكي CTF/HTB. اللاعب **يكتشف** الخوارزمية ويكسرها بنفسه.
|
| 12 |
+
**ممنوع** تسليم الإجابة في story/task. **ممنوع** إنشاء `flag.txt` كاختصار.
|
| 13 |
+
العلم الصحيح يُخزَّن كـ `SHA-256 hash` فقط — اللاعب يدخل إجابته والـ backend يقارن الـ hash.
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## 2. Database Schema
|
| 18 |
+
|
| 19 |
+
```sql
|
| 20 |
+
CREATE TABLE public.crypto_challenges (
|
| 21 |
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 22 |
+
team_role text NOT NULL CHECK (team_role IN ('blue', 'red')),
|
| 23 |
+
module text NOT NULL, -- encryption-basics, hash-cracking, rsa-aes, ...
|
| 24 |
+
title text NOT NULL,
|
| 25 |
+
story text NOT NULL, -- واقعي، ما يذكر اسم الخوارزمية
|
| 26 |
+
files jsonb NOT NULL DEFAULT '{}'::jsonb,
|
| 27 |
+
-- {filename: base64_content}
|
| 28 |
+
file_metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
| 29 |
+
-- {filename: {size, type, perms, mtime}}
|
| 30 |
+
command_outputs jsonb NOT NULL DEFAULT '{}'::jsonb,
|
| 31 |
+
-- {pattern: {stdout, stderr, exit_code}}
|
| 32 |
+
-- pattern: "cmd:args:filename" or "cmd:args"
|
| 33 |
+
hints jsonb NOT NULL DEFAULT '[]'::jsonb,
|
| 34 |
+
-- [{level: 1, text: "...", xp_cost: 25}, ...]
|
| 35 |
+
tools_whitelist text[] NOT NULL DEFAULT '{}'::text[],
|
| 36 |
+
flag_hash text NOT NULL, -- SHA-256 of correct flag
|
| 37 |
+
flag_preview text, -- "Cybe..." for UX only (not the full flag)
|
| 38 |
+
difficulty text NOT NULL CHECK (difficulty IN ('مبتدئ', 'متوسط', 'قوي')),
|
| 39 |
+
xp_reward integer NOT NULL DEFAULT 150,
|
| 40 |
+
created_at timestamptz NOT NULL DEFAULT now()
|
| 41 |
+
);
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### Indexes
|
| 45 |
+
- `idx_crypto_team_module` ON `(team_role, module)`
|
| 46 |
+
- `idx_crypto_difficulty` ON `(difficulty)`
|
| 47 |
+
|
| 48 |
+
### RLS
|
| 49 |
+
- `Allow all read` (like `blue_scenarios`)
|
| 50 |
+
- `Allow insert/update/delete` for service role only
|
| 51 |
+
|
| 52 |
+
---
|
| 53 |
+
|
| 54 |
+
## 3. Forbidden Anti-Patterns (in `story` + `task`)
|
| 55 |
+
|
| 56 |
+
**Validation script** يفحص كل challenge جديد/معدّل قبل الإدراج. أي ظهور لكلمة ممنوعة = reject.
|
| 57 |
+
|
| 58 |
+
### 3.1 أسماء الخوارزميات المباشرة
|
| 59 |
+
| Module | ممنوع |
|
| 60 |
+
|--------|-------|
|
| 61 |
+
| encryption-basics | `caesar`, `vigenere`, `rot13`, `base64`, `xor`, `aes`, `des` |
|
| 62 |
+
| hash-cracking | `md5`, `sha1`, `sha256`, `ntlm`, `bcrypt` |
|
| 63 |
+
| rsa-aes | `rsa`, `wiener`, `bleichenbacher`, `padding oracle` |
|
| 64 |
+
| steganography | `steganography`, `lsb`, `exiftool` |
|
| 65 |
+
| forensics | `volatility`, `memdump`, `pcap` |
|
| 66 |
+
|
| 67 |
+
### 3.2 أدوات مكشوفة
|
| 68 |
+
`john`, `hashcat`, `crackstation`, `cyberchef`, `openssl`, `binwalk`, `zsteg`
|
| 69 |
+
|
| 70 |
+
### 3.3 تسليم الإجابة
|
| 71 |
+
`الإجابة:`, `الحل:`, `العلم في`, `flag.txt`, `افتح flag`, `استخدم أداة X لكسر`, `المفتاح هو`, `الـ key:`, `crack with`
|
| 72 |
+
|
| 73 |
+
### 3.4 أسماء العلم في المتغير
|
| 74 |
+
`flag{...}`, `CTF{...}`, `APEX{...}` في `story` أو `task` (لكن `CyberArena{...}` مسموح في `flag_hash` فقط)
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## 4. Tools Whitelist (Command Allow-list)
|
| 79 |
+
|
| 80 |
+
Backend endpoint `POST /api/training/terminal` يقبل أوامر من القائمة فقط:
|
| 81 |
+
|
| 82 |
+
### Reading
|
| 83 |
+
`cat`, `head`, `tail`, `strings`, `file`, `xxd`, `od`, `wc`, `hexdump`, `less`, `more`, `tac`, `rev`, `cut`, `tr`
|
| 84 |
+
|
| 85 |
+
### Navigation
|
| 86 |
+
`ls`, `cd`, `pwd`, `tree`, `find`, `stat`, `du`, `df`
|
| 87 |
+
|
| 88 |
+
### Encoding/Transform
|
| 89 |
+
`base64`, `base32`, `tr`, `sort`, `uniq`, `diff`, `nl`, `fold`, `column`
|
| 90 |
+
|
| 91 |
+
### Analysis (custom helpers)
|
| 92 |
+
`freq` — letter/byte frequency analysis
|
| 93 |
+
`ic` — Index of Coincidence
|
| 94 |
+
`ent` — Shannon entropy
|
| 95 |
+
`kasiski` — Kasiski examination (Vigenere key length)
|
| 96 |
+
`chi2` — chi-squared test against English
|
| 97 |
+
`identify` — magic-byte file identification
|
| 98 |
+
|
| 99 |
+
### Crypto (pre-computed)
|
| 100 |
+
`openssl` — limited subcommands: `rsa -text`, `rsautl -decrypt`, `enc -d -aes-256-cbc`
|
| 101 |
+
`hashcat -m <mode> hashes.txt wordlist.txt` — returns pre-computed results
|
| 102 |
+
`john --format=<fmt> hashes.txt` — returns pre-computed results
|
| 103 |
+
`rsatool`, `wiener` (custom Python wrappers)
|
| 104 |
+
|
| 105 |
+
### Shell
|
| 106 |
+
`echo`, `printf`, `test`, `expr`, `bc`, `date`
|
| 107 |
+
`python3 -c "..."` — sandboxed Python REPL with limited stdlib (no `os`, `subprocess`, `socket`)
|
| 108 |
+
`export VAR=value` (limited to challenge scope)
|
| 109 |
+
|
| 110 |
+
### **Forbidden (always rejected)**
|
| 111 |
+
`rm`, `mv`, `cp` outside challenge dir, `curl`, `wget`, `chmod`, `chown`, `sudo`, network commands, `dd` to devices
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
## 5. Terminal Endpoint Contract
|
| 116 |
+
|
| 117 |
+
### Request
|
| 118 |
+
```http
|
| 119 |
+
POST /api/training/terminal
|
| 120 |
+
Content-Type: application/json
|
| 121 |
+
Authorization: Bearer <user_token>
|
| 122 |
+
|
| 123 |
+
{
|
| 124 |
+
"challenge_id": "uuid",
|
| 125 |
+
"command": "cat intercepted.bin",
|
| 126 |
+
"cwd": "/home/agent/challenge"
|
| 127 |
+
}
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### Response (success)
|
| 131 |
+
```json
|
| 132 |
+
{
|
| 133 |
+
"stdout": "THE CIPHERTEXT BYTES...",
|
| 134 |
+
"stderr": "",
|
| 135 |
+
"exit_code": 0,
|
| 136 |
+
"files_changed": [],
|
| 137 |
+
"duration_ms": 12
|
| 138 |
+
}
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
### Response (rejected)
|
| 142 |
+
```json
|
| 143 |
+
{
|
| 144 |
+
"error": "command_not_allowed",
|
| 145 |
+
"command": "rm -rf /",
|
| 146 |
+
"reason": "rm is in deny-list"
|
| 147 |
+
}
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### Response (file not found)
|
| 151 |
+
```json
|
| 152 |
+
{
|
| 153 |
+
"stdout": "",
|
| 154 |
+
"stderr": "cat: missing.bin: No such file or directory",
|
| 155 |
+
"exit_code": 1
|
| 156 |
+
}
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### Backend Logic
|
| 160 |
+
1. Validate challenge exists in `crypto_challenges`
|
| 161 |
+
2. Parse command → extract `cmd`, `args`, `filename`
|
| 162 |
+
3. Check `cmd in tools_whitelist` (challenge-specific) **AND** in global whitelist
|
| 163 |
+
4. Look up `command_outputs[pattern]`:
|
| 164 |
+
- Pattern: `cat:args:filename` (specific)
|
| 165 |
+
- Falls back to: `cat:filename`
|
| 166 |
+
- Falls back to: `cat`
|
| 167 |
+
5. If not found, simulate generic behavior (e.g., `ls` returns file list from `files` keys)
|
| 168 |
+
6. Return result
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
## 6. Flag Validation
|
| 173 |
+
|
| 174 |
+
### Submission Endpoint
|
| 175 |
+
```http
|
| 176 |
+
POST /api/training/answer
|
| 177 |
+
{
|
| 178 |
+
"challenge_id": "uuid",
|
| 179 |
+
"answer": "CyberArena{kasiski_1863_renewed}"
|
| 180 |
+
}
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
### Backend
|
| 184 |
+
```python
|
| 185 |
+
import hashlib
|
| 186 |
+
expected_hash = challenge["flag_hash"] # from DB
|
| 187 |
+
attempt_hash = hashlib.sha256(answer.strip().encode()).hexdigest()
|
| 188 |
+
secured = (attempt_hash == expected_hash)
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
### UI Behavior
|
| 192 |
+
- Wrong answer: `secured: false`, generic feedback ("حاول مرة أخرى")
|
| 193 |
+
- Hint spent: `xp_reward` reduced by `hints[i].xp_cost`
|
| 194 |
+
- Right answer: `secured: true`, full XP awarded, scenario deleted, refill triggered
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
## 7. Required Metadata (per challenge)
|
| 199 |
+
|
| 200 |
+
| Field | Type | Description |
|
| 201 |
+
|-------|------|-------------|
|
| 202 |
+
| `module` | enum | One of 28 modules in CYBER_SECURITY_TOPICS |
|
| 203 |
+
| `tools_whitelist` | string[] | Subset of global whitelist, e.g., `["cat", "freq", "python3", "kasiski"]` |
|
| 204 |
+
| `command_outputs` | jsonb | Pre-computed responses for common commands |
|
| 205 |
+
| `flag_hash` | sha256 hex | Lowercase, 64 chars |
|
| 206 |
+
| `hints` | jsonb array | 2-4 hints, increasing reveal |
|
| 207 |
+
| `difficulty` | enum | مبتدئ/متوسط/قوي |
|
| 208 |
+
|
| 209 |
+
---
|
| 210 |
+
|
| 211 |
+
## 8. Generator Prompt (Groq)
|
| 212 |
+
|
| 213 |
+
```
|
| 214 |
+
You are a CTF challenge author. Generate a realistic crypto/forensics challenge.
|
| 215 |
+
|
| 216 |
+
OUTPUT JSON ONLY with this exact structure:
|
| 217 |
+
{
|
| 218 |
+
"title": "...",
|
| 219 |
+
"story": "...",
|
| 220 |
+
"files": { "filename": "<base64 of file content>" },
|
| 221 |
+
"hints": [{"level":1,"text":"..."},{"level":2,"text":"..."}],
|
| 222 |
+
"flag": "CyberArena{...}",
|
| 223 |
+
"difficulty": "مبتدئ" | "متوسط" | "قوي"
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
RULES:
|
| 227 |
+
- NEVER name the algorithm in story/hints (no "Caesar", "MD5", "Vigenere", "AES")
|
| 228 |
+
- NEVER say "use tool X" or "the key is Y"
|
| 229 |
+
- NEVER use the word "flag.txt" or imply a pre-made file contains the answer
|
| 230 |
+
- Story should make the player curious, not instruct
|
| 231 |
+
- Files should be real data the player must analyze
|
| 232 |
+
- The flag should be the natural conclusion of solving the challenge
|
| 233 |
+
- Difficulty: stronger challenges have more steps, more files, more analysis
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
### Backend Validation
|
| 237 |
+
After Groq returns, run `validate_challenge(payload)`:
|
| 238 |
+
1. Lowercase + scan for forbidden words
|
| 239 |
+
2. SHA-256 hash the flag
|
| 240 |
+
3. Compute `command_outputs` for this specific payload
|
| 241 |
+
4. Insert into DB
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## 9. Sample Challenge (full)
|
| 246 |
+
|
| 247 |
+
See `migrations/004_crypto_challenges.sql` for working SQL.
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
## 10. Migration Plan
|
| 252 |
+
|
| 253 |
+
| Step | Action | Status |
|
| 254 |
+
|------|--------|--------|
|
| 255 |
+
| 1 | Create `crypto_challenges` table | TODO |
|
| 256 |
+
| 2 | Add 1 sample challenge (Vigenere) | TODO |
|
| 257 |
+
| 3 | Build `/api/training/terminal` endpoint | TODO |
|
| 258 |
+
| 4 | Build `/api/training/answer` endpoint | TODO |
|
| 259 |
+
| 5 | Frontend: `<CyberLab>` component | TODO |
|
| 260 |
+
| 6 | Frontend: `<FlagInput>` with hash check | TODO |
|
| 261 |
+
| 7 | Add 14 more challenges (all 4 focus areas) | TODO |
|
| 262 |
+
| 8 | Wire into `populate_pool_background` | TODO |
|
| 263 |
+
| 9 | Anti-pattern validator in `generate_crypto_challenge` | TODO |
|
Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
ENV PORT=7860
|
| 11 |
+
|
| 12 |
+
EXPOSE 7860
|
| 13 |
+
|
| 14 |
+
CMD ["python", "main.py"]
|
apply_migration_005.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
apply_migration_005.py
|
| 3 |
+
======================
|
| 4 |
+
Applies migration 005 to create the `web_exploitation_challenges` table.
|
| 5 |
+
|
| 6 |
+
Tries three strategies in order:
|
| 7 |
+
1. Supabase Management API (requires SUPABASE_ACCESS_TOKEN env var)
|
| 8 |
+
2. Direct PostgreSQL connection (requires DATABASE_URL env var)
|
| 9 |
+
3. Fall back to: print the SQL + clipboard + step-by-step instructions
|
| 10 |
+
|
| 11 |
+
Usage:
|
| 12 |
+
python apply_migration_005.py # tries strategies 1+2 then prints
|
| 13 |
+
python apply_migration_005.py --print # skip strategies, just print SQL
|
| 14 |
+
python apply_migration_005.py --copy # print + copy to clipboard
|
| 15 |
+
"""
|
| 16 |
+
import argparse
|
| 17 |
+
import os
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
# Force UTF-8 output
|
| 23 |
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
| 24 |
+
|
| 25 |
+
# Load .env if available
|
| 26 |
+
try:
|
| 27 |
+
from dotenv import load_dotenv
|
| 28 |
+
_env_path = Path(__file__).parent / ".env"
|
| 29 |
+
if _env_path.exists():
|
| 30 |
+
load_dotenv(_env_path)
|
| 31 |
+
except ImportError:
|
| 32 |
+
pass
|
| 33 |
+
|
| 34 |
+
MIGRATION_FILE = Path(__file__).parent / "migrations" / "005_web_exploitation_challenges.sql"
|
| 35 |
+
SUPABASE_URL = os.environ.get("SUPABASE_URL", "https://yevtnyokixocpihpdwqu.supabase.co")
|
| 36 |
+
SUPABASE_PROJECT_ID = SUPABASE_URL.replace("https://", "").replace("http://", "").split(".")[0]
|
| 37 |
+
SUPABASE_ACCESS_TOKEN = os.environ.get("SUPABASE_ACCESS_TOKEN", "")
|
| 38 |
+
DATABASE_URL = os.environ.get("DATABASE_URL", "")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def banner(msg: str) -> None:
|
| 42 |
+
print()
|
| 43 |
+
print("=" * 70)
|
| 44 |
+
print(f" {msg}")
|
| 45 |
+
print("=" * 70)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def print_sql() -> None:
|
| 49 |
+
print(MIGRATION_FILE.read_text(encoding="utf-8"))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def try_supabase_management_api() -> bool:
|
| 53 |
+
"""Strategy 1: use the Supabase Management API to run SQL.
|
| 54 |
+
|
| 55 |
+
Requires a personal access token from https://supabase.com/dashboard/account/tokens
|
| 56 |
+
in SUPABASE_ACCESS_TOKEN env var.
|
| 57 |
+
"""
|
| 58 |
+
if not SUPABASE_ACCESS_TOKEN:
|
| 59 |
+
return False
|
| 60 |
+
|
| 61 |
+
import httpx
|
| 62 |
+
|
| 63 |
+
banner("Strategy 1: Supabase Management API")
|
| 64 |
+
print(f"Project: {SUPABASE_PROJECT_ID}")
|
| 65 |
+
|
| 66 |
+
sql = MIGRATION_FILE.read_text(encoding="utf-8")
|
| 67 |
+
url = f"https://api.supabase.com/v1/projects/{SUPABASE_PROJECT_ID}/database/query"
|
| 68 |
+
headers = {
|
| 69 |
+
"Authorization": f"Bearer {SUPABASE_ACCESS_TOKEN}",
|
| 70 |
+
"Content-Type": "application/json",
|
| 71 |
+
}
|
| 72 |
+
try:
|
| 73 |
+
with httpx.Client(timeout=30) as client:
|
| 74 |
+
resp = client.post(url, headers=headers, json={"query": sql})
|
| 75 |
+
if resp.status_code == 200:
|
| 76 |
+
print("✓ Migration applied via Management API")
|
| 77 |
+
return True
|
| 78 |
+
else:
|
| 79 |
+
print(f"✗ Management API returned {resp.status_code}: {resp.text[:200]}")
|
| 80 |
+
return False
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"✗ Management API failed: {e}")
|
| 83 |
+
return False
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def try_direct_postgres() -> bool:
|
| 87 |
+
"""Strategy 2: connect directly to the Postgres database.
|
| 88 |
+
|
| 89 |
+
Requires DATABASE_URL env var (e.g. postgresql://postgres:PASS@db.PROJECT.supabase.co:5432/postgres)
|
| 90 |
+
Find this in Supabase Dashboard > Settings > Database > Connection string.
|
| 91 |
+
"""
|
| 92 |
+
if not DATABASE_URL:
|
| 93 |
+
return False
|
| 94 |
+
|
| 95 |
+
banner("Strategy 2: Direct Postgres connection")
|
| 96 |
+
try:
|
| 97 |
+
import psycopg2
|
| 98 |
+
except ImportError:
|
| 99 |
+
print("✗ psycopg2 not installed. Run: pip install psycopg2-binary")
|
| 100 |
+
return False
|
| 101 |
+
|
| 102 |
+
sql = MIGRATION_FILE.read_text(encoding="utf-8")
|
| 103 |
+
try:
|
| 104 |
+
conn = psycopg2.connect(DATABASE_URL)
|
| 105 |
+
conn.autocommit = True
|
| 106 |
+
with conn.cursor() as cur:
|
| 107 |
+
cur.execute(sql)
|
| 108 |
+
conn.close()
|
| 109 |
+
print("✓ Migration applied via direct Postgres connection")
|
| 110 |
+
return True
|
| 111 |
+
except Exception as e:
|
| 112 |
+
print(f"✗ Direct Postgres failed: {e}")
|
| 113 |
+
return False
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def fallback_manual() -> None:
|
| 117 |
+
"""Strategy 3: print the SQL and clear instructions."""
|
| 118 |
+
banner("Strategy 3: Manual — paste into Supabase SQL Editor")
|
| 119 |
+
print()
|
| 120 |
+
print("The migration SQL is printed below.")
|
| 121 |
+
print("To apply it manually:")
|
| 122 |
+
print(f" 1. Open: https://supabase.com/dashboard/project/{SUPABASE_PROJECT_ID}/sql/new")
|
| 123 |
+
print(" 2. Paste the SQL into the editor")
|
| 124 |
+
print(" 3. Click 'Run' (or press Ctrl+Enter)")
|
| 125 |
+
print()
|
| 126 |
+
print("-" * 70)
|
| 127 |
+
print_sql()
|
| 128 |
+
print("-" * 70)
|
| 129 |
+
|
| 130 |
+
# Optionally copy to clipboard (Windows)
|
| 131 |
+
if "--copy" in sys.argv:
|
| 132 |
+
try:
|
| 133 |
+
import subprocess
|
| 134 |
+
sql = MIGRATION_FILE.read_text(encoding="utf-8")
|
| 135 |
+
p = subprocess.Popen(["clip"], stdin=subprocess.PIPE, shell=True)
|
| 136 |
+
p.communicate(sql.encode("utf-16le"))
|
| 137 |
+
print("\n✓ SQL copied to clipboard")
|
| 138 |
+
except Exception as e:
|
| 139 |
+
print(f"\n(Clipboard copy failed: {e})")
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def main():
|
| 143 |
+
parser = argparse.ArgumentParser()
|
| 144 |
+
parser.add_argument("--print", action="store_true", help="Only print the SQL, skip auto-apply")
|
| 145 |
+
parser.add_argument("--copy", action="store_true", help="Print + copy to clipboard")
|
| 146 |
+
args = parser.parse_args()
|
| 147 |
+
|
| 148 |
+
banner("CyberArena — Migration 005 (web_exploitation_challenges)")
|
| 149 |
+
|
| 150 |
+
if args.print:
|
| 151 |
+
print_sql()
|
| 152 |
+
return
|
| 153 |
+
|
| 154 |
+
# Try automatic strategies first
|
| 155 |
+
if try_supabase_management_api():
|
| 156 |
+
return
|
| 157 |
+
if try_direct_postgres():
|
| 158 |
+
return
|
| 159 |
+
|
| 160 |
+
# Fall back to manual
|
| 161 |
+
fallback_manual()
|
| 162 |
+
|
| 163 |
+
print()
|
| 164 |
+
print("After applying the migration, restart the backend.")
|
| 165 |
+
print("The [webex:red] pool watcher will populate 5 challenges within ~8 seconds.")
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
if __name__ == "__main__":
|
| 169 |
+
main()
|
apply_migration_009.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Apply migration 009 (1v1 mode tables) to the Supabase project.
|
| 3 |
+
|
| 4 |
+
================================================================
|
| 5 |
+
3 ways to run this. Pick one:
|
| 6 |
+
================================================================
|
| 7 |
+
|
| 8 |
+
A) RECOMMENDED — one-shot via the Supabase SQL editor
|
| 9 |
+
1. Open https://app.supabase.com/project/yevtnyokixocpihpdwqu/sql/new
|
| 10 |
+
2. Paste the contents of backend/migrations/009_onevone_mode.sql
|
| 11 |
+
3. Click "Run"
|
| 12 |
+
(no service key needed, takes 5 seconds)
|
| 13 |
+
|
| 14 |
+
B) AUTOMATIC — this script, if you set SUPABASE_SERVICE_KEY in backend/.env
|
| 15 |
+
Get the key from: https://app.supabase.com/project/yevtnyokixocpihpdwqu/settings/api
|
| 16 |
+
→ service_role / secret (NOT the anon key)
|
| 17 |
+
|
| 18 |
+
C) PSQL DIRECT — if you have the postgres connection string
|
| 19 |
+
psql "postgresql://postgres:[PASSWORD]@db.yevtnyokixocpihpdwqu.supabase.co:5432/postgres" \
|
| 20 |
+
-f backend/migrations/009_onevone_mode.sql
|
| 21 |
+
|
| 22 |
+
================================================================
|
| 23 |
+
The script tries method B first, then falls back to printing the SQL
|
| 24 |
+
for method A. It's idempotent — every CREATE uses IF NOT EXISTS.
|
| 25 |
+
================================================================
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
import os
|
| 29 |
+
import sys
|
| 30 |
+
import httpx
|
| 31 |
+
from dotenv import load_dotenv
|
| 32 |
+
|
| 33 |
+
backend_dir = os.path.dirname(os.path.abspath(__file__))
|
| 34 |
+
load_dotenv(dotenv_path=os.path.join(backend_dir, ".env"))
|
| 35 |
+
|
| 36 |
+
SUPABASE_URL = os.environ.get("SUPABASE_URL", "https://yevtnyokixocpihpdwqu.supabase.co").rstrip("/")
|
| 37 |
+
SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY", "")
|
| 38 |
+
SUPABASE_SERVICE_KEY = os.environ.get("SUPABASE_SERVICE_KEY", "").strip()
|
| 39 |
+
MIGRATION_PATH = os.path.join(backend_dir, "migrations", "009_onevone_mode.sql")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def read_sql() -> str:
|
| 43 |
+
with open(MIGRATION_PATH, "r", encoding="utf-8") as f:
|
| 44 |
+
return f.read()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def split_sql(sql: str) -> list[str]:
|
| 48 |
+
"""Split a SQL script into individual statements (semi-colon separated,
|
| 49 |
+
ignoring those inside $$ ... $$ blocks)."""
|
| 50 |
+
statements: list[str] = []
|
| 51 |
+
buf: list[str] = []
|
| 52 |
+
in_dollar = False
|
| 53 |
+
for raw in sql.splitlines():
|
| 54 |
+
line = raw
|
| 55 |
+
if line.strip().startswith("--") and not buf:
|
| 56 |
+
continue
|
| 57 |
+
buf.append(line)
|
| 58 |
+
if "$$" in line:
|
| 59 |
+
in_dollar = not in_dollar
|
| 60 |
+
if line.rstrip().endswith(";") and not in_dollar:
|
| 61 |
+
stmt = "\n".join(buf).strip().rstrip(";").strip()
|
| 62 |
+
if stmt:
|
| 63 |
+
statements.append(stmt)
|
| 64 |
+
buf = []
|
| 65 |
+
if buf:
|
| 66 |
+
tail = "\n".join(buf).strip().rstrip(";").strip()
|
| 67 |
+
if tail:
|
| 68 |
+
statements.append(tail)
|
| 69 |
+
return statements
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def try_pg_endpoint(path: str, headers: dict, statements: list[str]) -> bool:
|
| 73 |
+
url = f"{SUPABASE_URL}{path}"
|
| 74 |
+
print(f" → trying {url}")
|
| 75 |
+
try:
|
| 76 |
+
with httpx.Client(timeout=60) as client:
|
| 77 |
+
for i, stmt in enumerate(statements, 1):
|
| 78 |
+
preview = stmt.splitlines()[0][:70]
|
| 79 |
+
r = client.post(url, headers=headers, json={"query": stmt})
|
| 80 |
+
if r.status_code in (200, 201):
|
| 81 |
+
print(f" [{i}/{len(statements)}] OK {preview}")
|
| 82 |
+
else:
|
| 83 |
+
body = r.text[:200]
|
| 84 |
+
if " PGREST205" in body or "schema cache" in body or "must be owner" in body:
|
| 85 |
+
return False
|
| 86 |
+
print(f" [{i}/{len(statements)}] FAIL HTTP {r.status_code} {preview}")
|
| 87 |
+
print(f" {body}")
|
| 88 |
+
return False
|
| 89 |
+
return True
|
| 90 |
+
except Exception as e:
|
| 91 |
+
print(f" → network error: {e}")
|
| 92 |
+
return False
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def apply_via_pg_meta() -> bool:
|
| 96 |
+
if not SUPABASE_SERVICE_KEY:
|
| 97 |
+
return False
|
| 98 |
+
sql = read_sql()
|
| 99 |
+
statements = split_sql(sql)
|
| 100 |
+
print(f"Attempting pg-meta apply with {len(statements)} statements ...")
|
| 101 |
+
headers = {
|
| 102 |
+
"apikey": SUPABASE_SERVICE_KEY,
|
| 103 |
+
"Authorization": f"Bearer {SUPABASE_SERVICE_KEY}",
|
| 104 |
+
"Content-Type": "application/json",
|
| 105 |
+
}
|
| 106 |
+
# try common paths
|
| 107 |
+
for path in ("/pg/query", "/pg-meta/query", "/rest/v1/rpc/exec_sql"):
|
| 108 |
+
if try_pg_endpoint(path, headers, statements):
|
| 109 |
+
return True
|
| 110 |
+
return False
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def apply_via_management_api() -> bool:
|
| 114 |
+
"""Try the Supabase management API (requires access token, not service key)."""
|
| 115 |
+
access = os.environ.get("SUPABASE_ACCESS_TOKEN", "").strip()
|
| 116 |
+
if not access:
|
| 117 |
+
return False
|
| 118 |
+
project_ref = SUPABASE_URL.split("//")[-1].split(".")[0]
|
| 119 |
+
url = f"https://api.supabase.com/v1/projects/{project_ref}/database/query"
|
| 120 |
+
sql = read_sql()
|
| 121 |
+
headers = {
|
| 122 |
+
"Authorization": f"Bearer {access}",
|
| 123 |
+
"Content-Type": "application/json",
|
| 124 |
+
}
|
| 125 |
+
print(f"Attempting Management API apply ...")
|
| 126 |
+
try:
|
| 127 |
+
with httpx.Client(timeout=60) as client:
|
| 128 |
+
r = client.post(url, headers=headers, json={"query": sql})
|
| 129 |
+
if r.status_code in (200, 201):
|
| 130 |
+
print(f" → OK")
|
| 131 |
+
return True
|
| 132 |
+
print(f" → HTTP {r.status_code}: {r.text[:200]}")
|
| 133 |
+
except Exception as e:
|
| 134 |
+
print(f" → network error: {e}")
|
| 135 |
+
return False
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def show_manual_steps() -> None:
|
| 139 |
+
print()
|
| 140 |
+
print("=" * 70)
|
| 141 |
+
print("MANUAL STEPS (5 seconds, no token needed)")
|
| 142 |
+
print("=" * 70)
|
| 143 |
+
print()
|
| 144 |
+
print("1. Open this URL in your browser:")
|
| 145 |
+
print(f" https://app.supabase.com/project/{SUPABASE_URL.split('//')[-1].split('.')[0]}/sql/new")
|
| 146 |
+
print()
|
| 147 |
+
print("2. Paste the following SQL:")
|
| 148 |
+
print("-" * 70)
|
| 149 |
+
print(read_sql())
|
| 150 |
+
print("-" * 70)
|
| 151 |
+
print()
|
| 152 |
+
print("3. Click 'Run' (or press Ctrl+Enter)")
|
| 153 |
+
print()
|
| 154 |
+
print("4. Re-run this script to verify:")
|
| 155 |
+
print(" python apply_migration_009.py --verify")
|
| 156 |
+
print()
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def verify() -> bool:
|
| 160 |
+
"""Hit a known table via PostgREST to confirm it exists."""
|
| 161 |
+
if not SUPABASE_ANON_KEY:
|
| 162 |
+
print("!! SUPABASE_ANON_KEY missing — cannot verify")
|
| 163 |
+
return False
|
| 164 |
+
tables = [
|
| 165 |
+
"onevone_rooms", "onevone_players",
|
| 166 |
+
"onevone_matches", "onevone_submissions",
|
| 167 |
+
]
|
| 168 |
+
headers = {
|
| 169 |
+
"apikey": SUPABASE_ANON_KEY,
|
| 170 |
+
"Authorization": f"Bearer {SUPABASE_ANON_KEY}",
|
| 171 |
+
}
|
| 172 |
+
all_ok = True
|
| 173 |
+
print("Verifying 1v1 tables exist ...")
|
| 174 |
+
with httpx.Client(timeout=20) as client:
|
| 175 |
+
for t in tables:
|
| 176 |
+
r = client.get(f"{SUPABASE_URL}/rest/v1/{t}?select=id&limit=1", headers=headers)
|
| 177 |
+
if r.status_code == 200:
|
| 178 |
+
print(f" [OK] {t}")
|
| 179 |
+
else:
|
| 180 |
+
body = r.text[:120].replace("\n", " ")
|
| 181 |
+
print(f" [!!] {t} → HTTP {r.status_code}: {body}")
|
| 182 |
+
all_ok = False
|
| 183 |
+
return all_ok
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def main() -> int:
|
| 187 |
+
if "--verify" in sys.argv:
|
| 188 |
+
return 0 if verify() else 2
|
| 189 |
+
|
| 190 |
+
if not os.path.exists(MIGRATION_PATH):
|
| 191 |
+
print(f"!! migration file not found: {MIGRATION_PATH}")
|
| 192 |
+
return 1
|
| 193 |
+
|
| 194 |
+
if apply_via_pg_meta():
|
| 195 |
+
print("\n[OK] Migration applied via pg-meta.")
|
| 196 |
+
verify()
|
| 197 |
+
return 0
|
| 198 |
+
if apply_via_management_api():
|
| 199 |
+
print("\n[OK] Migration applied via management API.")
|
| 200 |
+
verify()
|
| 201 |
+
return 0
|
| 202 |
+
|
| 203 |
+
show_manual_steps()
|
| 204 |
+
return 2
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
if __name__ == "__main__":
|
| 208 |
+
sys.exit(main())
|
challenge_files/blue_android-ios_1780374909_app_logs.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2023-02-15 14:30:00 - Sent data to 192.168.1.100:8080
|
| 2 |
+
2023-02-15 14:31:00 - Received response from 192.168.1.100:8080
|
| 3 |
+
2023-02-15 14:32:00 - Sent data to 192.168.1.101:8080
|
challenge_files/blue_binary-analysis_1780074195_MalwareX.exe
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
00000000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00
|
challenge_files/blue_binary-analysis_1780439585_MalwareX.exe
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
00000000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00 |MZ..............|
|
| 2 |
+
00000010 b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
|
| 3 |
+
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 4 |
+
00000030 00 00 00 00 00 00 00 00 00 10 00 00 00 02 00 00 |................|
|
| 5 |
+
00000040 14 01 00 00 00 10 00 00 00 00 00 00 00 00 00 00 |................|
|
| 6 |
+
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 7 |
+
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 8 |
+
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 9 |
+
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 10 |
+
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 11 |
+
000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 12 |
+
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 13 |
+
000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 14 |
+
000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 15 |
+
000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
| 16 |
+
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
|
challenge_files/blue_cloud-config_1780424632_docker-compose.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
version: '3'<br/>services:<br/> web:<br/> build: .<br/> ports:<br/> - "80:80"<br/> volumes:<br/> - .:/app
|
challenge_files/blue_csrf_1780074134_index.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html><html><head><title>تعديل الملفات الشخصية</title></head><body><h1>تعديل الملفات الشخصية</h1><form action="/edit-profile" method="post"><input type="hidden" name="csrf_token" value="1234567890"><input type="text" name="username" placeholder="اسم المستخدم"><input type="email" name="email" placeholder="البريد الإلكتروني"><input type="password" name="password" placeholder="كلمة المرور"><button type="submit">حفظ التغييرات</button></form></body></html>
|
challenge_files/blue_dns-poisoning_1780073949_dns-logs.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2023-02-16 14:30:00, 192.168.1.100, 8.8.8.8, A, apex.com, 192.168.1.200
|
| 2 |
+
2023-02-16 14:31:00, 192.168.1.101, 8.8.8.8, A, apex.com, 192.168.1.201
|
| 3 |
+
2023-02-16 14:32:00, 192.168.1.102, 8.8.8.8, A, apex.com, 192.168.1.202
|
challenge_files/blue_encryption-basics_1780073989_encrypted_message.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
GUR PENML XRL VF ZL FRPERG CBFG
|
challenge_files/blue_encryption-basics_1780074215_hashes.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
098f6bcd4621d373cade4e832627b4f6
|
challenge_files/blue_encryption-basics_1780430362_encryption_logs.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2022-01-01 12:00:00 - ملف1.txt - md5
|
| 2 |
+
2022-01-01 12:05:00 - ملف2.txt - sha1
|
| 3 |
+
2022-01-01 12:10:00 - ملف3.txt - md5
|
challenge_files/blue_encryption-basics_1780439473_file.enc
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
md5_hash_here
|
challenge_files/blue_firewall_1780074071_firewall.conf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
|
| 2 |
+
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
|
| 3 |
+
iptables -A INPUT -p icmp -j DROP
|
challenge_files/blue_hash-cracking_1780074009_hashes.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
098f6bcd4621d373cade4e832627b4f6
|
challenge_files/blue_idor_1780074051_users.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{'id': 1, 'name': 'مستخدم 1'}, {'id': 2, 'name': 'مستخدم 2'}]
|
challenge_files/blue_idor_1780435338_user_data.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id": 1, "name": "محمد", "email": "mohammed@example.com"}
|
challenge_files/blue_packet-analysis_1780073929_capture.pcap
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
حزم شبكة مجمعة تمثل حركة المرور على الشبكة، تحتوي على حزم مشبوهة
|
challenge_files/blue_rsa-aes_1780074174_public_key.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN PUBLIC KEY-----
|
| 2 |
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY
|
| 3 |
+
ozo2t60EG8L0561g13R29LvMR5hyvGZlGJpmn65+A4xHXInJYiPuKzrKfDNSH
|
| 4 |
+
-----END PUBLIC KEY-----
|
challenge_files/blue_scanning_1780074319_network_log.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
192.168.1.100 - - [21/Jan/2024:12:30:00 +0000] "GET / HTTP/1.1" 200 1234
|
| 2 |
+
192.168.1.101 - - [21/Jan/2024:12:30:01 +0000] "GET / HTTP/1.1" 200 1234
|
| 3 |
+
192.168.1.102 - - [21/Jan/2024:12:30:02 +0000] "GET / HTTP/1.1" 200 1234
|
| 4 |
+
192.168.1.100 - - [21/Jan/2024:12:30:03 +0000] "GET /admin HTTP/1.1" 401 1234
|
challenge_files/blue_sql-injection_1780438865_index.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<html><head><title>نظام تسجيل الدخول</title></head><body><h1>تسجيل الدخول</h1><form method='post' action='/login'><input type='text' name='username' placeholder='اسم المستخدم'><br><input type='password' name='password' placeholder='كلمة المرور'><br><button type='submit'>دخول</button></form></body></html>
|
challenge_files/blue_ssrf_1780074277_index.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html><html lang='ar' dir='rtl'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>تطبيق Apex للأمن السيبراني</title><style>body { background-color: #333; color: #fff; }</style></head><body><h1>تطبيق Apex للأمن السيبراني</h1><form id='myForm'><input type='text' id='url' name='url' placeholder='أدخل الرابط'><button type='submit'>إرسال</button></form><script>const form = document.getElementById('myForm');form.addEventListener('submit', (e) => {e.preventDefault();const url = document.getElementById('url').value;if (!url.startsWith('https://apex.com/')) {alert('الرابط غير مخول'); return;}fetch(url).then((response) => response.text()).then((data) => console.log(data));});</script></body></html>
|
challenge_files/blue_steganography_1780073889_image.png
ADDED
|
challenge_files/blue_steganography_1780073908_image.png
ADDED
|
challenge_files/blue_steganography_1780073969_image.png
ADDED
|
challenge_files/blue_web-security_1780671884_database.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function searchBooks(query) {
|
| 2 |
+
// استعلام قاعدة البيانات
|
| 3 |
+
var sql = "SELECT * FROM books WHERE title LIKE '%" + query + "%';";
|
| 4 |
+
// تنفيذ الاستعلام
|
| 5 |
+
db.execute(sql, function(err, results) {
|
| 6 |
+
if (err) {
|
| 7 |
+
console.error(err);
|
| 8 |
+
} else {
|
| 9 |
+
// معالجة النتائج
|
| 10 |
+
console.log(results);
|
| 11 |
+
}
|
| 12 |
+
});
|
| 13 |
+
}
|
challenge_files/blue_xxe_1780074360_example.xml
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<?xml version='1.0' encoding='UTF-8'?><root><data>بيانات مثال</data></root>
|
challenge_files/red_active-directory_1780077323_passwords.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
admin:admin123
|
| 2 |
+
user:user456
|
| 3 |
+
guest:guest789
|
challenge_files/red_active-directory_1780374994_active_directory_dump.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
domain: xyz.local
|
| 2 |
+
users: admin, user1, user2
|
| 3 |
+
groups: admins, users
|
challenge_files/red_android-ios_1780374952_config.xml
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<config><flag>CyberArena{Android_Security_Breach}</flag></config>
|
challenge_files/red_binary-analysis_1780074402_encrypted_file.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e4f9229b57de49dd3c5313210705d8c447fe31b4e1472e6436bf2fd59a273276
|
| 3 |
+
size 16
|
challenge_files/red_cloud-config_1780374930_Dockerfile
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
RUN pip install flask
|
| 3 |
+
COPY app.py /
|
| 4 |
+
EXPOSE 8080
|
| 5 |
+
CMD ["python", "app.py"]
|
challenge_files/red_command-injection_1780096653_output.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
سوف يحتوي هذا الملف على вывод الأمر 'ls -l'
|
challenge_files/red_command-injection_1780106333_users.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
مستخدم1:كلمة_سر_1
|
| 2 |
+
مستخدم2:كلمة_سر_2
|
challenge_files/red_csrf_1780120899_csrfExploit.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<html><body><h1>استغلال الثغرة</h1><form action='https://apex-admin.com/createUser' method='post'><input type='hidden' name='username' value='hacker'><input type='hidden' name='password' value='password123'><input type='submit' value='استغلال'></form></body></html>
|
challenge_files/red_encryption-basics_1780514019_secret.enc
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
U2FsdGVkX1+u8X5Wj7x4qJL7DZKJ8i4q
|
challenge_files/red_encryption-basics_1780604677_hieroglyph.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
GUR PENML XRL VF ZL FRPERG CBFG
|
challenge_files/red_hash-cracking_1780150004_hash.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
45c48ece2e2d4dd4c2d2d4c2d2d4c2
|
| 2 |
+
98f98f98f98f98f98f98f98f
|
| 3 |
+
76c76c76c76c76c76c76c
|
challenge_files/red_hash-cracking_1780528922_shadow.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
$6$rounds=5000$yQ6ZBh9p$ZzD1GyN8K4j9VW9pM8K4j9VW9pM8K4j9VW9pM8K4j9VW9pM8K4j9VW9pM8K4
|
challenge_files/red_idor_1780082059_idor_payload.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
456
|
challenge_files/red_idor_1780116014_user_profiles.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
معلومات المستخدمين:
|
| 2 |
+
المستخدم 1: رقم المستخدم = 123, اسم = أحمد, كلمة المرور = ahmed123
|
| 3 |
+
المستخدم 2: رقم المستخدم = 456, اسم = محمد, كلمة المرور = mohamed456
|
challenge_files/red_idor_1780125717_example.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
هذا هو الملف الذي يجب أن تتمكن من تحميله
|
challenge_files/red_idor_1780140240_idor_payload.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
username=apex_user&userId=1234
|
challenge_files/red_idor_1780375037_config.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fileId=123
|
| 2 |
+
fileName=example.txt
|
challenge_files/red_idor_1780438930_user.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id": 1, "name": "Ahmed", "email": "ahmed@example.com"}
|
challenge_files/red_lfi-rfi_1780154838_secret.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
البيانات السرية للمؤسسة
|
challenge_files/red_linux-privesc_1780439793_script.sh
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
echo 'تمت إضافة مستخدم جديد'
|
| 3 |
+
useradd -m -s /bin/bash user123
|
| 4 |
+
echo 'تم تعيين كلمة المرور للمستخدم الجديد'
|
| 5 |
+
echo 'user123:password123' | chpasswd
|
challenge_files/red_memory-forensics_1780074423_memory_dump.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fac4b93cead0f653491146588a3289777ab6278d94b1d8d455d397ba83758616
|
| 3 |
+
size 263
|
challenge_files/red_packet-analysis_1780374973_capture.pcap
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
حزم شبكية تم جمعها لمدة 10 دقائق
|