Skip to main content
Vulnerability Database/CVE-2026-107851

CVE-2026-107851: Contao CMS Privilege Escalation Vulnerability

CVE-2026-107851 is a privilege escalation flaw in Contao CMS that allows low-privileged backend users to access unauthorized data tables. This post explains its technical details, affected versions, and mitigation steps.

Published:

CVE-2026-107851 Overview

CVE-2026-107851 is a broken access control vulnerability in Contao, an open source content management system (CMS). The flaw resides in TableAccessVoter::hasAccessToModule() within core-bundle/src/Security/Voter/DataContainer/TableAccessVoter.php. Authorization decisions are cached using only the user's token hash, without including the target table name. A low-privileged backend user who first queries a permitted table and then a denied table can receive the cached allow decision for the second request. This affects Contao versions 5.7.0 through 5.7.11 and is fixed in 5.7.12.

Critical Impact

Authenticated backend users can read, create, update, or delete records in tables outside their assigned module permissions, including member and newsletter-subscriber data.

Affected Products

  • Contao CMS version 5.7.0 through 5.7.11
  • contao/core-bundle package (same version range)
  • Deployments relying on TableAccessVoter and DefaultDataContainerVoter for backend module separation

Discovery Timeline

  • 2026-10-09 - CVE-2026-107851 published to NVD
  • 2026-10-16 - Last updated in NVD database

Technical Details for CVE-2026-107851

Vulnerability Analysis

Contao's backend uses Symfony voters to decide whether an authenticated user may perform CreateAction, ReadAction, UpdateAction, or DeleteAction operations against data container tables. TableAccessVoter::hasAccessToModule() iterates over $GLOBALS['BE_MOD'] modules, checks if the current table belongs to a module the user may access, and caches the result in the canAccessTable and canReadAccessTable arrays.

The cache key was derived only from hash('xxh128', serialize($token)). The target table name returned by $subject->getDataSource() was not part of the key. When a backend user triggered a decision on an allowed table first, subsequent voter calls for other tables reused the stored true value. In combination with DefaultDataContainerVoter, an incorrect abstention was converted into a grant, bypassing module-to-table scoping. This maps to [CWE-524: Use of Cache Containing Sensitive Information].

Root Cause

The cache storing authorization results used an insufficient key. Because $tokenHash is identical across every voter invocation in a given request chain for the same user, any decision cached for one table was returned verbatim for every subsequent table check during that process.

Attack Vector

An attacker needs a valid low-privileged backend account. They navigate to a legitimately accessible module to prime the voter cache, then issue requests targeting tl_member, tl_newsletter_recipients, or other restricted data container tables through the Contao backend routes. The voter returns the previously cached true decision, allowing CRUD operations on records that the user's module permissions do not cover.

php
     private function hasAccessToModule(TokenInterface $token, CreateAction|DeleteAction|ReadAction|UpdateAction $subject): bool
     {
         $tokenHash = hash('xxh128', serialize($token));
+        $table = $subject->getDataSource();
+        $cacheKey = $tokenHash.' '.$table;
 
-        if (isset($this->canAccessTable[$tokenHash]) || ($subject instanceof ReadAction && isset($this->canReadAccessTable[$tokenHash]))) {
-            return $this->canAccessTable[$tokenHash] ?? $this->canReadAccessTable[$tokenHash];
+        if (isset($this->canAccessTable[$cacheKey]) || ($subject instanceof ReadAction && isset($this->canReadAccessTable[$cacheKey]))) {
+            return $this->canAccessTable[$cacheKey] ?? $this->canReadAccessTable[$cacheKey];
         }
 
-        $table = $subject->getDataSource();
-
         foreach ($GLOBALS['BE_MOD'] as $modules) {
             foreach ($modules as $name => $config) {
                 if (
                     \is_array($config['tables'] ?? null)
                     && \in_array($table, $config['tables'], true)
                     && $this->accessDecisionManager->decide($token, [ContaoCorePermissions::USER_CAN_ACCESS_MODULE], $name)
                 ) {
-                    return $this->canAccessTable[$tokenHash] = true;
+                    return $this->canAccessTable[$cacheKey] = true;
                 }

Source: GitHub commit 9d6f582. The patch adds the table name to the cache key, ensuring per-table authorization decisions are stored and retrieved independently.

Detection Methods for CVE-2026-107851

Indicators of Compromise

  • Backend audit log entries showing a single low-privileged user accessing data container tables outside their configured module scope (for example, hits on tl_member or tl_newsletter_recipients from a user limited to content modules).
  • Unexpected INSERT, UPDATE, or DELETE statements against member or subscriber tables originating from Contao backend sessions.
  • Rapid sequences of backend requests from the same session alternating between an allowed data container and a restricted one.

Detection Strategies

  • Inventory running Contao installations and flag any instance reporting a version between 5.7.0 and 5.7.11.
  • Correlate backend session identifiers with the set of tables touched per request and alert when the table set exceeds the user's assigned modules.
  • Review web server access logs for /contao backend requests referencing do= module parameters that do not match the authenticated user's role.

Monitoring Recommendations

  • Enable Contao's backend logging and forward it to a central log platform for query and retention.
  • Monitor the tl_log table for ACCESS and GENERAL category entries tied to sensitive data containers.
  • Alert on schema or record changes to tl_member, tl_newsletter_recipients, and any custom tables containing personal data.

How to Mitigate CVE-2026-107851

Immediate Actions Required

  • Upgrade contao/core-bundle to version 5.7.12 or later using Composer and redeploy the application.
  • Audit backend user activity logs for the period the vulnerable release has been in production and investigate any cross-module access.
  • Rotate credentials and session tokens for backend accounts if evidence of exploitation is found.

Patch Information

The fix is included in Contao 5.7.12, released as GitHub Release 5.7.12. The code change is published in GitHub commit 9d6f582 and documented in GitHub Security Advisory GHSA-5974-gfqc-wrcm. The patch adds $subject->getDataSource() to the cache key used by TableAccessVoter::hasAccessToModule(), isolating decisions per table.

Workarounds

  • Restrict backend login to trusted administrators only until the upgrade is deployed.
  • Place the /contao backend behind VPN or IP allow-listing to limit who can reach the vulnerable voter.
  • Temporarily reduce the number of backend modules assigned to low-privileged users to shrink the attack surface for cache priming.
bash
# Upgrade Contao to the patched release
composer require contao/core-bundle:^5.7.12 --with-all-dependencies
composer update contao/* --with-all-dependencies
vendor/bin/contao-console contao:migrate --no-interaction

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.