VYPR
Low severityNVD Advisory· Published Oct 8, 2024· Updated Oct 8, 2024

Information Disclosure in TYPO3 Page Tree

CVE-2024-47780

Description

TYPO3 is a free and open source Content Management Framework. Backend users could see items in the backend page tree without having access if the mounts pointed to pages restricted for their user/group, or if no mounts were configured but the pages allowed access to "everybody." However, affected users could not manipulate these pages. Users are advised to update to TYPO3 versions 10.4.46 ELTS, 11.5.40 LTS, 12.4.21 LTS, 13.3.1 that fix the problem described. There are no known workarounds for this vulnerability.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

In TYPO3, backend users could see restricted pages in the page tree if mounts pointed to restricted pages or no mounts were configured, leading to information disclosure.

Vulnerability

Overview

CVE-2024-47780 is an information disclosure vulnerability in the TYPO3 Content Management Framework that affects the backend page tree component. The root cause is that the page tree incorrectly displayed pages to backend users even when those users lacked explicit access rights. Specifically, if user or group mounts pointed to pages that were restricted for that user or group, the restricted pages still appeared as visible entries in the page tree. Additionally, if no mounts were configured for a user but certain pages had global access permissions set to "everybody," those pages also appeared in the page tree for users who should not have seen them [1].

Exploitation

Context

Exploitation of this vulnerability requires a valid backend user account, but no special privileges beyond that. The attack surface is the TYPO3 backend page tree interface, which is accessible to authenticated backend users. The vulnerability does not require any user interaction beyond logging into the backend; the unauthorized page listings are automatically displayed. The commit diffs show that the fix involves removing references to hardcoded page IDs with unrestricted symbolic references, ensuring that the page tree only shows pages that are explicitly configured for display through proper mountpoint settings [2][3][4].

Impact

An attacker with a backend user account could enumerate the structure and titles of pages that were meant to be hidden from them, potentially revealing sensitive content or internal site architecture. Importantly, the affected users could not manipulate these pages; the exposure was limited to read-only visibility of page tree entries [1]. This information leak could aid further attacks or expose confidential information.

Mitigation

TYPO3 has released patched versions that fix the issue: 10.4.46 ELTS, 11.5.40 LTS, 12.4.21 LTS, and 13.3.1. Users are strongly advised to update to these or later versions. There are no known workarounds for this vulnerability [1].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
typo3/cms-backendPackagist
>= 13.0.0, < 13.3.113.3.1
typo3/cms-backendPackagist
>= 12.0.0, < 12.4.2112.4.21
typo3/cms-backendPackagist
>= 11.0.0, < 11.5.4011.5.40
typo3/cms-backendPackagist
>= 10.0.0, < 10.4.4610.4.46

Affected products

2

Patches

3
9ae1ef969b63

[SECURITY] Show only explicitly configured page tree information

https://github.com/TYPO3-CMS/backendOliver HaderOct 8, 2024via ghsa
5 files changed · +70 20
  • Classes/Controller/Page/TreeController.php+4 4 modified
    @@ -407,11 +407,11 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0
                 'workspaceId' => !empty($page['t3ver_oid']) ? $page['t3ver_oid'] : $pageId,
             ];
     
    -        if (!empty($page['_children']) || $this->pageTreeRepository->hasChildren($pageId)) {
    +        if ($depth >= $this->levelsToFetch && $this->pageTreeRepository->hasChildren($pageId)) {
    +            $page = $this->pageTreeRepository->getTreeLevels($page, 1);
    +        }
    +        if (!empty($page['_children'])) {
                 $item['hasChildren'] = true;
    -            if ($depth >= $this->levelsToFetch) {
    -                $page = $this->pageTreeRepository->getTreeLevels($page, 1);
    -            }
             }
             if (is_array($lockInfo)) {
                 $item['locked'] = true;
    
  • Classes/Tree/Repository/PageTreeRepository.php+8 9 modified
    @@ -172,29 +172,28 @@ protected function applyCallbackToChildren(array &$tree, callable $callback): vo
          *
          * @param array $pageTree The page record of the top level page you want to get the page tree of
          * @param int $depth Number of levels to fetch
    -     * @param array $entryPointIds entryPointIds to include
    +     * @param ?array $entryPointIds entryPointIds to include (null in case no entry-points were provided)
          * @return array An array with page records and their children
          */
    -    public function getTreeLevels(array $pageTree, int $depth, array $entryPointIds = []): array
    +    public function getTreeLevels(array $pageTree, int $depth, ?array $entryPointIds = null): array
         {
             $groupedAndSortedPagesByPid = [];
    -
    -        if (count($entryPointIds) > 0) {
    +        // the method was called without any entry-point information
    +        if ($entryPointIds === null) {
    +            $parentPageIds = [$pageTree['uid']];
    +            // the method was called with entry-point information, that is not empty
    +        } elseif ($entryPointIds !== []) {
                 $pageRecords = $this->getPageRecords($entryPointIds);
                 $groupedAndSortedPagesByPid[$pageTree['uid']] = $pageRecords;
                 $parentPageIds = $entryPointIds;
    -        } else {
    -            $parentPageIds = [$pageTree['uid']];
             }
    -
             for ($i = 0; $i < $depth; $i++) {
    +            // stop in case the initial or recursive query did not have any pages
                 if (empty($parentPageIds)) {
                     break;
                 }
                 $pageRecords = $this->getChildPageRecords($parentPageIds);
    -
                 $groupedAndSortedPagesByPid = $this->groupAndSortPages($pageRecords, $groupedAndSortedPagesByPid);
    -
                 $parentPageIds = array_column($pageRecords, 'uid');
             }
             $this->addChildrenToPage($pageTree, $groupedAndSortedPagesByPid);
    
  • Tests/Functional/Controller/Page/Fixtures/be_users.csv+2 0 modified
    @@ -3,4 +3,6 @@
     ,1,0,"admin",,0,1,0
     ,2,0,"test1",,1,0,0
     ,3,0,"test1",,0,0,0
    +,7,0,"editor",7,0,0,3
    +,8,0,"editor",8,0,0,3
     ,9,0,"editor",9,0,0,3
    
  • Tests/Functional/Controller/Page/Fixtures/PagesWithBEPermissions.yaml+7 4 modified
    @@ -4,8 +4,6 @@ __variables:
       - &pageMount 7
       - &pageFolder 254
       - &contentText 'text'
    -  - &idAcmeRootPage 1000
    -  - &idAcmeFirstPage 1100
     
     entitySettings:
       '*':
    @@ -45,11 +43,13 @@ entities:
       workspace:
         - self: {id: 1, title: 'Workspace'}
       beGroup:
    +    - self: {id: 7, title: 'editors without DB mounts', db_mountpoints: '', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
    +    - self: {id: 8, title: 'editors with DB mounts', db_mountpoints: '9200', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
         - self: {id: 9, title: 'editors', db_mountpoints: '1000,2000,8110', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
       page:
    -    - self: {id: *idAcmeRootPage, title: 'ACME Inc', type: *pageShortcut, shortcut: 'first', root: true, slug: '/'}
    +    - self: {id: 1000, title: 'ACME Inc', type: *pageShortcut, shortcut: 'first', root: true, slug: '/'}
           children:
    -        - self: {id: *idAcmeFirstPage, title: 'EN: Welcome', slug: '/welcome', subtitle: 'hello-and-welcome'}
    +        - self: {id: 1100, title: 'EN: Welcome', slug: '/welcome', subtitle: 'hello-and-welcome'}
             - self: {id: 1200, title: 'EN: Features', slug: '/features'}
               children:
                 - self: {id: 1210, title: 'EN: Frontend Editing', slug: '/features/frontend-editing', perms_userid: 9, perms_groupid: 1, description: 'accessible for user, but not for group'}
    @@ -153,3 +153,6 @@ entities:
                             - { action: 'move', type: 'toPage', target: 1700 }
                 - self: {id: 8120, title: 'Asia', slug: '/divisions/asia', description: 'not visible as not mounted'}
                 - self: {id: 8130, title: 'South America', slug: '/divisions/south-america'}
    +    - self: {id: 9100, title: 'Page 9100', type: *pageStandard, root: true, slug: '/page-9100', perms_userid: 1, perms_groupid: 1, perms_everybody: 15}
    +    - self: {id: 9200, title: 'Page 9200', type: *pageStandard, root: true, slug: '/page-9200', perms_userid: 1, perms_groupid: 1, perms_everybody: 0}
    +    - self: {id: 9300, title: 'Page 9300', type: *pageStandard, root: true, slug: '/page-9300', perms_userid: 1, perms_groupid: 1, perms_everybody: 15}
    
  • Tests/Functional/Controller/Page/TreeControllerTest.php+49 3 modified
    @@ -59,11 +59,11 @@ protected function setUp(): void
             $this->withDatabaseSnapshot(function () {
                 $this->importCSVDataSet(__DIR__ . '/Fixtures/be_users.csv');
                 // Admin user for importing dataset
    -            $this->backendUser = $this->setUpBackendUser(1);
    -            $GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($this->backendUser);
    +            $backendUser = $this->setUpBackendUser(1);
    +            $GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($backendUser);
                 $scenarioFile = __DIR__ . '/Fixtures/PagesWithBEPermissions.yaml';
                 $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
    -            $writer = DataHandlerWriter::withBackendUser($this->backendUser);
    +            $writer = DataHandlerWriter::withBackendUser($backendUser);
                 $writer->invokeFactory($factory);
                 self::failIfArrayIsNotEmpty($writer->getErrors());
             }, function () {
    @@ -287,6 +287,19 @@ public function getAllEntryPointPageTreesWithRootPageAsMountPoint(): void
                                 ],
                             ],
                         ],
    +                    [
    +                        // 9100 is shown due to `perms_everybody=15`
    +                        'uid' => 9100,
    +                        'title' => 'Page 9100',
    +                        '_children' => [],
    +                    ],
    +                    // 9200 is omitted due to `perms_everybody=0`
    +                    [
    +                        // 9300 is shown due to `perms_everybody=15`
    +                        'uid' => 9300,
    +                        'title' => 'Page 9300',
    +                        '_children' => [],
    +                    ],
                     ],
                 ],
                 [
    @@ -813,4 +826,37 @@ static function (AfterPageTreeItemsPreparedEvent $event) use (&$afterPageTreeIte
             self::assertEquals('1000', $afterPageTreeItemsPreparedEvent->getItems()[1]['identifier']);
             self::assertEquals('ACME Inc', $afterPageTreeItemsPreparedEvent->getItems()[1]['name']);
         }
    +
    +    public static function fetchDataActionConsidersPermissionsDataProvider(): \Generator
    +    {
    +        yield 'admin user can see all root pages' => [
    +            'backendUser' => 1,
    +            'expectation' => ['0', '1000', '2000', '7000', '8000', '9100', '9200', '9300'],
    +        ];
    +        yield 'editor with DB mounts can only see accessible pages' => [
    +            'backendUser' => 9,
    +            'expectation' => ['0', '1000', '8110'],
    +        ];
    +        yield 'editor with DB mounts cannot see inaccessible pages' => [
    +            'backendUser' => 8,
    +            'expectation' => ['0'],
    +        ];
    +        yield 'editor without DB mounts cannot see any pages' => [
    +            'backendUser' => 7,
    +            'expectation' => ['0'],
    +        ];
    +    }
    +
    +    #[Test]
    +    #[DataProvider('fetchDataActionConsidersPermissionsDataProvider')]
    +    public function fetchDataActionConsidersPermissions(int $backendUser, array $expectation): void
    +    {
    +        $this->backendUser = $this->setUpBackendUser($backendUser);
    +        $request = (new ServerRequest(new Uri('https://example.com')))->withQueryParams(['depth' => 1]);
    +        $response = $this->get(TreeController::class)->fetchDataAction($request);
    +        $data = json_decode((string)$response->getBody(), true);
    +        $items = array_filter($data, static fn(array $page): bool => $page['depth'] <= 1);
    +        $items = array_map(static fn(array $page): string => $page['identifier'], $items);
    +        self::assertSame($expectation, array_values($items));
    +    }
     }
    
8b024b08a2c7

[SECURITY] Show only explicitly configured page tree information

https://github.com/TYPO3-CMS/backendOliver HaderOct 8, 2024via ghsa
5 files changed · +70 19
  • Classes/Controller/Page/TreeController.php+4 4 modified
    @@ -411,11 +411,11 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0
                 'allowEdit' => $this->userHasAccessToModifyPagesAndToDefaultLanguage && $backendUser->doesUserHaveAccess($page, Permission::PAGE_EDIT),
             ];
     
    -        if (!empty($page['_children']) || $this->pageTreeRepository->hasChildren($pageId)) {
    +        if ($depth >= $this->levelsToFetch && $this->pageTreeRepository->hasChildren($pageId)) {
    +            $page = $this->pageTreeRepository->getTreeLevels($page, 1);
    +        }
    +        if (!empty($page['_children'])) {
                 $item['hasChildren'] = true;
    -            if ($depth >= $this->levelsToFetch) {
    -                $page = $this->pageTreeRepository->getTreeLevels($page, 1);
    -            }
             }
             if (!empty($prefix)) {
                 $item['prefix'] = htmlspecialchars($prefix);
    
  • Classes/Tree/Repository/PageTreeRepository.php+8 9 modified
    @@ -166,29 +166,28 @@ protected function applyCallbackToChildren(array &$tree, callable $callback): vo
          *
          * @param array $pageTree The page record of the top level page you want to get the page tree of
          * @param int $depth Number of levels to fetch
    -     * @param array $entryPointIds entryPointIds to include
    +     * @param ?array $entryPointIds entryPointIds to include (null in case no entry-points were provided)
          * @return array An array with page records and their children
          */
    -    public function getTreeLevels(array $pageTree, int $depth, array $entryPointIds = []): array
    +    public function getTreeLevels(array $pageTree, int $depth, ?array $entryPointIds = null): array
         {
             $groupedAndSortedPagesByPid = [];
    -
    -        if (count($entryPointIds) > 0) {
    +        // the method was called without any entry-point information
    +        if ($entryPointIds === null) {
    +            $parentPageIds = [$pageTree['uid']];
    +            // the method was called with entry-point information, that is not empty
    +        } elseif ($entryPointIds !== []) {
                 $pageRecords = $this->getPageRecords($entryPointIds);
                 $groupedAndSortedPagesByPid[$pageTree['uid']] = $pageRecords;
                 $parentPageIds = $entryPointIds;
    -        } else {
    -            $parentPageIds = [$pageTree['uid']];
             }
    -
             for ($i = 0; $i < $depth; $i++) {
    +            // stop in case the initial or recursive query did not have any pages
                 if (empty($parentPageIds)) {
                     break;
                 }
                 $pageRecords = $this->getChildPageRecords($parentPageIds);
    -
                 $groupedAndSortedPagesByPid = $this->groupAndSortPages($pageRecords, $groupedAndSortedPagesByPid);
    -
                 $parentPageIds = array_column($pageRecords, 'uid');
             }
             $this->addChildrenToPage($pageTree, $groupedAndSortedPagesByPid);
    
  • Tests/Functional/Controller/Page/Fixtures/be_users.csv+2 0 modified
    @@ -3,4 +3,6 @@
     ,1,0,"admin",,0,1,0
     ,2,0,"test1",,1,0,0
     ,3,0,"test1",,0,0,0
    +,7,0,"editor",7,0,0,3
    +,8,0,"editor",8,0,0,3
     ,9,0,"editor",9,0,0,3
    
  • Tests/Functional/Controller/Page/Fixtures/PagesWithBEPermissions.yaml+7 4 modified
    @@ -4,8 +4,6 @@ __variables:
       - &pageMount 7
       - &pageFolder 254
       - &contentText 'text'
    -  - &idAcmeRootPage 1000
    -  - &idAcmeFirstPage 1100
     
     entitySettings:
       '*':
    @@ -45,11 +43,13 @@ entities:
       workspace:
         - self: {id: 1, title: 'Workspace'}
       beGroup:
    +    - self: {id: 7, title: 'editors without DB mounts', db_mountpoints: '', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
    +    - self: {id: 8, title: 'editors with DB mounts', db_mountpoints: '9200', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
         - self: {id: 9, title: 'editors', db_mountpoints: '1000,2000,8110', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
       page:
    -    - self: {id: *idAcmeRootPage, title: 'ACME Inc', type: *pageShortcut, shortcut: 'first', root: true, slug: '/'}
    +    - self: {id: 1000, title: 'ACME Inc', type: *pageShortcut, shortcut: 'first', root: true, slug: '/'}
           children:
    -        - self: {id: *idAcmeFirstPage, title: 'EN: Welcome', slug: '/welcome', subtitle: 'hello-and-welcome'}
    +        - self: {id: 1100, title: 'EN: Welcome', slug: '/welcome', subtitle: 'hello-and-welcome'}
             - self: {id: 1200, title: 'EN: Features', slug: '/features'}
               children:
                 - self: {id: 1210, title: 'EN: Frontend Editing', slug: '/features/frontend-editing', perms_userid: 9, perms_groupid: 1, description: 'accessible for user, but not for group'}
    @@ -153,3 +153,6 @@ entities:
                             - { action: 'move', type: 'toPage', target: 1700 }
                 - self: {id: 8120, title: 'Asia', slug: '/divisions/asia', description: 'not visible as not mounted'}
                 - self: {id: 8130, title: 'South America', slug: '/divisions/south-america'}
    +    - self: {id: 9100, title: 'Page 9100', type: *pageStandard, root: true, slug: '/page-9100', perms_userid: 1, perms_groupid: 1, perms_everybody: 15}
    +    - self: {id: 9200, title: 'Page 9200', type: *pageStandard, root: true, slug: '/page-9200', perms_userid: 1, perms_groupid: 1, perms_everybody: 0}
    +    - self: {id: 9300, title: 'Page 9300', type: *pageStandard, root: true, slug: '/page-9300', perms_userid: 1, perms_groupid: 1, perms_everybody: 15}
    
  • Tests/Functional/Controller/Page/TreeControllerTest.php+49 2 modified
    @@ -65,17 +65,18 @@ protected function setUp(): void
             $this->withDatabaseSnapshot(function () {
                 $this->importCSVDataSet(__DIR__ . '/Fixtures/be_users.csv');
                 // Admin user for importing dataset
    -            $this->backendUser = $this->setUpBackendUser(1);
    +            $backendUser = $this->setUpBackendUser(1);
                 Bootstrap::initializeLanguageObject();
                 $scenarioFile = __DIR__ . '/Fixtures/PagesWithBEPermissions.yaml';
                 $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
    -            $writer = DataHandlerWriter::withBackendUser($this->backendUser);
    +            $writer = DataHandlerWriter::withBackendUser($backendUser);
                 $writer->invokeFactory($factory);
                 static::failIfArrayIsNotEmpty($writer->getErrors());
             });
     
             // Regular editor, non admin
             $this->backendUser = $this->setUpBackendUser(9);
    +        Bootstrap::initializeLanguageObject();
             $this->context = GeneralUtility::makeInstance(Context::class);
             $this->subject = $this->getAccessibleMock(TreeController::class, null);
         }
    @@ -278,6 +279,19 @@ public function getAllEntryPointPageTreesWithRootPageAsMountPoint(): void
                                 ],
                             ],
                         ],
    +                    [
    +                        // 9100 is shown due to `perms_everybody=15`
    +                        'uid' => 9100,
    +                        'title' => 'Page 9100',
    +                        '_children' => [],
    +                    ],
    +                    // 9200 is omitted due to `perms_everybody=0`
    +                    [
    +                        // 9300 is shown due to `perms_everybody=15`
    +                        'uid' => 9300,
    +                        'title' => 'Page 9300',
    +                        '_children' => [],
    +                    ],
                     ],
                 ],
                 [
    @@ -721,6 +735,39 @@ static function (AfterPageTreeItemsPreparedEvent $event) use (&$afterPageTreeIte
             self::assertEquals('ACME Inc', $afterPageTreeItemsPreparedEvent->getItems()[1]['name']);
         }
     
    +    public static function fetchDataActionConsidersPermissionsDataProvider(): \Generator
    +    {
    +        yield 'admin user can see all root pages' => [
    +            'backendUser' => 1,
    +            'expectation' => ['0', '1000', '2000', '7000', '8000', '9100', '9200', '9300'],
    +        ];
    +        yield 'editor with DB mounts can only see accessible pages' => [
    +            'backendUser' => 9,
    +            'expectation' => ['0', '1000', '8110'],
    +        ];
    +        yield 'editor with DB mounts cannot see inaccessible pages' => [
    +            'backendUser' => 8,
    +            'expectation' => ['0'],
    +        ];
    +        yield 'editor without DB mounts cannot see any pages' => [
    +            'backendUser' => 7,
    +            'expectation' => ['0'],
    +        ];
    +    }
    +
    +    #[Test]
    +    #[DataProvider('fetchDataActionConsidersPermissionsDataProvider')]
    +    public function fetchDataActionConsidersPermissions(int $backendUser, array $expectation): void
    +    {
    +        $this->backendUser = $this->setUpBackendUser($backendUser);
    +        $request = (new ServerRequest(new Uri('https://example.com')))->withQueryParams(['depth' => 1]);
    +        $response = (new TreeController())->fetchDataAction($request);
    +        $data = json_decode((string)$response->getBody(), true);
    +        $items = array_filter($data, static fn(array $page): bool => $page['depth'] <= 1);
    +        $items = array_map(static fn(array $page): string => $page['identifier'], $items);
    +        self::assertSame($expectation, array_values($items));
    +    }
    +
         private function setWorkspace(int $workspaceId): void
         {
             $this->backendUser->workspace = $workspaceId;
    
a7b3c924014a

[SECURITY] Show only explicitly configured page tree information

https://github.com/TYPO3-CMS/backendOliver HaderOct 8, 2024via ghsa
4 files changed · +69 17
  • Classes/Controller/Page/TreeController.php+4 4 modified
    @@ -420,11 +420,11 @@ protected function pagesToFlatArray(array $page, int $entryPoint, int $depth = 0
                 'allowEdit' => $this->userHasAccessToModifyPagesAndToDefaultLanguage && $backendUser->doesUserHaveAccess($page, Permission::PAGE_EDIT),
             ];
     
    -        if (!empty($page['_children']) || $this->pageTreeRepository->hasChildren($pageId)) {
    +        if ($depth >= $this->levelsToFetch && $this->pageTreeRepository->hasChildren($pageId)) {
    +            $page = $this->pageTreeRepository->getTreeLevels($page, 1);
    +        }
    +        if (!empty($page['_children'])) {
                 $item['hasChildren'] = true;
    -            if ($depth >= $this->levelsToFetch) {
    -                $page = $this->pageTreeRepository->getTreeLevels($page, 1);
    -            }
             }
             if (!empty($prefix)) {
                 $item['prefix'] = htmlspecialchars($prefix);
    
  • Classes/Tree/Repository/PageTreeRepository.php+8 9 modified
    @@ -168,29 +168,28 @@ protected function applyCallbackToChildren(array &$tree, callable $callback)
          *
          * @param array $pageTree The page record of the top level page you want to get the page tree of
          * @param int $depth Number of levels to fetch
    -     * @param array $entryPointIds entryPointIds to include
    +     * @param ?array $entryPointIds entryPointIds to include (null in case no entry-points were provided)
          * @return array An array with page records and their children
          */
    -    public function getTreeLevels(array $pageTree, int $depth, array $entryPointIds = []): array
    +    public function getTreeLevels(array $pageTree, int $depth, ?array $entryPointIds = null): array
         {
             $groupedAndSortedPagesByPid = [];
    -
    -        if (count($entryPointIds) > 0) {
    +        // the method was called without any entry-point information
    +        if ($entryPointIds === null) {
    +            $parentPageIds = [$pageTree['uid']];
    +            // the method was called with entry-point information, that is not empty
    +        } elseif ($entryPointIds !== []) {
                 $pageRecords = $this->getPageRecords($entryPointIds);
                 $groupedAndSortedPagesByPid[$pageTree['uid']] = $pageRecords;
                 $parentPageIds = $entryPointIds;
    -        } else {
    -            $parentPageIds = [$pageTree['uid']];
             }
    -
             for ($i = 0; $i < $depth; $i++) {
    +            // stop in case the initial or recursive query did not have any pages
                 if (empty($parentPageIds)) {
                     break;
                 }
                 $pageRecords = $this->getChildPageRecords($parentPageIds);
    -
                 $groupedAndSortedPagesByPid = $this->groupAndSortPages($pageRecords, $groupedAndSortedPagesByPid);
    -
                 $parentPageIds = array_column($pageRecords, 'uid');
             }
             $this->addChildrenToPage($pageTree, $groupedAndSortedPagesByPid);
    
  • Tests/Functional/Controller/Page/Fixtures/PagesWithBEPermissions.yaml+7 4 modified
    @@ -4,8 +4,6 @@ __variables:
       - &pageMount 7
       - &pageFolder 254
       - &contentText 'text'
    -  - &idAcmeRootPage 1000
    -  - &idAcmeFirstPage 1100
     
     entitySettings:
       '*':
    @@ -52,11 +50,13 @@ entities:
         - self: {id: 2, title: 'Franco-Canadian', code: 'fr'}
         - self: {id: 3, title: 'Spanish', code: 'es'}
       beGroup:
    +    - self: {id: 7, title: 'editors without DB mounts', db_mountpoints: '', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
    +    - self: {id: 8, title: 'editors with DB mounts', db_mountpoints: '9200', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
         - self: {id: 9, title: 'editors', db_mountpoints: '1000,2000,8110', tables_select: 'pages,tt_content', tables_modify: 'pages,tt_content', page_types_select: '1,4,7,254', groupMods: 'web_layout,web_list'}
       page:
    -    - self: {id: *idAcmeRootPage, title: 'ACME Inc', type: *pageShortcut, shortcut: 'first', root: true, slug: '/'}
    +    - self: {id: 1000, title: 'ACME Inc', type: *pageShortcut, shortcut: 'first', root: true, slug: '/'}
           children:
    -        - self: {id: *idAcmeFirstPage, title: 'EN: Welcome', slug: '/welcome', subtitle: 'hello-and-welcome'}
    +        - self: {id: 1100, title: 'EN: Welcome', slug: '/welcome', subtitle: 'hello-and-welcome'}
             - self: {id: 1200, title: 'EN: Features', slug: '/features'}
               children:
                 - self: {id: 1210, title: 'EN: Frontend Editing', slug: '/features/frontend-editing', perms_userid: 9, perms_groupid: 1, description: "accessible for user, but not for group"}
    @@ -160,3 +160,6 @@ entities:
                             - { action: 'move', type: 'toPage', target: 1700 }
                 - self: {id: 8120, title: 'Asia', slug: '/divisions/asia', description: 'not visible as not mounted'}
                 - self: {id: 8130, title: 'South America', slug: '/divisions/south-america'}
    +    - self: {id: 9100, title: 'Page 9100', type: *pageStandard, root: true, slug: '/page-9100', perms_userid: 1, perms_groupid: 1, perms_everybody: 15}
    +    - self: {id: 9200, title: 'Page 9200', type: *pageStandard, root: true, slug: '/page-9200', perms_userid: 1, perms_groupid: 1, perms_everybody: 0}
    +    - self: {id: 9300, title: 'Page 9300', type: *pageStandard, root: true, slug: '/page-9300', perms_userid: 1, perms_groupid: 1, perms_everybody: 15}
    
  • Tests/Functional/Controller/Page/TreeControllerTest.php+50 0 modified
    @@ -23,6 +23,8 @@
     use TYPO3\CMS\Core\Context\Context;
     use TYPO3\CMS\Core\Context\WorkspaceAspect;
     use TYPO3\CMS\Core\Core\Bootstrap;
    +use TYPO3\CMS\Core\Http\ServerRequest;
    +use TYPO3\CMS\Core\Http\Uri;
     use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
     use TYPO3\CMS\Core\Utility\GeneralUtility;
     use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
    @@ -291,6 +293,19 @@ public function getAllEntryPointPageTreesWithRootPageAsMountPoint(): void
                                 ],
                             ],
                         ],
    +                    [
    +                        // 9100 is shown due to `perms_everybody=15`
    +                        'uid' => 9100,
    +                        'title' => 'Page 9100',
    +                        '_children' => [],
    +                    ],
    +                    // 9200 is omitted due to `perms_everybody=0`
    +                    [
    +                        // 9300 is shown due to `perms_everybody=15`
    +                        'uid' => 9300,
    +                        'title' => 'Page 9300',
    +                        '_children' => [],
    +                    ],
                     ],
                 ],
                 [
    @@ -723,6 +738,41 @@ public function getSubtreeForAccessiblePageInWorkspace(): void
             self::assertEquals($expected, $actual);
         }
     
    +    public static function fetchDataActionConsidersPermissionsDataProvider(): \Generator
    +    {
    +        yield 'admin user can see all root pages' => [
    +            'backendUser' => 1,
    +            'expectation' => ['0', '1000', '2000', '7000', '8000', '9100', '9200', '9300'],
    +        ];
    +        yield 'editor with DB mounts can only see accessible pages' => [
    +            'backendUser' => 9,
    +            'expectation' => ['0', '1000', '8110'],
    +        ];
    +        yield 'editor with DB mounts cannot see inaccessible pages' => [
    +            'backendUser' => 8,
    +            'expectation' => ['0'],
    +        ];
    +        yield 'editor without DB mounts cannot see any pages' => [
    +            'backendUser' => 7,
    +            'expectation' => ['0'],
    +        ];
    +    }
    +
    +    /**
    +     * @test
    +     * @dataProvider fetchDataActionConsidersPermissionsDataProvider
    +     */
    +    public function fetchDataActionConsidersPermissions(int $backendUser, array $expectation): void
    +    {
    +        $this->backendUser = $this->setUpBackendUser($backendUser);
    +        $request = (new ServerRequest(new Uri('https://example.com')))->withQueryParams(['depth' => 1]);
    +        $response = (new TreeController())->fetchDataAction($request);
    +        $data = json_decode((string)$response->getBody(), true);
    +        $items = array_filter($data, static fn(array $page): bool => $page['depth'] <= 1);
    +        $items = array_map(static fn(array $page): string => $page['identifier'], $items);
    +        self::assertSame($expectation, array_values($items));
    +    }
    +
         /**
          * @param int $workspaceId
          */
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.