{"id":"GHSA-5g75-477j-2c2f","summary":"LaunchServer FileServerHandler has an unauthenticated path traversal issue","details":"### Summary\nAn unauthenticated path traversal in the LaunchServer HTTP file server (`FileServerHandler`) lets any remote actor read **any file** readable by the LaunchServer process (e.g. `../../../../etc/passwd`). This is a generic arbitrary-file-read primitive, so the fix must address the traversal itself, not any specific file.\n\nThe readable files include the server's own secrets, which turns this from information disclosure into full compromise: the ECDSA private key that signs access JWTs (`.keys/ecdsa_id`), the refresh-token salt (`.keys/legacySalt`), and `LaunchServer.json` (database credentials). With the signing key an attacker mints a valid access token for any account, including admins. That is a full authentication bypass. Pre-auth, default config, port 9274.\n\n**Affected:** GravitLauncher LaunchServer ≤ 5.7.11 (the LaunchServer application; the published `pro.gravit.launcher:*-api` Maven artifacts do not contain the vulnerable code).\n\n### Details\nIn `FileServerHandler.channelRead0`:\n\n```java\npath = Paths.get(IOHelper.getPathFromUrlFragment(uri)).normalize().toString().substring(1); // line 194\nFile file = base.resolve(path).toFile();                                                     // line 200 - no second normalize()\n```\n\n`substring(1)` blindly strips a leading slash, assuming the request-target always starts with `/`. Netty's `HttpServerCodec` accepts a request-target **without** a leading slash verbatim (`decoderResult().isSuccess() == true`). For such a target, `normalize()` cannot collapse the leading `..`, `substring(1)` turns `../` into `./` (leaving the remaining `..`), and `base.resolve(path)`, which is not re-normalized, resolves **outside** `updatesDir`.\n\n`file.isHidden()` (line 201) is checked only on the final path component, so targets that don't start with a dot (`ecdsa_id`, `rsa_id`, `legacySalt`, `LaunchServer.json`) are served even with `showHiddenFiles=false`.\n\nThe file server is enabled by default (`netty.fileServerEnabled=true`) and bound to `0.0.0.0:9274`. No auth handler precedes `FileServerHandler`; `WebSocketServerProtocolHandler(\"/api\")` forwards non-WebSocket / non-`/api` requests down to it, so the attack is a plain HTTP GET (no WebSocket).\n\n### PoC\nReproduced on a from-source build of v5.7.11 (Netty 4.2.12).\n**Must use a raw socket.** curl/browsers/HTTP libraries normalize the path and prepend `/`, hitting the safe branch (false \"not reproducible\").\n\n```\nprintf 'GET ../../.keys/ecdsa_id HTTP/1.1\\r\\nHost: x\\r\\n\\r\\n' | nc \u003chost\u003e 9274\n```\n\nReturns the raw ECDSA private-key bytes. Same for `../../.keys/rsa_id`, `../../.keys/legacySalt`, `../../LaunchServer.json`. `%2e%2e/...` (no leading slash) also works. Depth-robust arbitrary read: `../../../../../../etc/passwd`.\nControl (confirms the root cause): `GET /../../.keys/ecdsa_id` (WITH leading slash) → 404. Only the no-leading-slash form escapes.\n\n### Impact\nUnauthenticated remote read of any file the process can access. What that exposes:\n- `.keys/ecdsa_id`: the key that signs access JWTs. With it, an attacker mints a valid token for any account, including admins, so this is a full authentication bypass.\n- `.keys/legacySalt`: lets an attacker forge refresh tokens.\n- `LaunchServer.json`: database credentials.\n- Any other file readable by the process (config, logs, system files).\n\nDeployment note: a normalizing L7 reverse proxy (stock nginx `location / { proxy_pass ...; }`) rejects the no-leading-slash request (400) and collapses leading-slash traversal, blocking the primary vector. But the default bind is `0.0.0.0:9274`, so protection relies on firewalling the backend port; L4/TCP proxies (HAProxy TCP, nginx `stream`, CF Spectrum) and direct exposure remain exploitable.\n\n### Suggested fix\n1. Re-`normalize()` after `base.resolve(path)` and verify `resolved.startsWith(base)`.\n2. Reject request-targets that don't start with `/` (400).\n3. Default-bind to `127.0.0.1`; store `.keys` outside `updatesDir`.","aliases":["CVE-2026-54617"],"modified":"2026-07-02T21:11:31.683686Z","published":"2026-07-02T20:49:18Z","database_specific":{"github_reviewed_at":"2026-07-02T20:49:18Z","nvd_published_at":null,"cwe_ids":["CWE-200","CWE-22","CWE-522"],"severity":"CRITICAL","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/GravitLauncher/Launcher/security/advisories/GHSA-5g75-477j-2c2f"},{"type":"PACKAGE","url":"https://github.com/GravitLauncher/Launcher"}],"affected":[{"package":{"name":"pro.gravit.launcher:launchserver-api","ecosystem":"Maven","purl":"pkg:maven/pro.gravit.launcher/launchserver-api"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"5.7.11"}]}],"versions":["5.0.10","5.0.6","5.0.7","5.0.8","5.1.0","5.1.10","5.1.6","5.1.8","5.1.9","5.2.0","5.2.1","5.2.10","5.2.11","5.2.12","5.2.13","5.2.2","5.2.3","5.2.5","5.2.6","5.2.7","5.2.8","5.2.9","5.3.0","5.3.1","5.3.2","5.3.3","5.3.5","5.3.6","5.4.0","5.4.3","5.4.4","5.5.0","5.5.1","5.5.2","5.5.3","5.5.4","5.6.0","5.6.1","5.6.2","5.6.3","5.6.7","5.6.8","5.6.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-5g75-477j-2c2f/GHSA-5g75-477j-2c2f.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}]}