Nextcloud 35.0.0 — federated share secret is an unscoped app password for the sharer's whole Files tree
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · nextcloud-federated-ocm-token
Nextcloud Server 35.0.0 - Nextcloud GmbH
A federated share secret is minted as an unscoped PERMANENT_TOKEN app password for the sharer. The recipient API returns it as refresh_token. Replaying it as Bearer on the sender WebDAV logs in as the sharer and reads files that were never shared.
A bad actor you federated-share one file with can log in as you on your Nextcloud, skip 2FA, and read or overwrite all of your files, not just the one you shared.
| ID | no CVE yet |
| CWE | CWE-269, CWE-287 |
| CVSS | Critical: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N (federated recipient). Public-link convert is worse (no recipient account on a peer). |
| Product | Nextcloud Server |
| Affected | 35.0.0 (da02f41) official nextcloud:35.0.0-apache |
| Auth | federated recipient (or public-link converter) |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
You share one file or folder with bob on another Nextcloud. Nextcloud mints a 32-character secret, stores it as your app password, and hands that secret to Bob as refresh_token.
Bob (or anyone who converts your public link into a federated share) then:
They do not get a PHP shell on the host. They do not need your password. Do not accept the share and do not call OCM access-token first: that exchange locks filesystem scope. The lab proves the pre-exchange secret.
A share is supposed to be a share. One file. One folder. I read FederatedShareProvider::createFederatedShare on tag v35.0.0. It mints a 32-character secret as IToken::PERMANENT_TOKEN for the sharer with no scope. PublicKeyToken::getScopeAsArray defaults SCOPE_FILESYSTEM => true. That is an app password. Session::tryTokenLogin takes a permanent Bearer.
The OCM exchange work (PR #57234 / #57166) already locks filesystem scope after the first exchange. An unofficial writeup used that post-exchange hop. I tried it first. On this tree the exchanged token is TEMPORARY, named OCM Access Token, and /remote.php/dav rejects it (allowOcmAccessToken false). DAV said no.
So I did not exchange. ocm_discovery_enabled stayed false so the recipient would not exchange it for me. Two official nextcloud:35.0.0-apache boxes, overwrite.cli.url as docker DNS (http://sender / http://recipient). Leave that at 127.0.0.1 and S2S talks to itself.
Alice shares one file with bob@http://recipient. Bob reads pending shares. refresh_token length 32. access_token length 0. Unauthenticated Bearer GET of Alice's private witness file, never in the share, returned 200, X-User-Id: alice.
cd lab
./run.sh
Target only http://127.0.0.1:18340 (sender) and :18341 (recipient).
SUCCESS NEXTCLOUD-OCM-PERMANENT-TOKEN who=federated-recipient uid=alice NEXTCLOUD-OCM-PERMANENT-TOKEN-WITNESS
Mint the federated secret with filesystem scope false from the first insert. Do not accept that secret as Bearer/Basic on /remote.php/dav. First exchange already locks FS scope; create should never have handed out an app password.
GNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.