Cache Key Collision Leads to Node Access Bypass
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3593384. --> Reported by: [smustgrave](https://www.drupal.org/user/3252890) Related to !183 >>> <h3 id="summary-problem-motivation">Problem/Motivation</h3> <p><strong>This was originally reported as a private security issue but cleared by the security team for a public issue</strong></p> <p>A low-privileged authenticated user may be able to view unpublished or otherwise access-restricted child nodes through the printer-friendly export functionality after cache priming by a higher-privileged user.<br> The observed behavior appears related to subtree caching and access filtering during Book export rendering.</p> <h4 id="summary-steps-reproduce">Steps to reproduce</h4> <ol> <li>Enable the Book module.</li> <li>Create a public root node and add it as a new book.</li> <li>Create a child node under the book hierarchy and mark it unpublished.</li> <li>Create a low-privileged authenticated role with:</li> </ol><ul> <li>View published content</li> <li>View printer-friendly books</li> </ul> <li>Verify the low-privileged user receives HTTP 403 when directly accessing the unpublished child node.</li> <li>Clear caches:</li> <li>As an administrator (or any user with view access to the unpublished child), visit: /book/export/html/{root_nid}</li> <li>This populates the persistent cache (cache.data bin) under the key book-links:subtree-data:{root_nid} with a tree containing the unpublished child node.</li> <li>Without clearing caches in between, switch to the low-privileged user session and visit: /book/export/html/{root_nid}</li> <li>Sanity: with the low-privileged user, verify the following still return HTTP 403:</li> <ul> <li> /node/{child_nid}</li> <li> /book/export/html/{child_nid}</li> </ul> <li>This confirms that the user genuinely lacks view access to the restricted child; the leak is solely through the parent export.</li> <p>Observed result:<br> The export response in step 8 includes content from the unpublished child node.<br> Expected result:<br> Restricted child nodes should not appear in export output for users lacking node access permissions.</p> <p>Expected result:<br> Restricted child nodes should not appear in export output for users lacking node access permissions.</p> <p>Technical observations:</p> <ul> <li>BookManager::bookSubtreeData() caches subtree data using: book-links:subtree-data:{nid}</li> <li>The cache key does not appear to include user-specific access context.</li> <li>The chained cache backend defined in book.services.yml (book.backend_chained_cache) appends the persistent cache.data SQL bin, which is shared across all users and survives across requests.</li> <li>The cache row can be observed directly: SELECT cid, expire FROM cache_data WHERE cid LIKE 'book-links:subtree-data:%';</li> <li>The export route validates access for the root node parameter, while subtree rendering later relies on the cached hierarchy representation.</li> <li>exportTraverse() assumes access validation has already occurred during tree construction.</li> </ul> <p>Suggested fix direction: include a hash of the current user's permissions or roles in the cache key (consistent with the user.permissions cache context used elsewhere in core), or re-run bookTreeCheckAccess on retrieval rather than caching the filtered result.</p> <h3 id="summary-proposed-resolution">Proposed resolution</h3> <p>TBD</p> <h3 id="summary-remaining-tasks">Remaining tasks</h3> <p>Fix<br> Test coverage<br> Commit</p> <h3 id="summary-ui-changes">User interface changes</h3> <p>NA</p> <h3 id="summary-api-changes">API changes</h3> <p>NA</p> <h3 id="summary-data-model-changes">Data model changes</h3> <p>NA</p> <p>Credit from use @m4nh_thu4n, @catch, @larowlan, @greggles</p>
issue