diff options
Diffstat (limited to 'piolium/attack-surface/spec-gap-summary.md')
| -rw-r--r-- | piolium/attack-surface/spec-gap-summary.md | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/piolium/attack-surface/spec-gap-summary.md b/piolium/attack-surface/spec-gap-summary.md new file mode 100644 index 0000000..5e52e81 --- /dev/null +++ b/piolium/attack-surface/spec-gap-summary.md | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | # P7 Spec, Framework Contract & Parser Gap Summary | ||
| 2 | |||
| 3 | **Target:** salmon/minecraft-ui | ||
| 4 | **Date:** 2025-07-29 | ||
| 5 | **Phase:** P7 — Specification, Framework Contract & Parser Differential Analysis | ||
| 6 | **Mode:** deep | ||
| 7 | |||
| 8 | --- | ||
| 9 | |||
| 10 | ## Overview | ||
| 11 | |||
| 12 | This report documents gaps between authoritative specifications, framework contracts, and the codebase implementation for salmon/minecraft-ui. The analysis covers: | ||
| 13 | |||
| 14 | - **RFC 9106** (Argon2) — password hashing parameter compliance | ||
| 15 | - **Java `.properties` format** (Oracle spec) — server.properties parsing | ||
| 16 | - **Source RCON Protocol** (Minecraft community spec — wiki.vg/RCON) — packet handling | ||
| 17 | - **RFC 6265** (HTTP State Management) — cookie parsing and session handling | ||
| 18 | - **Axum Framework Contracts** — middleware ordering, extension-based auth, multipart boundary parsing | ||
| 19 | - **Hidden Control Channels** — Docker environment injection, file path construction, host header trust | ||
| 20 | - **Deployment Assumptions** — reverse proxy, localhost binding, no TLS | ||
| 21 | |||
| 22 | --- | ||
| 23 | |||
| 24 | ## Spec Gap Candidates | ||
| 25 | |||
| 26 | ### Gap 1: Java `.properties` — Missing Escape Sequence Processing | ||
| 27 | |||
| 28 | - **Spec:** Oracle Java `.properties` file format | ||
| 29 | - **Requirement:** The format specifies escape sequences: `\n`, `\r`, `\t`, `\\`, `\uXXXX`, `\ ` (literal space), `\!`, `\#`, `\=`, `\:`, and multi-line continuation with trailing `\`. | ||
| 30 | - **Code Path:** `src/properties.rs:13-19` — `read_properties()` splits on `=` or `:` without escaping | ||
| 31 | - **Gap Type:** parsing | canonicalization | ||
| 32 | - **Impact:** Values containing `=`, `:`, or Java escape sequences in `server.properties` are misparsed. An attacker who can control `server.properties` values (e.g., via `level-seed` setting) can inject fake key-value pairs or corrupt parsing. The `level-seed` value in `server.properties` is written by `regenerate_world` via a line-replacement loop that creates `level-seed={seed}` — the seed value is not escaped for the `.properties` format. | ||
| 33 | - **Attack Vector:** Auth'd user sets a seed containing `\nlevel-type=FLAT\n` which, when written to `server.properties` via the naive line-replacement in `regenerate_world`, injects additional properties. On next server start, the injected properties alter server behavior. | ||
| 34 | - **Exploit Conditions:** Auth'd user; instance not active; access to `regenerate_world` handler. | ||
| 35 | - **Severity:** MEDIUM | ||
| 36 | - **Evidence:** `src/properties.rs:13-19` uses `split_once('=')` and `split_once(':')` with no escape processing. Java `.properties` spec requires backslash escaping for separators. | ||
| 37 | |||
| 38 | ### Gap 2: Argon2id (RFC 9106) — Non-Recommended Default Parameters | ||
| 39 | |||
| 40 | - **RFC/Spec:** RFC 9106, Sections 3.1, 4, 7.4 | ||
| 41 | - **Requirement:** RFC 9106 §4 recommends as FIRST option: `Argon2id` with `t=1`, `p=4`, `m=2^21` (2 GiB). §7.4 states "Argon2id variant with t=1 and 2 GiB memory is the FIRST RECOMMENDED option". Salt SHOULD be 16 bytes and SHOULD be unique per password. | ||
| 42 | - **Code Path:** `src/auth.rs:28` — `Argon2::default().hash_password(password.as_bytes(), &salt)?` | ||
| 43 | - **Gap Type:** missing-check | parameter-choice | ||
| 44 | - **Impact:** `Argon2::default()` uses m_cost=19456 KiB (~19 MiB), t_cost=2 iterations, p_cost=1 lane. This is ~100x less memory than RFC 9106 recommends. While not a compliance vulnerability (any parameters are valid per the algorithm spec), the weak defaults reduce brute-force attack resistance relative to the RFC's recommended "default setting for all environments." | ||
| 45 | - **Attack Vector:** Offline password hash cracking benefits from the 19 MiB vs 2 GiB memory reduction (~100x cheaper per guess). | ||
| 46 | - **Exploit Conditions:** Attacker obtains password hash from database (SQLite file access or SQL injection). | ||
| 47 | - **Impact:** Accelerated offline brute-force by ~100x less memory per hash evaluation. | ||
| 48 | - **Severity:** MEDIUM | ||
| 49 | - **Evidence:** `src/auth.rs:28` uses `Argon2::default()` (m=19456 KiB, t=2, p=1). RFC 9106 §4 recommends m=2^21 KiB (2 GiB), t=1, p=4 for pass hashing. Source: `/home/salmon/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/argon2-0.5.3/src/params.rs:29` defines DEFAULT_M_COST = 19 * 1024. | ||
| 50 | |||
| 51 | ### Gap 3: RCON Protocol — Insufficient Response Read After EXECCOMMAND | ||
| 52 | |||
| 53 | - **Spec:** Source RCON Protocol (Minecraft community convention — wiki.vg/RCON) | ||
| 54 | - **Requirement:** After sending `SERVERDATA_EXECCOMMAND` (type 2), the server responds with a `SERVERDATA_RESPONSE_VALUE` (type 0) packet containing the command output, followed by a second `SERVERDATA_RESPONSE_VALUE` (type 0) as an end marker. Multi-packet responses are possible. | ||
| 55 | - **Code Path:** `src/rcon.rs:72-76` — reads one response, then ignores errors on second | ||
| 56 | - **Gap Type:** state-machine | error-handling | ||
| 57 | - **Impact:** Reads only one guaranteed response. Fragmented multi-packet responses are truncated. The comment acknowledges this: "some servers send it, some don't; ignore errors." | ||
| 58 | - **Attack Vector:** Long RCON command output (> 1400 bytes) gets truncated. | ||
| 59 | - **Exploit Conditions:** Any RCON command with output exceeding ~1400 bytes. | ||
| 60 | - **Impact:** Truncated command response (information disclosure). | ||
| 61 | - **Severity:** LOW — protocol compliance only, no direct security bypass. | ||
| 62 | - **Evidence:** `src/rcon.rs:72` — `let response = self.read_packet(&mut stream)?;` followed by `let _ = self.read_packet(&mut stream);` with error ignored. | ||
| 63 | |||
| 64 | ### Gap 4: Java `.properties` — Key Continuation and Whitespace Separator Not Supported | ||
| 65 | |||
| 66 | - **Spec:** Oracle Java `.properties` format | ||
| 67 | - **Code Path:** `src/properties.rs:13-19` | ||
| 68 | - **Gap Type:** parsing | ||
| 69 | - **Impact:** Lines with whitespace-only separators (e.g., `key value`) are silently ignored. | ||
| 70 | - **Severity:** LOW — informational; conventional Minecraft server.properties uses `=`. | ||
| 71 | |||
| 72 | ### Gap 5: Auth Middleware — Manual Cookie Parsing (RFC 6265) | ||
| 73 | |||
| 74 | - **RFC/Spec:** RFC 6265 (HTTP State Management), Sections 4.2.2, 5 | ||
| 75 | - **Code Path:** `src/auth.rs:105-118` — manual `split(';')` then `split_once('=')` | ||
| 76 | - **Gap Type:** parsing | canonicalization | ||
| 77 | - **Impact:** The parser does NOT strip OWS around cookie names per RFC 6265 §4.2.2. Does NOT handle quoted cookie values. | ||
| 78 | - **Severity:** LOW — well-formed browser cookies not affected; no auth bypass possible. | ||
| 79 | |||
| 80 | ### Gap 6: Framework Contract — Logout Without Auth Middleware | ||
| 81 | |||
| 82 | - **Contract:** Axum middleware; all state-changing ops require auth. | ||
| 83 | - **Code Path:** `src/routes/mod.rs:29` — `/logout` on outer router, not in `protected` group. | ||
| 84 | - **Gap Type:** middleware-ordering | ||
| 85 | - **Impact:** Anonymous requests can delete sessions (requires valid session ID — UUIDv4 impractical). | ||
| 86 | - **Severity:** LOW — theoretical; UUIDv4 unpredictability prevents exploitation. | ||
| 87 | |||
| 88 | ### Gap 7: Framework Contract — No Server-Side Session TTL Enforcement | ||
| 89 | |||
| 90 | - **Contract:** Session cookie Max-Age=86400 but no server-side expiration check. | ||
| 91 | - **Code Path:** `src/auth.rs:93` — `Max-Age=86400`; `src/auth.rs:108-115` — no `created_at` check. | ||
| 92 | - **Gap Type:** missing-check | ||
| 93 | - **Impact:** Session rows remain valid indefinitely in DB. Stolen session ID (via XSS, log access, DB leak) works forever. | ||
| 94 | - **Attack Vector:** Session ID leak → permanent session hijacking. No server-side TTL means an attacker who obtains a session ID can reuse it indefinitely, long after the Max-Age expires. | ||
| 95 | - **Exploit Conditions:** Attacker must first obtain a valid session ID. | ||
| 96 | - **Impact:** Session hijacking window extended indefinitely. | ||
| 97 | - **Severity:** MEDIUM | ||
| 98 | - **Evidence:** `src/auth.rs:93` sets `Max-Age=86400` but `src/auth.rs:108-115` does `WHERE s.id = ?1` with no date filter. | ||
| 99 | |||
| 100 | ### Gap 8: Hidden Control Channel — Instance MOTD Injected Into Docker Env Without Sanitization | ||
| 101 | |||
| 102 | - **Contract:** Docker env vars derived from instance settings; Docker env parsing splits on `\n`. | ||
| 103 | - **Code Path:** `src/docker.rs:103` — `format!("MOTD={}", instance.motd)` | ||
| 104 | - **Gap Type:** hidden-control-channel | missing-check | ||
| 105 | - **Impact:** MOTD containing newline characters (`%0a`) injects arbitrary additional env vars into the Docker container. For example, MOTD=`hello\nEULA=FALSE\nONLINE_MODE=true` would override security-critical env vars. All Docker env vars (VERSION, TYPE, MEMORY, MAX_PLAYERS, DIFFICULTY, RCON_PASSWORD, GAMEMODE, LEVEL_TYPE, LEVEL_SEED, PVP, MOTD) are set without sanitization. | ||
| 106 | - **Attack Vector:** Auth'd user edits instance MOTD → saves → triggers container restart → injected env vars alter Docker container behavior. | ||
| 107 | - **Exploit Conditions:** Auth'd user; container restarted after instance edit. | ||
| 108 | - **Impact:** Full Docker container environment variable injection — could override EULA, ONLINE_MODE, RCON settings, or inject arbitrary env vars. | ||
| 109 | - **Severity:** HIGH | ||
| 110 | - **Evidence:** `src/docker.rs:103` — `format!("MOTD={}", instance.motd)`. No escaping. MOTD sourced from `src/routes/instances.rs:206` via `Form<EditInstanceForm>` without sanitization. | ||
| 111 | |||
| 112 | ### Gap 9: Hidden Control Channel — Instance Name Used in File Paths Without Sanitization | ||
| 113 | |||
| 114 | - **Contract:** Instance name → filesystem path. | ||
| 115 | - **Code Path:** `src/routes/instances.rs:173` — `server_directory.join("instances").join(&form.name)` | ||
| 116 | - **Gap Type:** hidden-control-channel | missing-check | ||
| 117 | - **Impact:** `std::path::Path::join` DOES traverse on `..` prefix. Instance name `../../evil` results in path outside intended tree. All subsequent operations (mod uploads, world import, backups) land at attacker-chosen locations. Files created: instance data directory, mod JAR files, world files. | ||
| 118 | - **Attack Vector:** Auth'd user creates instance with `../../evil_name` → data dir created at `server_directory/instances/../../evil_name/data` = `server_directory/evil_name/data`. Then upload mod or import world to write files at traversed location. | ||
| 119 | - **Exploit Conditions:** Auth'd user. | ||
| 120 | - **Impact:** Arbitrary file write relative to server_directory (WebUI process user permissions). | ||
| 121 | - **Severity:** HIGH | ||
| 122 | - **Evidence:** `src/routes/instances.rs:173` — `let instance_dir = state.config.server_directory.join("instances").join(&form.name).join("data");` No validation of `form.name` for `..` or path separators. Also stored in DB at `src/routes/instances.rs:119` without sanitization. | ||
| 123 | |||
| 124 | ### Gap 10: Hidden Control Channel — Content-Type Boundary Parsing Trusts Attacker Input | ||
| 125 | |||
| 126 | - **Contract:** Multer multipart parser; boundary from Content-Type header. | ||
| 127 | - **Code Path:** `src/routes/mods.rs:140-149` — boundary parsed without validation | ||
| 128 | - **Gap Type:** hidden-control-channel | missing-check | ||
| 129 | - **Impact:** Attacker-controlled boundary value fed to `multer::Multipart::with_constraints`. While multer validates boundaries internally, a crafted boundary could cause field misidentification, size limit bypass, or field-skipping behavior. | ||
| 130 | - **Attack Vector:** Send crafted Content-Type header with boundary overlapping body content: malformed multipart parsing. | ||
| 131 | - **Exploit Conditions:** Auth'd user uploading mods. | ||
| 132 | - **Impact:** Potential DoS or file size limit bypass. | ||
| 133 | - **Severity:** MEDIUM | ||
| 134 | - **Evidence:** `src/routes/mods.rs:141-148` — boundary parsed from user-supplied `Content-Type`. | ||
| 135 | |||
| 136 | ### Gap 11: Hidden Control Channel — All Instance Settings In Docker Env Without Sanitization | ||
| 137 | |||
| 138 | - **Contract:** All instance settings → Docker env. | ||
| 139 | - **Code Path:** `src/docker.rs:87-113` | ||
| 140 | - **Gap Type:** hidden-control-channel | missing-check | ||
| 141 | - **Impact:** Beyond MOTD (Gap 8), LEVEL_SEED, LEVEL_TYPE, GAMEMODE, DIFFICULTY, VERSION, RCON_PASSWORD are all injected without sanitization. | ||
| 142 | - **Severity:** HIGH — combined with Gap 8. | ||
| 143 | |||
| 144 | ### Gap 12: Framework Contract — RCON Password Exposed to All Authenticated Users | ||
| 145 | |||
| 146 | - **Contract:** Supposed role-based access control for sensitive instance data. | ||
| 147 | - **Code Path:** `src/routes/instances.rs:313` — `rcon_password: row.get(15)?` in `edit_form` | ||
| 148 | - **Gap Type:** missing-check | ||
| 149 | - **Impact:** Any authenticated user can view any instance's RCON password by visiting `/instances/{id}/edit`. No superuser or instance-ownership check. RCON password grants full Minecraft server control. | ||
| 150 | - **Attack Vector:** Auth'd user navigates to `/instances/1/edit` → sees RCON password → uses RCON client to issue arbitrary commands. | ||
| 151 | - **Exploit Conditions:** Auth'd user. | ||
| 152 | - **Impact:** Full Minecraft server compromise via RCON — whitelist bypass, data destruction, command execution. | ||
| 153 | - **Severity:** HIGH | ||
| 154 | - **Evidence:** `src/routes/instances.rs:313` — `rcon_password` selected and returned. No superuser check. Compare with `src/routes/accounts.rs:35` where checks exist. | ||
| 155 | |||
| 156 | --- | ||
| 157 | |||
| 158 | ## Cross-Reference with Phase 3 Domain Attack Research | ||
| 159 | |||
| 160 | - **Gap 8/11** (Docker env injection) — NOT covered in Phase 3. New finding. | ||
| 161 | - **Gap 9** (Instance name path traversal) — NOT specifically covered. Phase 3 identified mod filename traversal (T2) but not instance name as traversal vector. | ||
| 162 | - **Gap 12** (RCON password exposure) — Covered in Phase 3 as T5. | ||
| 163 | - **Gap 7** (No session TTL) — Covered in Phase 3 domain research. | ||
| 164 | - **Gap 2** (Argon2 defaults) — NOT covered in Phase 3. | ||
