diff options
Diffstat (limited to '')
| -rw-r--r-- | piolium/attack-surface/source-sink-flows-all-severities.md | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/piolium/attack-surface/source-sink-flows-all-severities.md b/piolium/attack-surface/source-sink-flows-all-severities.md new file mode 100644 index 0000000..d7f93da --- /dev/null +++ b/piolium/attack-surface/source-sink-flows-all-severities.md | |||
| @@ -0,0 +1,143 @@ | |||
| 1 | # Source-to-Sink Flow Paths (All Severities) | ||
| 2 | |||
| 3 | Generated by manual structural extraction (codeql not available on this system). | ||
| 4 | |||
| 5 | ## Critical Flows | ||
| 6 | |||
| 7 | ### Flow C1: Instance Name Path Traversal → Arbitrary Directory Creation | ||
| 8 | - **Source:** `POST /instances/new` → `CreateInstanceForm.name` (free text, no validation) | ||
| 9 | - **Path:** `src/routes/instances.rs:165` → `form.name` → `src/routes/instances.rs:205` `.join(&form.name)` → `std::fs::create_dir_all()` | ||
| 10 | - **Risk:** Attacker can create directories anywhere the webUI user has write access via `../` in instance name | ||
| 11 | - **Control:** NO sanitization whatsoever on `form.name` | ||
| 12 | - **CWE:** CWE-22 (Path Traversal) | ||
| 13 | |||
| 14 | ### Flow C2: Instance Name Path Traversal → Arbitrary Directory Deletion | ||
| 15 | - **Source:** `POST /instances/{id}/delete` → name from DB (originally `form.name`, no sanitization) | ||
| 16 | - **Path:** `src/routes/instances.rs:360` → `DELETE FROM instances WHERE id=?` → name from DB → `src/routes/instances.rs:392-393` `.join(&name)` → `std::fs::remove_dir_all()` | ||
| 17 | - **Risk:** Attacker can delete directories anywhere via `../` in instance name | ||
| 18 | - **Control:** Name stored in DB at creation time without sanitization | ||
| 19 | - **CWE:** CWE-22 (Path Traversal) | ||
| 20 | |||
| 21 | ## High Severity Flows | ||
| 22 | |||
| 23 | ### Flow H1: RCON Command Injection via Player Username | ||
| 24 | - **Source:** `POST /players/whitelist/add` → `PlayerAction.username` (free text) | ||
| 25 | - **Path:** `src/routes/players.rs:93` → `form.username` → `src/routes/players.rs:97` `format!("whitelist add {}", form.username)` → `RconClient.send_command_async(cmd)` | ||
| 26 | - **Risk:** Attacker can inject arbitrary RCON commands via username field (e.g., `foo; op attacker`) | ||
| 27 | - **Control:** NO sanitization on `form.username` | ||
| 28 | - **CWE:** CWE-77 (Command Injection), CWE-88 (Argument Injection) | ||
| 29 | |||
| 30 | ### Flow H2: RCON Command Injection via OP/Ban Username | ||
| 31 | - **Source:** `POST /players/op/add` → `PlayerAction.username` (same for ban/pardon/deop) | ||
| 32 | - **Path:** `src/routes/players.rs:90-98` → `player_action!` macro generates: `format!("op {}", form.username)` → `RconClient.send_command_async()` | ||
| 33 | - **Risk:** Same as H1 — arbitrary RCON command injection | ||
| 34 | - **CWE:** CWE-77, CWE-88 | ||
| 35 | |||
| 36 | ### Flow H3: No Session Expiration/TTL Server-Side | ||
| 37 | - **Source:** Any authenticated session | ||
| 38 | - **Path:** Session stored in SQLite with `created_at` timestamp but **never checked** on authentication | ||
| 39 | - **Risk:** Stolen session cookies remain valid indefinitely server-side | ||
| 40 | - **CWE:** CWE-613 (Insufficient Session Expiration) | ||
| 41 | |||
| 42 | ### Flow H4: No CSRF Protection on Any POST Endpoint | ||
| 43 | - **Source:** All POST endpoints | ||
| 44 | - **Path:** No CSRF token validation anywhere in the application | ||
| 45 | - **Risk:** Cross-site request forgery — any website can trigger state-changing operations | ||
| 46 | - **CWE:** CWE-352 (Cross-Site Request Forgery) | ||
| 47 | |||
| 48 | ## Medium Severity Flows | ||
| 49 | |||
| 50 | ### Flow M1: Mod Filename After Directory Stripping Special Characters | ||
| 51 | - **Source:** `POST /instances/{id}/mods` → multipart upload filename | ||
| 52 | - **Path:** `field.file_name()` → `process_jar_upload()` → `Path::new(filename).file_name()` (strips dir) → `mods_dir.join(&safe_name)` → `std::fs::write()` | ||
| 53 | - **Risk:** After directory stripping, filename may still contain special characters or Unicode normalization issues | ||
| 54 | - **CWE:** CWE-22 | ||
| 55 | |||
| 56 | ### Flow M2: World Import ZIP Extraction (Mitigated) | ||
| 57 | - **Source:** `POST /instances/{id}/world/import` → multipart ZIP | ||
| 58 | - **Path:** `zip::ZipArchive::new()` → `archive.by_index(i)` → `file.enclosed_name()` → `world_dir.join(p)` → `File::create()` | ||
| 59 | - **Risk:** Zip slip partially mitigated by `enclosed_name()` but edge cases may exist | ||
| 60 | - **CWE:** CWE-22 | ||
| 61 | |||
| 62 | ### Flow M3: Backup Download Using Filename from DB | ||
| 63 | - **Source:** `GET /backups/{id}/download` → `backup_id` path parameter | ||
| 64 | - **Path:** `Path(backup_id)` → SQL SELECT filename → `backup_dir.join(&filename)` → `std::fs::read()` | ||
| 65 | - **Risk:** If filename field in DB is manipulated, could read arbitrary files | ||
| 66 | - **CWE:** CWE-22 | ||
| 67 | |||
| 68 | ### Flow M4: Docker Container Environment Variable Injection | ||
| 69 | - **Source:** `POST /instances/new` → various form fields | ||
| 70 | - **Path:** `CreateInstanceForm` → SQL INSERT → Instance model → `docker.start_container(&instance)` → `ContainerConfig.env` includes instance fields unescaped | ||
| 71 | - **Risk:** User-controlled strings passed to Docker container environment | ||
| 72 | - **CWE:** CWE-77 | ||
| 73 | |||
| 74 | ### Flow M5: Level-Seed Injection into server.properties | ||
| 75 | - **Source:** `POST /instances/{id}/world/regenerate` → `form.seed` | ||
| 76 | - **Path:** `form.seed` → `format!("level-seed={seed}")` → `std::fs::write(&props_path, ...)` | ||
| 77 | - **Risk:** Seed string written directly to properties file; could corrupt configuration | ||
| 78 | - **CWE:** CWE-93 (CRLF Injection) | ||
| 79 | |||
| 80 | ### Flow M6: Weak RCON Password Generation | ||
| 81 | - **Source:** `generate_rcon_password()` called during instance creation | ||
| 82 | - **Path:** `rand::thread_rng()` → 16-char alphanumeric → stored in DB | ||
| 83 | - **Risk:** Not cryptographically secure RNG for password generation | ||
| 84 | - **CWE:** CWE-338 (Use of Cryptographically Weak PRNG) | ||
| 85 | |||
| 86 | ### Flow M7: RCON Password Exposed to All Authenticated Users | ||
| 87 | - **Source:** `GET /instances/{id}/edit` template | ||
| 88 | - **Path:** SQL SELECT includes `rcon_password` → `EditInstanceTemplate` → rendered in HTML | ||
| 89 | - **Risk:** Any authenticated user can see the RCON password | ||
| 90 | - **CWE:** CWE-200 (Information Exposure) | ||
| 91 | |||
| 92 | ### Flow M8: Logout Handler Without Auth Middleware | ||
| 93 | - **Source:** `POST /logout` (registered on outer router, not behind `require_auth`) | ||
| 94 | - **Path:** Cookie header parsed → `DELETE FROM sessions WHERE id = ?` | ||
| 95 | - **Risk:** Anonymous requests can delete sessions if session ID is known/guessed | ||
| 96 | - **CWE:** CWE-306 (Missing Authentication) | ||
| 97 | |||
| 98 | ### Flow M9: No Rate Limiting on Login | ||
| 99 | - **Source:** `POST /login` | ||
| 100 | - **Path:** Infinite brute-force attempts possible | ||
| 101 | - **Risk:** Account enumeration and password brute-force | ||
| 102 | - **CWE:** CWE-307 (Improper Restriction of Excessive Authentication Attempts) | ||
| 103 | |||
| 104 | ### Flow M10: Cookie Without Secure Flag | ||
| 105 | - **Source:** Login sets cookie | ||
| 106 | - **Path:** `format!("{SESSION_COOKIE}={session_id}; Path=/; HttpOnly; Max-Age=86400; SameSite=Lax")` | ||
| 107 | - **Risk:** Cookie transmitted over HTTP (missing Secure flag) | ||
| 108 | - **CWE:** CWE-614 (Sensitive Cookie in HTTPS Without Secure Attribute) | ||
| 109 | |||
| 110 | ## Low Severity Flows | ||
| 111 | |||
| 112 | ### Flow L1: Mod JAR Parsing Denial of Service | ||
| 113 | - **Source:** `POST /instances/{id}/mods` → uploaded JAR | ||
| 114 | - **Path:** `validate_mod()` → `ZipArchive::new()` → `scan_jar_for_side()` → constant pool parsing | ||
| 115 | - **Risk:** Crafted JAR with malicious constant pool could cause OOM or infinite loop | ||
| 116 | - **CWE:** CWE-770 (Allocation of Resources Without Limits) | ||
| 117 | |||
| 118 | ### Flow L2: Recursive ZIP Extraction Without Depth Limit | ||
| 119 | - **Source:** `POST /instances/{id}/mods` → ZIP containing JAR (which is also a ZIP) | ||
| 120 | - **Path:** `process_zip_upload()` → extracts JAR → `process_jar_upload()` → `validate_mod()` opens JAR as ZIP again | ||
| 121 | - **Risk:** No depth counter; nested ZIP-in-JAR could cause resource exhaustion | ||
| 122 | - **CWE:** CWE-674 (Uncontrolled Recursion) | ||
| 123 | |||
| 124 | ### Flow L3: Backup Restore is No-Op | ||
| 125 | - **Source:** `POST /backups/{id}/restore` | ||
| 126 | - **Path:** Looks up (instance_id, _filename) from DB, then returns redirect without restoring | ||
| 127 | - **Risk:** Backup restore functionality is implemented as a stub; users think data is restored | ||
| 128 | - **CWE:** CWE-1039 (Automated Recognition Mechanism with Inadequate Detection) | ||
| 129 | |||
| 130 | ### Flow L4: No Audit Logging | ||
| 131 | - **Source:** All state-changing operations | ||
| 132 | - **Path:** No logging of who performed what action | ||
| 133 | - **Risk:** No accountability or forensic evidence for security incidents | ||
| 134 | |||
| 135 | ### Flow L5: ThreadRng Used for Password Generation | ||
| 136 | - **Source:** `src/routes/instances.rs:530` | ||
| 137 | - **Path:** `rand::thread_rng()` → 36^16 password space | ||
| 138 | - **Risk:** Predictable RNG seed may make RCON password guessable | ||
| 139 | |||
| 140 | ### Flow L6: Cookie Splitting Parsing is Naive | ||
| 141 | - **Source:** `src/auth.rs:107` and `src/auth.rs:182` | ||
| 142 | - **Path:** `raw_cookies.split(';')` then `split_once('=')` | ||
| 143 | - **Risk:** If cookie value contains `=` or `;`, parsing may be confused | ||
