diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-29 02:24:41 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-29 02:24:41 +0200 |
| commit | c654a6d17eb58adbb15b9c47ab32bcc3a1666307 (patch) | |
| tree | 545544bf7d805254415da787fad1cdc711767833 /piolium/final-audit-report.md | |
| parent | Add per-instance mod management with client-side detection (diff) | |
| download | minecraft-ui-c654a6d17eb58adbb15b9c47ab32bcc3a1666307.tar.gz minecraft-ui-c654a6d17eb58adbb15b9c47ab32bcc3a1666307.zip | |
Security audit fixes: patch 16 vulnerabilities across 7 source files
CRITICAL:
- Instance name path traversal (AP-001): sanitize::sanitize_instance_name()
strips .., /, \, \0, spaces, special chars at all 12 join call sites
- Zip-slip protection in world import: starts_with() guard after enclosed_name()
HIGH:
- RCON command injection: validate_username() rejects non-MC usernames
across all 6 player action macros (whitelist, op, ban)
- Level-seed properties injection: sanitize_seed() strips \n \r = : \0
- CSRF protection: SameSite=Lax -> SameSite=Strict on session cookie
MEDIUM:
- Session TTL: server-side 24h expiry via strftime check in require_auth
- Rate limiting: in-memory 10-ip/5min sliding window on login
- Background task stale is_active: all queries use concrete WHERE id=?1
- Mod validation DoS: 500MB cap, 10k class scan limit, 10k CP entry cap
LOW:
- Logout auth guard: moved into protected route group + CurrentUser param
- Mod orphan race: DB insert before file write
- Backup orphan race: DB insert before ZIP creation
- Template fix: broken Askama == comparisons in edit.html
All fixes compile clean with zero warnings. 10 unit tests pass.
Diffstat (limited to '')
| -rw-r--r-- | piolium/final-audit-report.md | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/piolium/final-audit-report.md b/piolium/final-audit-report.md new file mode 100644 index 0000000..049d2b2 --- /dev/null +++ b/piolium/final-audit-report.md | |||
| @@ -0,0 +1,407 @@ | |||
| 1 | # Security Audit Report: salmon/minecraft-ui | ||
| 2 | |||
| 3 | ========================================= | ||
| 4 | |||
| 5 | ## Executive Summary | ||
| 6 | |||
| 7 | This audit of **minecraft-ui** — a self-hosted Rust (axum) web application that manages Docker-based Minecraft server instances — identified **17 validated vulnerabilities** spanning path traversal, injection, CSRF, session management, authorization, concurrency, denial-of-service, and cryptographic weaknesses. The most critical issue is **arbitrary filesystem access via unsanitized instance names** (AP-001), which enables path-traversal-driven directory creation, deletion, and constrained file read/write across 12 confirmed code paths — including 4 variant instances discovered in Phase 12. Four high-severity findings were cold-verified via adversarial review (P11). Combined with universal CSRF absence (31 POST endpoints) and a flat authorization model, the application provides minimal defense-in-depth: any authenticated user can destroy data, modify server configurations, or gain persistent access to Minecraft server consoles. The audit reached draft stage but the **findings promotion pipeline stalled** — no PoCs were built and no `piolium/findings/` directory was created. This report is assembled from available draft, chamber, and knowledge-base artifacts. | ||
| 8 | |||
| 9 | ## Methodology Summary | ||
| 10 | |||
| 11 | - **Intelligence Gathering:** Advisory collection via `cargo-audit-advisory` (SBOM), architecture inventory of 16 Rust source files, dependency analysis (rusqlite, bollard, multer, zip) | ||
| 12 | - **Knowledge Base:** Threat modeling with DFD/CFD slices (P3), manual attack surface inventory of 38 routes (P8), domain attack research (Minecraft RCON, Java .properties, Docker env injection) | ||
| 13 | - **Static Analysis:** Structural extraction via manual code review; CodeQL database could not be built (system constraint); Semgrep ran but with limited Rust rule coverage | ||
| 14 | - **Review Chambers:** 6-cluster multi-agent debate system (path-traversal, injection, auth-session, state-concurrency, denial-of-service, security-architecture). 37 findings from P4/P5/P6/P8 reviewed → **17 VALID** survivors, 7 FALSE POSITIVE, 8 DROP, 5 DUPLICATE | ||
| 15 | - **Verification:** P11 cold verification (adversarial review) for 4 highest-severity findings: p10-001 (CONFIRMED), p10-003 (CONFIRMED), p10-004 (DISPROVED), p10-005 (CONFIRMED with severity downgrade challenge) | ||
| 16 | - **Variant Analysis (P12):** 4 path-traversal variants discovered from AP-001 root cause search | ||
| 17 | |||
| 18 | ## Summary of Findings | ||
| 19 | |||
| 20 | | ID | Title | Severity | PoC Status | Status | | ||
| 21 | |----|-------|----------|------------|--------| | ||
| 22 | | — | Instance Name Path Traversal (p10-001) | CRITICAL | draft-only | Not promoted | | ||
| 23 | | — | Level-Seed Properties Injection (p10-003) | HIGH | draft-only | Not promoted | | ||
| 24 | | — | Missing CSRF Protection (p10-005) | HIGH | draft-only | Not promoted | | ||
| 25 | | — | RCON Password Exposed to All Users (p10-004) | **LOW** (DISPROVED) | blocked | Adversarial reversal | | ||
| 26 | | — | RCON Command Injection (p10-002) | MEDIUM | draft-only | Not promoted | | ||
| 27 | | — | Flat Authorization Model (p10-010) | MEDIUM | draft-only | Not promoted | | ||
| 28 | | — | No Server-Side Session TTL (p10-006) | MEDIUM | draft-only | Not promoted | | ||
| 29 | | — | No Rate Limiting Login (p10-007) | MEDIUM | draft-only | Not promoted | | ||
| 30 | | — | Background Task Stale is_active (p10-011) | MEDIUM | draft-only | Not promoted | | ||
| 31 | | — | Delete-Create Name Reuse Race (p10-012) | MEDIUM | draft-only | Not promoted | | ||
| 32 | | — | No Idempotency (p10-013) | MEDIUM | draft-only | Not promoted | | ||
| 33 | | — | Mod Validation Denial of Service (p10-016) | MEDIUM | draft-only | Not promoted | | ||
| 34 | | — | No Encryption at Rest (p10-017) | MEDIUM | draft-only | Not promoted | | ||
| 35 | | — | Logout No Auth Middleware (p10-008) | LOW | draft-only | Not promoted | | ||
| 36 | | — | Cookie No Secure Flag (p10-009) | LOW | draft-only | Not promoted | | ||
| 37 | | — | Mod Write Before DB Orphan (p10-014) | LOW | draft-only | Not promoted | | ||
| 38 | | — | Backup Orphan Race (p10-015) | LOW | draft-only | Not promoted | | ||
| 39 | |||
| 40 | ### Variant Findings (Phase 12) | ||
| 41 | |||
| 42 | | Variant ID | Title | Severity | Parent | PoC Status | | ||
| 43 | |-----------|-------|----------|--------|------------| | ||
| 44 | | p12-001 | World Import Path Traversal | HIGH | p10-001 | draft-only | | ||
| 45 | | p12-002 | Regenerate World Path Traversal | HIGH | p10-001 | draft-only | | ||
| 46 | | p12-003 | Player Management Path Traversal | MEDIUM | p10-001 | draft-only | | ||
| 47 | | p12-004 | Backup Create Path Traversal | MEDIUM | p10-001 | draft-only | | ||
| 48 | |||
| 49 | > **Note:** No findings were promoted to `piolium/findings/` with PoC scripts, evidence, or full report.md files. All entries above remain at draft stage in `piolium/findings-draft/`. | ||
| 50 | |||
| 51 | --- | ||
| 52 | |||
| 53 | ## Technical Findings Detail | ||
| 54 | |||
| 55 | ### Instance Name Path Traversal (p10-001 — CRITICAL equivalent) | ||
| 56 | |||
| 57 | - **Severity:** CRITICAL | ||
| 58 | - **Summary:** The instance `name` field flows unsanitized into `Path::join()` for 8 different filesystem operations. Rust's `Path::join()` follows `..` components, enabling arbitrary directory creation, deletion, and constrained file read/write outside the intended data directory. | ||
| 59 | - **Impact:** An authenticated attacker can: | ||
| 60 | - **Delete arbitrary directories** via `remove_dir_all` (path: `src/routes/instances.rs:388-392`) — e.g., name=`../../../target` deletes the project build directory | ||
| 61 | - **Create arbitrary directories** via `create_dir_all` (path: `src/routes/instances.rs:204-206`) | ||
| 62 | - **Write files to arbitrary locations** (constrained by `/mods/` or `/data/world/` suffix chaining via mod upload and world import) | ||
| 63 | - **Read files from arbitrary locations** (constrained by world export and backup download paths) | ||
| 64 | - **Root Cause:** `Path::join()` in Rust follows `..` components. User-controlled `form.name` is passed directly with no sanitization, length limits, or character restrictions. No validation exists at any of the 12 known join call sites. | ||
| 65 | - **Key Code Reference:** `src/routes/instances.rs:204-206` — `Path::join(&form.name)` → `create_dir_all` | ||
| 66 | - **PoC Status:** draft-only (no executable PoC built) | ||
| 67 | - **Detailed Report:** `piolium/findings-draft/p10-001-instance-name-path-traversal.md` | ||
| 68 | - **Adversarial Review:** `piolium/adversarial-reviews/p10-001-instance-name-path-traversal-review.md` — CONFIRMED, severity challenge rejected | ||
| 69 | |||
| 70 | #### Attack Pattern: AP-001 | ||
| 71 | |||
| 72 | This finding is the root of attack pattern **AP-001** (Path Traversal via Unsanitized Instance Name), which has 12 confirmed instances across 4 route modules: | ||
| 73 | - `src/routes/instances.rs:205` (create_dir_all) | ||
| 74 | - `src/routes/instances.rs:392` (remove_dir_all) | ||
| 75 | - `src/routes/mods.rs:126` (mods dir creation) | ||
| 76 | - `src/routes/mods.rs:343` (mod rescan) | ||
| 77 | - `src/routes/mods.rs:394` (mod delete) | ||
| 78 | - `src/routes/backups.rs:107` (backup path) | ||
| 79 | - `src/routes/instances.rs:593` (world export) | ||
| 80 | - `src/routes/instances.rs:739` (server.properties) | ||
| 81 | - `src/routes/instances.rs:672` (world import — variant) | ||
| 82 | - `src/routes/instances.rs:776` (regenerate world — variant) | ||
| 83 | - `src/routes/players.rs:53` (player management — variant) | ||
| 84 | - `src/routes/backups.rs:112` (backup create — variant) | ||
| 85 | |||
| 86 | #### Variants | ||
| 87 | |||
| 88 | | Variant ID | Title | Severity | Location | Flow | | ||
| 89 | |-----------|-------|----------|----------|------| | ||
| 90 | | p12-001 | World Import Path Traversal | HIGH | `instances.rs:672` | `instance_name` from DB → `world_dir.join(zip_entry)` → `File::create()` | | ||
| 91 | | p12-002 | Regenerate World Path Traversal | HIGH | `instances.rs:776` | `instance_name` from DB → `.join(&name)` → `remove_dir_all(world_dir)` | | ||
| 92 | | p12-003 | Player Management Path Traversal | MEDIUM | `players.rs:53` | `inst_name` → `.join(...)` → `read_json_list()` (read-only, 3 filenames) | | ||
| 93 | | p12-004 | Backup Create Path Traversal | MEDIUM | `backups.rs:112` | `name` → `{name}_{timestamp}.zip` → `.join(&filename)` → `File::create()` | | ||
| 94 | |||
| 95 | Variant details: `piolium/findings-draft/p12-001-*.md` through `piolium/findings-draft/p12-004-*.md` | ||
| 96 | |||
| 97 | --- | ||
| 98 | |||
| 99 | ### Level-Seed Properties Injection (p10-003 — HIGH) | ||
| 100 | |||
| 101 | - **Severity:** HIGH | ||
| 102 | - **Summary:** The `POST /instances/{id}/world/regenerate` handler writes the user-supplied `seed` value into `server.properties` via line-based replacement with no escaping. Newline characters in the seed inject arbitrary Java `.properties` entries. | ||
| 103 | - **Impact:** Persistent modification of Minecraft server configuration: | ||
| 104 | - Set `online-mode=false` (disable authentication — any player can join as anyone) | ||
| 105 | - Set `enforce-whitelist=false` (bypass whitelist restrictions) | ||
| 106 | - Change difficulty, gamemode, max-players, PvP settings | ||
| 107 | - Inject malformed properties causing server startup failure (DoS) | ||
| 108 | - **Persistence:** Injected values survive across server restarts until manually cleaned | ||
| 109 | - **Root Cause:** `format!("level-seed={seed}")` writes the seed value directly into `server.properties` without escaping. The Java `.properties` spec treats newlines as property separators. The `properties.rs:13-19` reader also lacks escape-sequence processing (spec gap G1). | ||
| 110 | - **Key Code Reference:** `src/routes/instances.rs:715-759` — `regenerate_world` handler, line replacement loop | ||
| 111 | - **PoC Status:** draft-only | ||
| 112 | - **Detailed Report:** `piolium/findings-draft/p10-003-level-seed-properties-injection.md` | ||
| 113 | - **Adversarial Review:** `piolium/adversarial-reviews/p10-003-level-seed-properties-injection-review.md` — CONFIRMED, severity unchanged | ||
| 114 | |||
| 115 | --- | ||
| 116 | |||
| 117 | ### Missing CSRF Protection (p10-005 — HIGH) | ||
| 118 | |||
| 119 | - **Severity:** HIGH (adversarial challenge to MEDIUM rejected) | ||
| 120 | - **Summary:** The application has no CSRF protection across any of its 31 POST endpoints. No CSRF tokens are generated or validated. No Origin/Referer header validation exists. The session cookie uses `SameSite=Lax` which permits cookies on top-level cross-site POST navigations. | ||
| 121 | - **Impact:** Universal cross-site request forgery enabling: | ||
| 122 | - **Account takeover:** Cross-site password change (though `current_password` IS required for `/change-password`, the superuser `/accounts/set-password` does NOT require the current password) | ||
| 123 | - **Instance destruction:** Cross-site delete requests wipe server data | ||
| 124 | - **File system manipulation:** CSRF + path traversal (p10-001) to write files to arbitrary locations | ||
| 125 | - **Minecraft server compromise:** CSRF to op attackers on the Minecraft server via player management endpoints | ||
| 126 | - **Malicious mod upload:** Cross-site mod upload writes JAR files to server | ||
| 127 | - **Root Cause:** Cookie `SameSite=Lax` (not `Strict`) and zero CSRF token infrastructure. No axum CSRF middleware, no custom token generation, no header validation. | ||
| 128 | - **Key Code Reference:** `src/auth.rs:83-86` — Cookie with `SameSite=Lax`; entire `src/routes/mod.rs` — no CSRF middleware | ||
| 129 | - **PoC Status:** draft-only | ||
| 130 | - **Detailed Report:** `piolium/findings-draft/p10-005-missing-csrf-protection.md` | ||
| 131 | - **Adversarial Review:** `piolium/adversarial-reviews/p10-005-missing-csrf-protection-review.md` — CONFIRMED; severity down-challenge from HIGH to MEDIUM rejected by chamber (sub-claim D partially false but overall impact remains HIGH) | ||
| 132 | |||
| 133 | --- | ||
| 134 | |||
| 135 | ### RCON Command Injection (p10-002 — MEDIUM) | ||
| 136 | |||
| 137 | - **Severity:** MEDIUM | ||
| 138 | - **Summary:** The `player_action!` macro in `src/routes/players.rs` constructs RCON commands by interpolating user-supplied usernames via `format!()` without sanitization. Six player management endpoints are affected. | ||
| 139 | - **Impact:** Operation-target manipulation via space injection, silent operation failure from malformed usernames. In vanilla Minecraft, RCON does not support command chaining (no `;` or `&&` injection), limiting impact to argument injection. On Spigot/Paper servers with command-chaining plugins, broader injection may be possible. | ||
| 140 | - **Root Cause:** `format!("whitelist add {}", form.username)` — no validation, no character filtering, no Minecraft username format enforcement. | ||
| 141 | - **Key Code Reference:** `src/routes/players.rs:89-102` — `player_action!` macro; `PlayerAction.username` is a raw `String` | ||
| 142 | - **PoC Status:** draft-only | ||
| 143 | - **Detailed Report:** `piolium/findings-draft/p10-002-rcon-command-injection.md` | ||
| 144 | |||
| 145 | --- | ||
| 146 | |||
| 147 | ### Flat Authorization Model (p10-010 — MEDIUM) | ||
| 148 | |||
| 149 | - **Severity:** MEDIUM | ||
| 150 | - **Summary:** Any authenticated user has full access to ALL instances, mods, backups, and player operations. There is no per-resource ownership, no role granularity beyond binary superuser/non-superuser, and no instance-level authorization. | ||
| 151 | - **Impact:** In multi-user deployments, every user is a full administrator of every server instance. Data exfiltration, destructive operations, and Minecraft server control are all available to any authenticated account. | ||
| 152 | - **Root Cause:** No `owner_id` column on `instances` table; all handlers use `CurrentUser(_user)` with destructured-and-ignored user variable; all queries lack user-scoped filters. | ||
| 153 | - **Key Code References:** `src/routes/instances.rs` (all handlers), `src/routes/players.rs` (`player_action!`), `src/routes/backups.rs`, `src/routes/mods.rs` | ||
| 154 | - **PoC Status:** draft-only | ||
| 155 | - **Detailed Report:** `piolium/findings-draft/p10-010-flat-authorization-model.md` | ||
| 156 | |||
| 157 | --- | ||
| 158 | |||
| 159 | ### No Server-Side Session TTL (p10-006 — MEDIUM) | ||
| 160 | |||
| 161 | - **Severity:** MEDIUM | ||
| 162 | - **Summary:** The `require_auth` middleware validates session cookies against the database but never checks the `created_at` timestamp, despite the column existing in the schema. Stolen session cookies remain valid indefinitely server-side. | ||
| 163 | - **Root Cause:** `WHERE s.id = ?1` query at `src/auth.rs:108-115` lacks a time-based filter. Cookie Max-Age=86400 is client-side only and trivially bypassed. | ||
| 164 | - **Key Code Reference:** `src/auth.rs:108-115` — session query without `created_at` filter | ||
| 165 | - **PoC Status:** draft-only | ||
| 166 | - **Detailed Report:** `piolium/findings-draft/p10-006-no-server-side-session-ttl.md` | ||
| 167 | |||
| 168 | --- | ||
| 169 | |||
| 170 | ### No Rate Limiting Login (p10-007 — MEDIUM) | ||
| 171 | |||
| 172 | - **Severity:** MEDIUM | ||
| 173 | - **Summary:** `POST /login` has no rate limiting, account lockout, or progressive delay. Argon2id (~100ms/attempt) provides a weak mitigating factor. | ||
| 174 | - **Root Cause:** No rate-limiting middleware, no `governor` or equivalent crate in Cargo.toml. | ||
| 175 | - **Key Code Reference:** `src/auth.rs:39-92` — authenticate handler; `src/routes/mod.rs:44` — route registration | ||
| 176 | - **PoC Status:** draft-only | ||
| 177 | - **Detailed Report:** `piolium/findings-draft/p10-007-no-rate-limiting-login.md` | ||
| 178 | |||
| 179 | --- | ||
| 180 | |||
| 181 | ### Background Task Stale is_active (p10-011 — MEDIUM) | ||
| 182 | |||
| 183 | - **Severity:** MEDIUM | ||
| 184 | - **Summary:** `activate_instance` spawns a background tokio task that updates `container_state` using `WHERE is_active = 1` instead of the concrete instance ID. If the user switches active instances during the 60-second polling window, the stale task writes to the wrong instance's state. | ||
| 185 | - **Root Cause:** 5 UPDATE statements at `src/routes/instances.rs:457-502` all use `WHERE is_active = 1` instead of `WHERE id = ?1`. | ||
| 186 | - **Key Code Reference:** `src/routes/instances.rs:457-502` — background tokio task | ||
| 187 | - **PoC Status:** draft-only (requires dynamic test) | ||
| 188 | - **Detailed Report:** `piolium/findings-draft/p10-011-background-task-stale-is-active.md` | ||
| 189 | |||
| 190 | --- | ||
| 191 | |||
| 192 | ### Delete-Create Name Reuse Race (p10-012 — MEDIUM) | ||
| 193 | |||
| 194 | - **Severity:** MEDIUM | ||
| 195 | - **Summary:** `delete_instance` releases the DB mutex before `remove_dir_all`. A concurrent `create_instance` with the same name can create a new instance whose data directory is then wiped by the still-running deletion. | ||
| 196 | - **Root Cause:** Mutex released at `drop(conn)` between `DELETE FROM instances` and `remove_dir_all()` (microsecond-to-millisecond race window). | ||
| 197 | - **Key Code Reference:** `src/routes/instances.rs:360-396` | ||
| 198 | - **PoC Status:** draft-only (requires dynamic test) | ||
| 199 | - **Detailed Report:** `piolium/findings-draft/p10-012-delete-create-name-reuse-race.md` | ||
| 200 | |||
| 201 | --- | ||
| 202 | |||
| 203 | ### No Idempotency (p10-013 — MEDIUM) | ||
| 204 | |||
| 205 | - **Severity:** MEDIUM | ||
| 206 | - **Summary:** Zero idempotency infrastructure across all 31 POST endpoints. Network request replay (HTMX retry, proxy retry, rapid clicks) causes duplicate operations. | ||
| 207 | - **Impact:** Duplicate mod uploads cause classloader conflicts on server restart (DoS). Duplicate backup creation wastes disk space. Duplicate player RCON commands are mostly harmless. | ||
| 208 | - **Root Cause:** No idempotency key table, no deduplication logic, no request ID storage. | ||
| 209 | - **Key Code Reference:** All POST endpoints in `src/routes/*.rs` | ||
| 210 | - **PoC Status:** draft-only (static hypothesis) | ||
| 211 | - **Detailed Report:** `piolium/findings-draft/p10-013-no-idempotency.md` | ||
| 212 | |||
| 213 | --- | ||
| 214 | |||
| 215 | ### Mod Validation Denial of Service (p10-016 — MEDIUM) | ||
| 216 | |||
| 217 | - **Severity:** MEDIUM | ||
| 218 | - **Summary:** `validate_mod()` parses uploaded JAR constant pools with no timeout or resource limits. A crafted constant pool (cp_count=65535, expensive CONSTANT_Utf8 entries) causes CPU exhaustion on the async runtime. | ||
| 219 | - **Impact:** Async thread pool exhaustion; multiple concurrent crafted uploads make the server unresponsive. | ||
| 220 | - **Root Cause:** Unbounded `while i < cp_count` loop at `src/mods.rs:173-232` with no timeout wrapping. | ||
| 221 | - **Key Code Reference:** `src/mods.rs:173-232` — `scan_single_class()`; `src/routes/mods.rs:109-250` — no timeout around `validate_mod` | ||
| 222 | - **PoC Status:** draft-only | ||
| 223 | - **Detailed Report:** `piolium/findings-draft/p10-016-mod-validation-denial-of-service.md` | ||
| 224 | |||
| 225 | --- | ||
| 226 | |||
| 227 | ### No Encryption at Rest (p10-017 — MEDIUM) | ||
| 228 | |||
| 229 | - **Severity:** MEDIUM | ||
| 230 | - **Summary:** SQLite database `mcui.db` is stored unencrypted on disk with plaintext RCON passwords. Disclosure via prerequisite file read (p10-001 path traversal) or physical access exposes all secrets. | ||
| 231 | - **Root Cause:** `Connection::open(path)` at `src/db.rs:16` with no encryption (no SQLCipher, no individual field encryption). | ||
| 232 | - **Key Code Reference:** `src/db.rs:16` — `Connection::open(path)` | ||
| 233 | - **PoC Status:** draft-only | ||
| 234 | - **Detailed Report:** `piolium/findings-draft/p10-017-no-encryption-at-rest.md` | ||
| 235 | |||
| 236 | --- | ||
| 237 | |||
| 238 | ### Logout No Auth Middleware (p10-008 — LOW) | ||
| 239 | |||
| 240 | - **Severity:** LOW | ||
| 241 | - **Summary:** `POST /logout` is registered outside the `require_auth` protected router group. Any unauthenticated request can reach the logout handler. Requires prior session ID disclosure (UUIDv4, 122-bit entropy) for practical exploitation. | ||
| 242 | - **Key Code Reference:** `src/routes/mod.rs:20-33` — logout registered before `.merge(protected)` | ||
| 243 | - **PoC Status:** draft-only | ||
| 244 | - **Detailed Report:** `piolium/findings-draft/p10-008-logout-no-auth-middleware.md` | ||
| 245 | |||
| 246 | --- | ||
| 247 | |||
| 248 | ### Cookie No Secure Flag (p10-009 — LOW) | ||
| 249 | |||
| 250 | - **Severity:** LOW | ||
| 251 | - **Summary:** Session cookie lacks `Secure` flag. With localhost-bound traffic behind an HTTPS-terminating reverse proxy, the cookie travels unencrypted between proxy and app. | ||
| 252 | - **Key Code Reference:** `src/auth.rs:83-86` — cookie string missing `; Secure` | ||
| 253 | - **PoC Status:** draft-only | ||
| 254 | - **Detailed Report:** `piolium/findings-draft/p10-009-cookie-no-secure-flag.md` | ||
| 255 | |||
| 256 | --- | ||
| 257 | |||
| 258 | ### Mod Write Before DB Orphan (p10-014 — LOW) | ||
| 259 | |||
| 260 | - **Severity:** LOW | ||
| 261 | - **Summary:** `process_jar_upload` writes the mod file to disk before inserting the DB record. A crash between these operations orphans the file (invisible to UI, never cleaned up). | ||
| 262 | - **Key Code Reference:** `src/routes/mods.rs:234-260` | ||
| 263 | - **PoC Status:** draft-only (static hypothesis) | ||
| 264 | - **Detailed Report:** `piolium/findings-draft/p10-014-mod-write-before-db-orphan.md` | ||
| 265 | |||
| 266 | --- | ||
| 267 | |||
| 268 | ### Backup Orphan Race (p10-015 — LOW) | ||
| 269 | |||
| 270 | - **Severity:** LOW | ||
| 271 | - **Summary:** `create_backup` creates the ZIP file before inserting the DB record. Concurrent instance deletion during ZIP creation causes a silently-swallowed FK violation, leaving orphaned ZIP files on disk. | ||
| 272 | - **Key Code Reference:** `src/routes/backups.rs:85-139` | ||
| 273 | - **PoC Status:** draft-only (requires dynamic test) | ||
| 274 | - **Detailed Report:** `piolium/findings-draft/p10-015-backup-orphan-race.md` | ||
| 275 | |||
| 276 | --- | ||
| 277 | |||
| 278 | ## Attack Surface Summary | ||
| 279 | |||
| 280 | The complete attack surface inventory is documented at: | ||
| 281 | |||
| 282 | | Artifact | Path | Description | | ||
| 283 | |----------|------|-------------| | ||
| 284 | | **Knowledge Base Report** | `piolium/attack-surface/knowledge-base-report.md` | Full architecture model, DFD/CFD slices, trust boundaries, component inventory (18 sections) | | ||
| 285 | | **Attack Pattern Registry** | `piolium/attack-pattern-registry.json` | 5 confirmed attack patterns (AP-001 through AP-005) with 12+32+1+31+1=77 total confirmed instances | | ||
| 286 | | **Manual Attack Surface Inventory** | `piolium/attack-surface/manual-attack-surface-inventory.md` | 38 HTTP routes catalogued with entry points, sinks, trust boundaries | | ||
| 287 | | **Source-Sink Flow Paths** | `piolium/attack-surface/source-sink-flows-all-severities.md` | All data flow paths from attacker input to dangerous sinks | | ||
| 288 | | **Unauthenticated Surface** | `piolium/attack-surface/unauthenticated-surface.md` | Routes accessible without authentication | | ||
| 289 | | **Spec Gap Summary** | `piolium/attack-surface/spec-gap-summary.md` | RFC/contract gaps (Java .properties, Argon2, RCON protocol, Axum contracts) | | ||
| 290 | | **Variant Analysis Summary** | `piolium/attack-surface/variant-summary.md` | Phase 12 variant search results | | ||
| 291 | | **State/Concurrency Summary** | `piolium/attack-surface/state-concurrency-summary.md` | Race conditions, TOCTOU, ordering bugs | | ||
| 292 | | **Advisory Summary** | `piolium/attack-surface/advisory-summary.md` | External advisory intelligence | | ||
| 293 | | **SBOM** | `piolium/attack-surface/sbom.json` | Software bill of materials | | ||
| 294 | | **Patch Bypass Summary** | `piolium/attack-surface/patch-bypass-summary.md` | Prior-sec patch regression analysis | | ||
| 295 | |||
| 296 | ### Key Attack Surface Dimensions | ||
| 297 | |||
| 298 | 1. **38 HTTP routes** — 3 public POST, 1 public GET, 31 authenticated POST, 3 authenticated GET | ||
| 299 | 2. **5 attacker-controlled input sources** — form fields (instance name, seed, username, motd), multipart uploads (JAR/ZIP files), path parameters (instance ID, backup ID, mod ID), cookie header, outbound Mojang API response | ||
| 300 | 3. **4 dangerous sinks** — filesystem (create_dir_all, remove_dir_all, File::create, read_to_string), RCON TCP (send_command_async), Docker Unix socket (bollard API), SQLite (rusqlite) | ||
| 301 | 4. **3 trust boundaries** — unauthenticated→authenticated (session cookie), webUI→filesystem (path traversal), webUI→RCON/Minecraft (command injection) | ||
| 302 | |||
| 303 | --- | ||
| 304 | |||
| 305 | ## Coverage Gaps | ||
| 306 | |||
| 307 | ### Pipeline Failure: Findings Never Promoted | ||
| 308 | |||
| 309 | The most significant gap is that **the findings promotion pipeline terminated prematurely** between Phase 12 (variant analysis) and Phase 15 (report assembly): | ||
| 310 | |||
| 311 | - `piolium/findings/` — **does not exist** — no finding was ever promoted from draft | ||
| 312 | - No PoC scripts were built for any finding | ||
| 313 | - No `report.md` files were generated with structured severity/PoC/evidence sections | ||
| 314 | - No execution evidence was collected | ||
| 315 | - Only 4 of 17 findings received adversarial review (P11 cold verification) | ||
| 316 | - No `metadata.json` files were created for variant-parent tracking | ||
| 317 | - Validating script (`validate_phase_output.py`) not present at expected path | ||
| 318 | |||
| 319 | ### Static Analysis Gaps | ||
| 320 | |||
| 321 | - **CodeQL database could not be built** — the Rust analyzer version mismatch prevented database construction. Only structural extraction (call graphs, entry points, sinks) was performed from manual review. | ||
| 322 | - **Semgrep Pro** not available — cross-file taint analysis was not performed for Rust | ||
| 323 | - **No automated sink-to-source tracing** — all data flow mapping was manual | ||
| 324 | |||
| 325 | ### Multi-User Deployment Not Analyzed for Scale | ||
| 326 | |||
| 327 | - The flat authorization model (p10-010) was assessed as MEDIUM based on single-user assumptions | ||
| 328 | - In a multi-user deployment (2+ users), the severity of p10-010 + p10-001 + p10-005 escalates to CRITICAL | ||
| 329 | - No instance-level access control, audit logging, or rate limiting for user actions | ||
| 330 | |||
| 331 | ### Encryption & Key Management | ||
| 332 | |||
| 333 | - No encryption at rest (p10-017) | ||
| 334 | - RCON passwords stored in plaintext | ||
| 335 | - No TLS termination handled by the application (delegated to reverse proxy) | ||
| 336 | - Session IDs stored in unencrypted SQLite | ||
| 337 | |||
| 338 | ### Supply Chain Not Scanned | ||
| 339 | |||
| 340 | - 19 Rust crate dependencies listed in SBOM but no automated vulnerability scan (cargo-audit not integrated) | ||
| 341 | - Outbound Mojang API calls not constrained (no TLS pinning, no timeout documented) | ||
| 342 | |||
| 343 | --- | ||
| 344 | |||
| 345 | ## Methodology Notes | ||
| 346 | |||
| 347 | ### Chamber Workspace Summary | ||
| 348 | |||
| 349 | The Phase 10 adversarial review chamber processed 37 finding candidates across 6 clusters: | ||
| 350 | |||
| 351 | | Cluster | Attack Class | Candidates | Valid Survivors | Rejected | | ||
| 352 | |---------|-------------|-----------|-----------------|----------| | ||
| 353 | | path-traversal | Path Traversal & File System | 7 | 1 (H-01) | 6 | | ||
| 354 | | injection | Injection (RCON, Docker, Properties) | 5 | 2 (H-08, H-10) | 3 | | ||
| 355 | | auth-session | Auth & Session Management | 9 | 4 (H-13, H-14, H-16, H-17) | 5 | | ||
| 356 | | state-concurrency | State & Concurrency | 6 | 5 | 1 | | ||
| 357 | | denial-of-service | Denial of Service | 4 | 1 (H-28) | 3 | | ||
| 358 | | security-architecture | Security Architecture | 6 | 1 (H-33) | 5 | | ||
| 359 | | **Total** | | **37** | **17** | **20** | | ||
| 360 | |||
| 361 | **Rejection breakdown:** 7 FALSE POSITIVE, 8 DROP, 5 DUPLICATE | ||
| 362 | |||
| 363 | ### Cold Verification (P11) | ||
| 364 | |||
| 365 | 4 findings subjected to adversarial review: | ||
| 366 | - **p10-001** (Instance Name Path Traversal): CONFIRMED — severity HIGH→HIGH (no change) | ||
| 367 | - **p10-003** (Level-Seed Properties Injection): CONFIRMED — severity HIGH→HIGH (no change) | ||
| 368 | - **p10-004** (RCON Password Exposed): DISPROVED — password never rendered in edit.html template (Askama explicit-field model) | ||
| 369 | - **p10-005** (Missing CSRF Protection): CONFIRMED — severity HIGH→MEDIUM challenged, rejected by chamber (CSRF+chain attacks warrant HIGH) | ||
| 370 | |||
| 371 | ### Variant Analysis (P12) | ||
| 372 | |||
| 373 | 4 new variant instances discovered from AP-001 root cause search: | ||
| 374 | - All 4 come from instance name path traversal pattern | ||
| 375 | - Zero variants found for the remaining 13 findings | ||
| 376 | - The path traversal pattern (AP-001) is the most pervasive vulnerability class with 12 total confirmed instances | ||
| 377 | |||
| 378 | ### Attack Pattern Registry | ||
| 379 | |||
| 380 | 5 attack patterns documented in `piolium/attack-pattern-registry.json`: | ||
| 381 | - AP-001: Path Traversal via Unsanitized Instance Name (12 instances) | ||
| 382 | - AP-002: RCON Command Injection via Unsanitized Username (6 instances) | ||
| 383 | - AP-003: Java Properties Injection via Unsanitized Seed Value (1 instance) | ||
| 384 | - AP-004: Missing CSRF Protection (31 endpoints) | ||
| 385 | - AP-005: No Server-Side Session TTL Enforcement (1 query) | ||
| 386 | |||
| 387 | --- | ||
| 388 | |||
| 389 | ## Conclusion | ||
| 390 | |||
| 391 | This audit catalogued 17 validated vulnerabilities across the minecraft-ui codebase. The most severe class — **path traversal via unsanitized instance names** — creates a systemic risk: any authenticated user can read, write, and delete files anywhere the webUI process can access. The absence of CSRF protection (31 endpoints) and per-resource authorization means that a single compromised account (or a cross-site request from a victim's browser) grants full control over all server instances, their files, and their Minecraft consoles. | ||
| 392 | |||
| 393 | The application's security posture is appropriate _only_ for its documented single-user, localhost-only deployment model. Any deployment that: | ||
| 394 | - Exposes the WebUI beyond localhost | ||
| 395 | - Adds a second user account | ||
| 396 | - Sits behind a reverse proxy without CSRF middleware at the proxy layer | ||
| 397 | - Does not validate instance names at the input layer | ||
| 398 | |||
| 399 | ...faces unacceptable risk of data loss, privilege escalation, and persistent compromise. | ||
| 400 | |||
| 401 | **Pipeline note:** The audit's findings-draft→findings promotion failed. The 17 validated findings, 4 variants, and 5 attack patterns documented in this report are derived from draft-level analysis without confirmed PoC execution. A follow-up audit should focus on building working PoCs for the critical and high-severity findings and executing them against a deployed instance to confirm exploitability and impact in a real environment. | ||
| 402 | |||
| 403 | --- | ||
| 404 | |||
| 405 | ## Deferred Findings (triage skip) | ||
| 406 | |||
| 407 | *No findings were triaged to `piolium/findings-deferred/`. The `piolium/findings-deferred/` directory does not exist because no findings were promoted past the draft stage. All 17 validated findings remain at investigative (draft) status.* \ No newline at end of file | ||
