FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[Bug]: Strict cookie check on /public.php/dav/files/<token> cannot be satisfied by non-browser clients (password protected shares) · Issue #63418 · nextcloud/server · GitHub

[Bug]: Strict cookie check on /public.php/dav/files/<token> cannot be satisfied by non-browser clients (password protected shares) #63418

Description

⚠️ This issue respects the following points: ⚠️

Bug description

On a password protected link share, PROPFIND /public.php/dav/files/<token> with correct Basic credentials answers 302 to the share page instead of authenticating, as soon as the request carries any cookie. Without cookies the same request answers 207.

On where this belongs: I did not take it to HackerOne because the check refuses access it should grant, rather than granting access it should refuse. Nothing is exposed by it.

This sits behind #59391 and I am reporting it separately because it is a different line and needs a different change. #59391 is about the missing WWW-Authenticate on the 401; a client that gets past that, or that sends credentials unprompted, then runs into the check described here. If you see the two as one, please close this as a duplicate of it.

The check is in apps/dav/lib/Connector/Sabre/PublicAuth.php:

if (count($_COOKIE) > 0 && !$this->request->passesStrictCookieCheck() && $this->getShare()->isPasswordProtected()) {
    throw new PreconditionFailed('Strict cookie check failed');
}

passesStrictCookieCheck() wants __Host-nc_sameSiteCookielax and __Host-nc_sameSiteCookiestrict. Those are set by performSameSiteCookieProtection() in lib/base.php, which returns early for user agents listed in csrf.optout (/^WebDAVFS/ and /^Microsoft-WebDAV-MiniRedir/ by default). Those clients do still receive the session cookies from the same response, so count($_COOKIE) > 0 becomes true on the next request while the two cookies the check asks for were never sent to them. As far as I can tell the check can then never pass for those user agents.

Measured on 32.0.14, same share, same request, only the user agent differs:

user agent cookies the 401 sets second request, with those cookies and the password
curl, default user agent session cookies and both __Host-nc_sameSiteCookie* 207
Microsoft-WebDAV-MiniRedir/10.0.19041 session cookies only 302 to /s/<token>

csrf.optout is not set on this instance, so the defaults apply. Setting 'csrf.optout' => [] makes the second row behave like the first, which is what makes me think the opt-out list is what the check collides with.

The check moved into PublicAuth with #52810, which in the same step took public.php out of performSameSiteCookieProtection():

if ($processingScript === 'index.php' // index.php routes are handled in the middleware
    || $processingScript === 'cron.php' // and cron.php does not need any authentication at all
    || $processingScript === 'public.php' // For public.php, auth for password protected shares is done in the PublicAuth plugin
) {
    return;
}

That is the early return the csrf.optout check sits behind. In PublicAuth there is no equivalent, so as far as I can read it a Microsoft-WebDAV-MiniRedir request did not reach the check before #52810 and does now. I do not know whether that was intended and I may be missing the reason the opt-out was not carried over.

The same shape applies beyond the Windows redirector: WebDAVFS is the other default entry in csrf.optout, and any client that cannot produce the __Host- cookies is in the same position, which as far as I can see includes rclone, davfs2 and Cyberduck.

Steps to reproduce

  1. Create a public link share and give it a password.
  2. First request, no cookies, to collect what the server sets:
curl -si -c jar.txt -A 'Microsoft-WebDAV-MiniRedir/10.0.19041' -X PROPFIND -H 'Depth: 0' 'https://<server>/public.php/dav/files/<token>'
  1. Second request, with those cookies and the share password:
curl -si -b jar.txt -A 'Microsoft-WebDAV-MiniRedir/10.0.19041' -u '<token>:<share-password>' -X PROPFIND -H 'Depth: 0' 'https://<server>/public.php/dav/files/<token>'

Expected behavior

Step 3 answers 207, as the same request does without the cookie jar.

This is the documented use of the endpoint, Accessing public shares over WebDAV: "in a WebDAV client, use the share token as username and the (optional) share password as the password". The federation option the note there refers to is enabled on this instance.

What happens instead:

HTTP/2 302
location: /s/<token>

Two controls, both 207, which is why I believe the cookie check is what is being hit and not the credentials:

  • step 3 without -b jar.txt
  • step 3 with -b jar.txt -b '__Host-nc_sameSiteCookielax=true; __Host-nc_sameSiteCookiestrict=true'

The legacy /public.php/webdav also answers 207 for the same share and password. It authenticates through LegacyPublicAuth, which does not run this check.

Nextcloud Server version

32

Operating system

Debian/Ubuntu

PHP engine version

PHP 8.4

Web server

Apache (supported)

Database engine version

MySQL

Is this bug present after an update or on a fresh install?

Updated from a MINOR version (ex. 32.0.1 to 32.0.2)

Are you using the Nextcloud Server Encryption module?

Encryption is Disabled

What user-backends are you using?

  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other

Configuration report

No response

List of activated Apps

No response

Nextcloud Signing status

No response

Nextcloud Logs

No response

Additional info

Exact version is 32.0.14. Nothing is written to nextcloud.log for these requests; PreconditionFailed is turned into the redirect without a log entry.

Where I ran into it. I maintain NcDavTray, a Windows tray client that maps public link shares to drive letters. It routes password protected shares over /public.php/webdav for this reason, with the intent of dropping that route once the modern endpoint can serve them:

https://github.com/ernolf/NcDavTray/blob/6ad844ac3737560ab326bcd4a986af196a87fd9b/modules/Ensure-MountPassword.ps1#L30-L46

That works everywhere today, but the legacy endpoint is deprecated, and if it goes while this stands, password protected shares are no longer mountable from the Windows redirector at all.

Possible directions. My understanding of the SameSite cookie check is that it defends against a browser being made to issue a request that rides on an ambient session. If that is right, a request carrying Authorization: Basic does not look like that case to me: the credentials come from the client, not from the browser's cookie jar, and a wrong password is rejected by validateUserPass() before checkToken() and the session are ever reached. On that reading, skipping the check when the request carries Basic credentials would keep the browser case intact and would also cover the clients named above, not only the two user agents in csrf.optout.

I am not confident about that, which is why I am describing it rather than proposing it. The narrower alternative would be to evaluate csrf.optout inside PublicAuth as well, which only restores what the check did before #52810.

I have tried the first one locally against 32.0.14. The Windows redirector then mounts a password protected share over /public.php/dav/files/<token>, and the check still answers 302 for a request that carries cookies without credentials.

Metadata

Metadata

Assignees

Type

No type

Projects

Status
To triage

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions


Back | FazBrowse Home | New Git URL