summaryrefslogtreecommitdiffstats
path: root/piolium/attack-surface/unauthenticated-surface.md
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--piolium/attack-surface/unauthenticated-surface.md110
1 files changed, 110 insertions, 0 deletions
diff --git a/piolium/attack-surface/unauthenticated-surface.md b/piolium/attack-surface/unauthenticated-surface.md
new file mode 100644
index 0000000..d899854
--- /dev/null
+++ b/piolium/attack-surface/unauthenticated-surface.md
@@ -0,0 +1,110 @@
1# Unauthenticated Attack Surface
2
3Reachable by an anonymous attacker — no valid session, token, or API key.
4
5**Coverage**: 4 entry points | 3 by-design public | 1 missing-guard | 0 middleware-gap
6**Auth model**: Cookie-based session (mcui_session UUIDv4) via `require_auth` middleware (src/auth.rs:55). Login sets Set-Cookie with HttpOnly; middleware extracts cookie header and queries sessions table.
7**Coverage gaps**: None — all routes are statically registered in the axum Router at src/routes/mod.rs. No reflection-based or dynamically-registered handlers.
8
9## Pre-Auth HTTP / API Routes
10
11| # | Method | Path | Handler (file:line) | Why pre-auth | Notable inputs / sinks | Blast radius |
12|---|--------|------|---------------------|--------------|------------------------|--------------|
13| 1 | GET | `/login` | `login_page` (src/routes/mod.rs:53) | by-design | None — renders static template | None — no side effects, no data access |
14| 2 | POST | `/login` | `authenticate` (src/auth.rs:34) | by-design | `username`, `password` form fields → Argon2id password hash verification → session INSERT | Account compromise if credentials brute-forced (no rate limiting — see p4-005). Session fixation not possible (new UUID on each login). |
15| 3 | POST | `/logout` | `logout` (src/auth.rs:130) | **missing-guard** (p4-008) | Cookie header → raw string split on `;` and `=` → session ID → `DELETE FROM sessions WHERE id = ?1` | Session deletion if valid session ID known. UUIDv4 is unpredictable (122 bits). Low practical risk but defense-in-depth violation. |
16| 4 | GET | `/static/*` | `ServeDir::new("static")` (src/routes/mod.rs:28) | by-design | File path after `/static/` → static file read | Static file read only. Directory contains only `style.css` and `htmx.min.js`. No user data, no secrets, no configuration. |
17
18## Other Unauthenticated Entry Points
19
20| Kind | Entry point (file:line) | Why pre-auth | Notes |
21|------|-------------------------|--------------|-------|
22| **Static file server** | `ServeDir::new("static")` mounted at Router level (src/routes/mod.rs:28) | by-design | Serves `static/` directory contents (style.css, htmx.min.js). No auth, no session check, no dynamic content. Served directly without any middleware. |
23| **Logout session deletion** | `logout` handler (src/auth.rs:130) registered on **outer Router** (src/routes/mod.rs:20) | missing-guard (p4-008) | Covered by row #3 above. The handler is on the outer Router, NOT behind `require_auth`. Any anonymous request can reach it. The handler extracts session ID from Cookie header via raw string splitting (`split(';')` then `split_once('=')`) and attempts to delete the session from DB. |
24
25## Why `POST /logout` is classified `missing-guard`
26
27The route registration at src/routes/mod.rs:20:
28
29```rust
30Router::new()
31 .route("/login", axum::routing::get(login_page))
32 .route("/login", axum::routing::post(crate::auth::authenticate))
33 .route("/logout", axum::routing::post(crate::auth::logout)) // <-- OUTSIDE protected group
34 .merge(protected) // <-- protected has .layer(auth_required)
35```
36
37The `require_auth` middleware (src/auth.rs:55) is applied only to the `protected` router group. The logout handler processes the request without any session validation — it simply reads the Cookie header, extracts the session ID by splitting on `;` and `=`, and executes `DELETE FROM sessions WHERE id = ?1`.
38
39**Mitigating factors:**
401. Session IDs are UUIDv4 (122 bits of entropy) — practically unpredictable
412. Attacker would need a victim's exact session cookie value
423. No account information is leaked; only session deletion occurs
43
44**Risk if session IDs leak:**
45Session IDs could leak via:
46- Server access logs (if session IDs appear in logged cookie headers)
47- Error messages (currently suppressed by `unwrap_or` / `ok()` patterns)
48- Database backup exposure
49- Shoulder surfing / physical access
50
51If leaked, an attacker can force-logout the victim by sending `Cookie: mcui_session=<leaked-id>` to `/logout`.
52
53## Pre-Auth Risk Summary
54
55The webUI is designed to bind to `127.0.0.1` and sit behind a reverse proxy (nginx), which mitigates direct anonymous access. If exposed to the internet (even unintentionally via misconfiguration, Docker port mapping, or reverse proxy error):
56
57| Risk | Pre-auth endpoint | Impact | Existing draft |
58|------|-------------------|--------|----------------|
59| **HIGH** | `POST /login` (no rate limit) | Account compromise via credential brute-force | p4-005 |
60| **LOW** | `POST /logout` (missing guard) | Session deletion (requires valid session ID) | p4-008 |
61| **HIGH** | All POST endpoints (no CSRF) | Cross-site request forgery on any state-changing operation | p4-003 |
62| **NONE** | `GET /login` | None — static page | — |
63| **NONE** | `GET /static/*` | None — only CSS/JS files | — |
64
65## Auth Bypass Verification
66
67**All protected routes are correctly behind `require_auth` middleware:**
68- ✓ Dashboard (`/`)
69- ✓ Instances CRUD (`/instances`, `/instances/{id}/edit`, etc.)
70- ✓ World management (`/instances/{id}/world/*`)
71- ✓ Player management (`/players`, `/players/*`)
72- ✓ Backup management (`/instances/{id}/backups`, `/backups/{id}/*`)
73- ✓ Mod management (`/instances/{id}/mods`, `/instances/{id}/mods/*`)
74- ✓ Account management (`/accounts`, `/accounts/*`)
75- ✓ Password change (`/change-password`)
76
77**Routes NOT behind `require_auth` (intentional for auth protocol):**
78- `GET /login` — login form (by-design)
79- `POST /login` — credential verification (by-design)
80- `GET /static/*` — static assets (by-design)
81
82**Route NOT behind `require_auth` (anomaly):**
83- `POST /logout` — session deletion (missing-guard, p4-008)
84
85## Hidden Control Channel Verification
86
87| Channel | Present? | Can it bypass auth? |
88|---------|----------|---------------------|
89| Cookie header (`mcui_session`) | Yes, in `require_auth` + `logout` | No — session must exist in DB |
90| Content-Type boundary | Yes, in `upload_mod` handler | No — only affects file parsing |
91| X-Forwarded-* headers | **Not read anywhere** | N/A (safe) |
92| X-HTTP-Method-Override | **Not supported** | N/A (safe) |
93| X-Admin / X-Internal / X-Debug | **Not supported** | N/A (safe) |
94| Host header | **Not used for routing decisions** | N/A (safe) |
95
96## References to Existing Drafts
97
98The following p4 findings document pre-auth and authz issues in detail:
99
100| Draft | Class | Covers |
101|-------|-------|--------|
102| p4-003 | csrf | No CSRF on any POST endpoint |
103| p4-005 | missing-rate-limit | No rate limiting on POST /login |
104| p4-008 | missing-guard | POST /logout not behind auth middleware |
105| p4-009 | cookie-no-secure-flag | Cookie missing Secure flag |
106| p4-019 | cookie-parsing-naive | Naive cookie header string splitting |
107
108## Cross-Reference: Matrix Entry vs Unauthenticated Surface
109
110All entries in this file are derived from the authorization matrix (`piolium/attack-surface/public-routes-authz-matrix.md`). The matrix covers 37 total endpoints; 4 are reachable pre-auth as documented above. The remaining 33 require authentication via `require_auth` middleware.