# Pterodactyl's improper JWT scoping allows subuser to upload files when not explicitly granted `file.create` permissions

Wings /upload/file accepts any panel-signed JWT with server_uuid/user_uuid/unique_id, so a subuser with only websocket.connect can reuse a console token to upload arbitrary files.

- Payout: Research
- Severity: high
- Program: Pterodactyl
- Found by: TrixterTheTux
- Disclosed: 2026-07-28
- CVE: CVE-2026-54593
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8r6w-3qq5-4p4r
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8r6w-3qq5-4p4r
- Tags: area:web domain:web impact:integrity impact:privesc kind:research platform:ghsa sev:high tech:jwt technique:source-review vuln:access-control vuln:auth-bypass vuln:privilege-escalation

**Target:** Wings daemon /upload/file endpoint and Panel JWT issuance
**Root cause:** Panel issues JWTs with identical claims (server_uuid, user_uuid, unique_id) for many purposes but never encodes the token's intended purpose; Wings validates only signature and claims, not scope.
**Steps:**
1. Create a subuser with only `websocket.connect` on a server.
2. As that subuser, call `GET /api/client/servers/[...]/websocket` to obtain a websocket JWT.
3. POST the token to the Wings node: `POST http://<wings>:8080/upload/file?token=<websocket_token>` with multipart field `files` containing arbitrary content.
4. Observe the file is written to the server despite lacking `file.create`.
**How found:** Noticed the Panel issues JWTs with the same claim set for many distinct purposes (backup download, file upload, websocket, file download) and that Wings only checks signature plus server_uuid/user_uuid/unique_id, so tokens are interchangeable across endpoints.
**Exploitation:** Any authenticated subuser with console/download/backup-download permission obtains a panel-signed JWT, then replays it against the Wings /upload/file endpoint to write arbitrary files to the same server, escalating from read-only console access to file write.
**Escalation:** Arbitrary file write on the server can be chained toward code execution depending on server contents; the advisory notes the same pattern affects many other actions.
**Insight:** When a service mints JWTs for multiple purposes with identical claims, test cross-endpoint token reuse: grab the lowest-privilege token (e.g. websocket auth) and replay it against higher-privilege endpoints that share the same claim validation.

## Proof of concept (copied exactly from the original report)

**Python PoC reusing websocket JWT to upload file**

```
import requests

wings_url = 'http://[...]:8080'
websocket_token = '[...]'

res = requests.post(f'{wings_url}/upload/file', params = {
    'token': websocket_token,
}, files = {
    'files': ('file-upload.txt', b'Hello, World!'),
})
print(res.status_code, res.content)
```

