SSRF via External DAV Storage — Bypass of preventLocalAddress Protection
Summary
Nextcloud external DAV storage backend (OC\Files\Storage\DAV) uses Sabre\DAV\Client (raw cURL) for all HTTP requests, completely bypassing the preventLocalAddress() SSRF protection that guards all other HTTP client usage in Nextcloud. An authenticated user with permission to create external storages can configure a DAV storage pointing to an internal address (127.0.0.1, 169.254.169.254, 10.x.x.x) and read the response through the filesystem interface.
Affected Component
- File: lib/private/Files/Storage/DAV.php
- Class: OC\Files\Storage\DAV
- Client: BearerAuthAwareSabreClient extends Sabre\DAV\Client (uses raw cURL via CURLOPT_*)
Vulnerability Description
All other HTTP clients in Nextcloud go through OC\Http\Client.Client which calls preventLocalAddress() on every request. This uses IRemoteHostValidator::isValid() to check the host against IpAddressClassifier and HostnameClassifier, blocking connections to localhost, private ranges, and link-local addresses.
However, the DAV external storage backend instantiates BearerAuthAwareSabreClient (which extends Sabre\DAV\Client) directly:
// DAV.php L273
$this->client = new BearerAuthAwareSabreClient($settings);
Sabre\DAV\Client uses PHP cURL extension directly. It does NOT call preventLocalAddress(), IRemoteHostValidator::isValid(), or any equivalent check. The host parameter from storage configuration flows directly to the cURL handle:
// DAV.php L189-197
$host = $parameters['host'];
if (str_starts_with($host, 'https://')) {
$host = substr($host, 8);
} elseif (str_starts_with($host, 'http://')) {
$host = substr($host, 7);
}
$this->host = $host;
No validation is performed on the host value. There is no call to preventLocalAddress, remoteHostValidator, isValid, filter, sanitize, escape, or FILTER_VALIDATE anywhere in DAV.php.
The base URI is constructed in createBaseUri() and used for all PROPFIND/GET/PUT requests via Sabre cURL client.
Attack Scenario
- Attacker creates external storage of type WebDAV via files_external API
- Host parameter set to internal address (169.254.169.254 for AWS metadata, 127.0.0.1:8080 for internal services, 10.0.0.1 for internal network)
- When storage is accessed (directory listing), Nextcloud makes PROPFIND request to internal address via cURL
- Response from internal service is returned through filesystem interface, allowing attacker to read internal service responses, cloud metadata, or other internal resources
Impact
- Authenticated SSRF: Any user who can create external storages can make arbitrary requests to internal services
- Information disclosure: Internal service responses (including cloud provider metadata endpoints) are readable through the filesystem interface
- Network scanning: By testing different hosts/ports, attacker can enumerate internal network topology
- Bypass of security controls: The preventLocalAddress() guard that protects all other Nextcloud HTTP clients is completely bypassed
Prerequisites
- Authenticated Nextcloud user with permission to create external storages
- If allow_user_mounting is disabled (default), admin access is required
- If allow_user_mounting is enabled, any authenticated user can create DAV external storages
Remediation
Add preventLocalAddress() check (or equivalent IRemoteHostValidator::isValid() call) before creating the Sabre DAV client in DAV.php init(), and validate the host parameter in the constructor.
References
- lib/private/Http/Client/Client.php L168 — preventLocalAddress() implementation
- lib/private/Security/RemoteHostValidator.php — isValid() method
- lib/private/Net/IpAddressClassifier.php — IP range blocking
- lib/private/Net/HostnameClassifier.php — hostname blocking
SSRF via External DAV Storage — Bypass of preventLocalAddress Protection
Summary
Nextcloud external DAV storage backend (OC\Files\Storage\DAV) uses Sabre\DAV\Client (raw cURL) for all HTTP requests, completely bypassing the preventLocalAddress() SSRF protection that guards all other HTTP client usage in Nextcloud. An authenticated user with permission to create external storages can configure a DAV storage pointing to an internal address (127.0.0.1, 169.254.169.254, 10.x.x.x) and read the response through the filesystem interface.
Affected Component
Vulnerability Description
All other HTTP clients in Nextcloud go through OC\Http\Client.Client which calls preventLocalAddress() on every request. This uses IRemoteHostValidator::isValid() to check the host against IpAddressClassifier and HostnameClassifier, blocking connections to localhost, private ranges, and link-local addresses.
However, the DAV external storage backend instantiates BearerAuthAwareSabreClient (which extends Sabre\DAV\Client) directly:
Sabre\DAV\Client uses PHP cURL extension directly. It does NOT call preventLocalAddress(), IRemoteHostValidator::isValid(), or any equivalent check. The host parameter from storage configuration flows directly to the cURL handle:
No validation is performed on the host value. There is no call to preventLocalAddress, remoteHostValidator, isValid, filter, sanitize, escape, or FILTER_VALIDATE anywhere in DAV.php.
The base URI is constructed in createBaseUri() and used for all PROPFIND/GET/PUT requests via Sabre cURL client.
Attack Scenario
Impact
Prerequisites
Remediation
Add preventLocalAddress() check (or equivalent IRemoteHostValidator::isValid() call) before creating the Sabre DAV client in DAV.php init(), and validate the host parameter in the constructor.
References