{"id":"CVE-2026-54617","aliases":["EUVD-2026-82391","GHSA-5g75-477j-2c2f"],"status":"published","title":"GravitLauncher: Unauthenticated path traversal in LaunchServer FileServerHandler","description":"GravitLauncher is an open-source Minecraft launcher based on sashok724's v3. Prior to 5.7.12, an unauthenticated remote actor can send a raw HTTP request target without a leading slash to the default LaunchServer file server on port 9274. FileServerHandler.channelRead0 in components/launchserver/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java strips the first request-target character and resolves the remaining path against updatesDir without re-normalizing and verifying containment. This leaves parent-directory components in a no-leading-slash request and allows reading any file accessible to the LaunchServer process, including .keys/ecdsa_id, .keys/legacySalt, and LaunchServer.json. Disclosure of those files can expose signing keys, refresh-token material, and database credentials, enabling forged administrative access tokens and full authentication bypass. A normalizing L7 proxy may block the primary request form, but direct exposure and L4/TCP proxies remain affected, and netty.fileServerEnabled is enabled by default. This issue is fixed in 5.7.12.","severity":{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.03457,"percentile":0.89201,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"cwes":["CWE-200","CWE-22","CWE-522"],"published":"2026-07-02T20:49:18Z","modified":"2026-10-28T11:09:57Z","exploits":[{"source":"nuclei","id":"CVE-2026-54617","title":"GravitLauncher LaunchServer \u003c=5.7.11 - Unauthenticated Path Traversal to Arbitrary File Read","type":"detection template","url":"https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2026/CVE-2026-54617.yaml"}],"affected":[{"source":"ghsa","ecosystem":"Maven","package":"pro.gravit.launcher:launchserver-api","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"5.7.11"}]}]},{"source":"osv","ecosystem":"Maven","package":"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"]}],"products":[{"vendor":"gravitlauncher","product":"launcher"},{"vendor":"kolide","product":"launcher"}],"references":[{"url":"https://github.com/GravitLauncher/Launcher/security/advisories/GHSA-5g75-477j-2c2f","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/GravitLauncher/Launcher/commit/8114a1d4cfbcaab4c1c337d921597805d71dee3b","tags":["x_refsource_MISC","fix"]},{"url":"https://github.com/GravitLauncher/Launcher/releases/tag/v5.7.12","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-5g75-477j-2c2f"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/54xxx/CVE-2026-54617.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54617","tags":["advisory"]},{"url":"https://github.com/GravitLauncher/Launcher","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-54617","title":"GravitLauncher: Unauthenticated path traversal in LaunchServer FileServerHandler","description":"GravitLauncher is an open-source Minecraft launcher based on sashok724's v3. Prior to 5.7.12, an unauthenticated remote actor can send a raw HTTP request target without a leading slash to the default LaunchServer file server on port 9274. FileServerHandler.channelRead0 in components/launchserver/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java strips the first request-target character and resolves the remaining path against updatesDir without re-normalizing and verifying containment. This leaves parent-directory components in a no-leading-slash request and allows reading any file accessible to the LaunchServer process, including .keys/ecdsa_id, .keys/legacySalt, and LaunchServer.json. Disclosure of those files can expose signing keys, refresh-token material, and database credentials, enabling forged administrative access tokens and full authentication bypass. A normalizing L7 proxy may block the primary request form, but direct exposure and L4/TCP proxies remain affected, and netty.fileServerEnabled is enabled by default. This issue is fixed in 5.7.12.","status":"published","published":"2026-09-17T18:34:40.563Z","modified":"2026-09-18T18:33:25.74Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"cna"}],"cwes":["CWE-22","CWE-200","CWE-522"],"updated_at":"2026-11-02T13:38:09.774038Z"}],"euvd":[{"id":"EUVD-2026-82391","description":"GravitLauncher is an open-source Minecraft launcher based on sashok724's v3. Prior to 5.7.12, an unauthenticated remote actor can send a raw HTTP request target without a leading slash to the default LaunchServer file server on port 9274. FileServerHandler.channelRead0 in components/launchserver/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java strips the first request-target character and resolves the remaining path against updatesDir without re-normalizing and verifying containment. This leaves parent-directory components in a no-leading-slash request and allows reading any file accessible to the LaunchServer process, including .keys/ecdsa_id, .keys/legacySalt, and LaunchServer.json. Disclosure of those files can expose signing keys, refresh-token material, and database credentials, enabling forged administrative access tokens and full authentication bypass. A normalizing L7 proxy may block the primary request form, but direct exposure and L4/TCP proxies remain affected, and netty.fileServerEnabled is enabled by default. This issue is fixed in 5.7.12.","status":"published","published":"2026-09-17T18:34:40Z","modified":"2026-09-18T18:33:25Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"updated_at":"2026-11-02T17:28:07.574446Z"}],"ghsa":[{"id":"GHSA-5g75-477j-2c2f","title":"LaunchServer FileServerHandler has an unauthenticated path traversal issue","description":"### 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`.","status":"published","published":"2026-07-02T20:49:18Z","modified":"2026-07-02T20:49:20Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"cwes":["CWE-22","CWE-200","CWE-522"],"updated_at":"2026-11-02T14:40:38.48196Z"}],"nvd":[{"id":"CVE-2026-54617","description":"GravitLauncher is an open-source Minecraft launcher based on sashok724's v3. Prior to 5.7.12, an unauthenticated remote actor can send a raw HTTP request target without a leading slash to the default LaunchServer file server on port 9274. FileServerHandler.channelRead0 in components/launchserver/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java strips the first request-target character and resolves the remaining path against updatesDir without re-normalizing and verifying containment. This leaves parent-directory components in a no-leading-slash request and allows reading any file accessible to the LaunchServer process, including .keys/ecdsa_id, .keys/legacySalt, and LaunchServer.json. Disclosure of those files can expose signing keys, refresh-token material, and database credentials, enabling forged administrative access tokens and full authentication bypass. A normalizing L7 proxy may block the primary request form, but direct exposure and L4/TCP proxies remain affected, and netty.fileServerEnabled is enabled by default. This issue is fixed in 5.7.12.","status":"published","published":"2026-09-17T19:16:51.003Z","modified":"2026-09-24T21:25:27.05Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"secondary"}],"cwes":["CWE-22","CWE-200","CWE-522"],"updated_at":"2026-11-02T14:12:49.70655Z"}],"osv":[{"id":"CVE-2026-54617","title":"GravitLauncher: Unauthenticated path traversal in LaunchServer FileServerHandler","description":"GravitLauncher is an open-source Minecraft launcher based on sashok724's v3. Prior to 5.7.12, an unauthenticated remote actor can send a raw HTTP request target without a leading slash to the default LaunchServer file server on port 9274. FileServerHandler.channelRead0 in components/launchserver/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java strips the first request-target character and resolves the remaining path against updatesDir without re-normalizing and verifying containment. This leaves parent-directory components in a no-leading-slash request and allows reading any file accessible to the LaunchServer process, including .keys/ecdsa_id, .keys/legacySalt, and LaunchServer.json. Disclosure of those files can expose signing keys, refresh-token material, and database credentials, enabling forged administrative access tokens and full authentication bypass. A normalizing L7 proxy may block the primary request form, but direct exposure and L4/TCP proxies remain affected, and netty.fileServerEnabled is enabled by default. This issue is fixed in 5.7.12.","status":"published","published":"2026-09-17T18:34:40.563Z","modified":"2026-10-28T11:09:57.028487948Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"cwes":["CWE-200","CWE-22","CWE-522"],"updated_at":"2026-11-02T15:45:10.818486Z"},{"id":"GHSA-5g75-477j-2c2f","title":"LaunchServer FileServerHandler has an unauthenticated path traversal issue","description":"### 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`.","status":"published","published":"2026-07-02T20:49:18Z","modified":"2026-07-02T21:11:31.683686Z","cvss":[{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"cwes":["CWE-200","CWE-22","CWE-522"],"updated_at":"2026-11-02T15:46:38.213481Z"}]}}
