Pimcore: Missing Authorization in WebDAV MOVE via unchecked asset move handling
Description
Summary
Pimcore's WebDAV asset endpoint exposes a MOVE operation through /asset/webdav{path} without adding an authentication plugin in the WebDAV controller. The Tree::move() implementation then performs asset mutation and deletion before checking a current Pimcore user or any asset permissions.
An unauthenticated remote attacker who knows two existing asset paths in the same directory can send a WebDAV MOVE request that deletes the source asset. Authenticated low-privileged users may also be able to perform unauthorized asset move or overwrite operations because the move path does not enforce rename, delete, create, or publish permissions.
Details
The route for WebDAV is globally registered and accepts arbitrary trailing paths:
# bundles/CoreBundle/config/routing.yaml
pimcore_webdav:
path: /asset/webdav{path}
defaults: { _controller: Pimcore\Bundle\CoreBundle\Controller\WebDavController::webdavAction }
requirements:
path: '.*'
The controller constructs a SabreDAV server but only attaches lock and browser plugins. It does not attach an authentication plugin or perform an explicit user/session check before starting the server:
# bundles/CoreBundle/src/Controller/WebDavController.php
$publicDir = new Asset\WebDAV\Folder($homeDir);
$objectTree = new Asset\WebDAV\Tree($publicDir);
$server = new \Sabre\DAV\Server($objectTree);
$server->setBaseUri($this->generateUrl('pimcore_webdav', ['path' => '/']));
$server->addPlugin($lockPlugin);
$server->addPlugin(new \Sabre\DAV\Browser\Plugin());
$server->start();
Most WebDAV file and folder operations perform permission checks through isAllowed(), but Tree::move() does not. In the overwrite path for a same-directory move, it deletes the source asset before resolving the current user:
# models/Asset/WebDAV/Tree.php
if (dirname($sourcePath) == dirname($destinationPath)) {
if ($asset = Asset::getByPath('/' . $destinationPath)) {
$sourceAsset = Asset::getByPath('/' . $sourcePath);
$asset->setData($sourceAsset->getData());
$sourceAsset->delete();
}
...
}
$user = \Pimcore\Tool\Admin::getCurrentUser();
$asset->setUserModification($user->getId());
$asset->save();
Asset::delete() removes the asset without an internal permission gate:
# models/Asset.php
public function delete(bool $isNested = false): void
{
...
$this->getDao()->delete();
...
$this->deletePhysicalFile();
}
Because the source asset deletion happens before $user->getId(), an unauthenticated request can still cause a deletion even if later execution fails when no current user is present.
PoC
Prerequisites:
- Pimcore 2026.1.0 with the built-in WebDAV route enabled.
- Two existing asset paths in the same directory, for example
/products/source.jpgand/products/existing.jpg. - No valid session is required for the unauthenticated deletion path.
PoC request:
MOVE /asset/webdav/products/source.jpg HTTP/1.1
Host: target.example
Destination: http://target.example/asset/webdav/products/existing.jpg
Overwrite: T
Result:
The server will return an error after the deletion because Tree::move() later attempts to call $user->getId() when no current user exists. However, the source asset at /products/source.jpg has already been deleted by $sourceAsset->delete() before that failure point.
For an authenticated low-privileged backend user without sufficient asset permissions, the same request can also reach the unchecked move path and may overwrite the destination asset or move an asset without the expected per-asset permission checks.
Impact
This issue allows remote unauthorized destruction of assets when paths are known or guessable. In Pimcore deployments where assets represent product images, documents, media, or DAM-managed business content, deletion or unauthorized overwrite can cause data loss, content integrity loss, and service disruption.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Unauthenticated WebDAV MOVE in Pimcore allows asset deletion or unauthorized overwrite without permission checks.
Vulnerability
The Pimcore WebDAV endpoint at /asset/webdav{path} exposes a MOVE operation in the Tree::move() method without an authentication plugin or user-session check [1][2]. The route is globally registered with a wildcard path requirement [1]. The controller (WebDavController) creates a SabreDAV server and attaches only lock and browser plugins, omitting any authentication plugin [1][2]. Most WebDAV operations enforce permissions via isAllowed(), but Tree::move() does not; it performs asset mutation and deletion before resolving the current user [1][2]. In a same-directory move where the destination exists, the source asset is deleted to overwrite the destination, all without verifying the user identity or asset permissions [2]. All versions prior to the fix are affected; the advisory references a commit to the 12.3 branch [3][4].
Exploitation
An unauthenticated remote attacker who knows two existing asset paths in the same directory can send a WebDAV MOVE request to trigger deletion of the source asset [1][2]. Authenticated low-privileged users may also exploit the missing permission checks to perform unauthorized asset moves or overwrites, as the code path does not enforce rename, delete, create, or publish permissions [1][2]. The attacker only needs network access to the WebDAV endpoint; no prior session or authentication is required for the unauthenticated variant.
Impact
Successful exploitation allows an unauthenticated attacker to delete an arbitrary asset if they know two existing asset paths in the same directory [1][2]. For authenticated low-privileged users, the impact extends to unauthorized asset move or overwrite operations, potentially corrupting or displacing critical assets [1][2]. The attack leads to loss of asset availability (deletion) and integrity (overwrite), with no privilege escalation beyond the asset scope.
Mitigation
The fix is implemented in commit 9d7c77f and pull request #19120, targeting the 12.3 branch [3][4]. The patch adds a call to Admin::getCurrentUser() at the start of Tree::move() and throws a Sabre\DAV\Exception\Forbidden if no authenticated user is found [3]. Administrators should update to the patched version as soon as it is released. No workaround is available for the authentication gap; disabling the WebDAV route at the reverse-proxy level can temporarily block external access. The CVE is not listed on the CISA KEV as of publication.
- CVE-2026-45260 - GitHub Advisory Database
- Missing Authorization in WebDAV MOVE via unchecked asset move handling
- [Security]Enhance Authorization in WebDAV MOVE via unchecked asset mo… · pimcore/pimcore@9d7c77f
- [Security]Enhance Authorization in WebDAV MOVE via unchecked asset move handling by kingjia90 · Pull Request #19120 · pimcore/pimcore
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
19d7c77fd9b19[Security]Enhance Authorization in WebDAV MOVE via unchecked asset move handling (#19120)
2 files changed · +38 −7
bundles/CoreBundle/src/Controller/WebDavController.php+0 −1 modified@@ -17,7 +17,6 @@ use Pimcore\Controller\Controller; use Pimcore\Logger; use Pimcore\Model\Asset; - /** * @internal */
models/Asset/WebDAV/Tree.php+38 −6 modified@@ -17,8 +17,10 @@ use Pimcore\Logger; use Pimcore\Model\Asset; use Pimcore\Model\Element; +use Pimcore\Tool\Admin; use Pimcore\Tool\Serialize; use Sabre\DAV; +use Sabre\DAV\Exception\Forbidden; /** * @internal @@ -33,6 +35,11 @@ class Tree extends DAV\Tree */ public function move($sourcePath, $destinationPath): void { + $user = Admin::getCurrentUser(); + if ($user === null) { + throw new Forbidden('No authenticated user available'); + } + $nameParts = explode('/', $sourcePath); $nameParts[count($nameParts) - 1] = Element\Service::getValidKey($nameParts[count($nameParts) - 1], 'asset'); $sourcePath = implode('/', $nameParts); @@ -42,14 +49,20 @@ public function move($sourcePath, $destinationPath): void $destinationPath = implode('/', $nameParts); try { - if (dirname($sourcePath) == dirname($destinationPath)) { - $asset = null; + if (dirname($sourcePath) === dirname($destinationPath)) { + $asset = Asset::getByPath('/' . $destinationPath); - if ($asset = Asset::getByPath('/' . $destinationPath)) { + if ($asset) { // If we got here, this means the destination exists, and needs to be overwritten + // NB: due to the nature of how the WebDav might be used with third party software (like Photoshop), + // a move in here it has to be an overwrite in the history of destination file to keep the file + // history and make it seamlessly and quickly reverted within the file change history. + // It also helps keeping the hardcoded reference or dependencies of a specific asset ID that might + // be used elsewhere in the project that users/collaborators given only WebDav access have + // no control nor access. $sourceAsset = Asset::getByPath('/' . $sourcePath); $asset->setData($sourceAsset->getData()); - $sourceAsset->delete(); + } // see: Asset\WebDAV\File::delete() why this is necessary @@ -59,7 +72,6 @@ public function move($sourcePath, $destinationPath): void if ($asset) { $sourceAsset = Asset::getByPath('/' . $sourcePath); $asset->setData($sourceAsset->getData()); - $sourceAsset->delete(); } } @@ -75,11 +87,31 @@ public function move($sourcePath, $destinationPath): void $asset->setParentId($parent->getId()); } - $user = \Pimcore\Tool\Admin::getCurrentUser(); + if (isset($parent)) { + if (!$parent->isAllowed('create', $user)) { + throw new Forbidden('No create permission on destination folder'); + } + } + + if (!$asset->isAllowed('publish', $user)) { + throw new Forbidden('No publish permission on target asset'); + } + + if (isset($sourceAsset) && !$sourceAsset->isAllowed('delete', $user)) { + throw new Forbidden('No delete permission on source'); + } + $asset->setUserModification($user->getId()); $asset->save(); + + if (isset($sourceAsset)) { + $sourceAsset->delete(); + } + } catch (Forbidden $e) { + throw $e; } catch (Exception $e) { Logger::error((string) $e); + throw $e; } } }
Vulnerability mechanics
Root cause
"Missing authentication plugin on the WebDAV endpoint and missing permission checks in `Tree::move()` allow unauthenticated asset deletion before user resolution."
Attack vector
An unauthenticated attacker sends a WebDAV `MOVE` request to `/asset/webdav{path}` with a `Destination` header pointing to an existing asset in the same directory and `Overwrite: T` [ref_id=1]. The `Tree::move()` method deletes the source asset via `$sourceAsset->delete()` before it resolves the current user or checks any permissions [ref_id=1]. Even though execution later fails when `$user->getId()` is called on a null user, the source asset has already been permanently removed [ref_id=1]. Authenticated low-privileged users can also exploit the missing `rename`, `delete`, `create`, and `publish` permission checks to move or overwrite assets they should not be allowed to touch [ref_id=1].
Affected code
The vulnerable code is in `models/Asset/WebDAV/Tree.php` in the `move()` method, which performs asset mutation and deletion before checking authentication or permissions [ref_id=1]. The WebDAV controller at `bundles/CoreBundle/src/Controller/WebDavController.php` does not attach an authentication plugin, leaving the `MOVE` endpoint globally accessible [ref_id=1]. The route is registered in `bundles/CoreBundle/config/routing.yaml` with a wildcard path requirement [ref_id=1].
What the fix does
The patch [patch_id=2713662] moves the user resolution (`Admin::getCurrentUser()`) to the top of `move()` and throws a `Forbidden` exception if no authenticated user is present [ref_id=3]. It then adds explicit `isAllowed()` checks for `create` on the destination parent folder, `publish` on the target asset, and `delete` on the source asset before any mutation occurs [ref_id=3]. Critically, `$sourceAsset->delete()` is deferred until after all permission checks pass and the asset is saved, preventing the unauthenticated deletion race [ref_id=3].
Preconditions
- configThe Pimcore WebDAV route must be enabled (default in Pimcore 2026.1.0)
- inputAttacker must know two existing asset paths in the same directory
- authNo authentication required for unauthenticated deletion; low-privileged session sufficient for unauthorized move/overwrite
- networkAttacker must be able to send HTTP requests to the target server
Reproduction
Prerequisites: Pimcore 2026.1.0 with WebDAV route enabled, two existing asset paths in the same directory (e.g. `/products/source.jpg` and `/products/existing.jpg`), no valid session required. Send the following HTTP request:
``` MOVE /asset/webdav/products/source.jpg HTTP/1.1 Host: target.example Destination: http://target.example/asset/webdav/products/existing.jpg Overwrite: T ```
The server returns an error after deletion, but `/products/source.jpg` is already deleted [ref_id=1].
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.