Aug 21, 2026·7 min read·12 visits
A missing object-level permission check in Wagtail's page translation endpoint allows authenticated users with global translation rights to duplicate and read restricted pages, bypass access controls, and extract sensitive content.
An authenticated user with global translation permissions can exploit a missing authorization check on the page translation endpoint in Wagtail CMS. This allows the attacker to copy and view pages they do not have explicit edit or explore access to.
Wagtail is an open-source Content Management System (CMS) built on the Django framework. The platform structures content hierarchically as pages, with access controls governed by an object-level permissions model. This design ensures that users can only interact with sections of the site tree for which they have explicit authorization. Among its localization features, Wagtail supports multi-language workflows through its translation framework, specifically implemented in the simple translation module.
The translation copy endpoint is a key component of this localization framework, designed to allow authorized users to duplicate content from one locale to another. To perform this action, a user must possess a global permission allowing translation submissions. This architecture creates an attack surface where a low-privileged authenticated user with global translation rights can interact with arbitrary page objects if the endpoint does not properly validate object-level access controls.
The vulnerability, identified as GHSA-jm5p-837g-rv8g, resides in this specific endpoint. Due to a missing authorization check, a user possessing only the global translation privilege can issue requests to translate any arbitrary page ID. This constitutes an Insecure Direct Object Reference (IDOR) vulnerability, also classified as Broken Object Level Authorization (BOLA), allowing unauthorized read access to restricted page hierarchies.
The root cause of this vulnerability lies in the divergence between global permission verification and object-level permission enforcement within Django and Wagtail. Django applications frequently utilize global model-level permissions to control broad actions, such as whether a user can add, change, or delete any instance of a model. Wagtail complements this with granular, tree-based hierarchical permissions to restrict access to specific subsets of pages.
In the vulnerable implementation, the API view handling the translation copy request only validated the requesting user's global permission. It verified that the user possessed the general translation submission permission. However, once this check passed, the code proceeded to fetch and copy the target page using its database identifier without verifying if the user had permission to view or edit that specific page instance.
Because Wagtail copies the page into a workspace or locale where the low-privileged user possesses editing privileges, the user can subsequently access the copied instance. This indirect data leakage permits the unauthorized user to read the draft status, metadata, and full body content of pages they are restricted from accessing in the primary tree. The application essentially acts as a confused deputy, using its elevated privileges to duplicate restricted pages into an insecure context on behalf of the attacker.
To understand the vulnerability mechanism, we compare the vulnerable logical flow with the patched implementation. In the vulnerable version, the request is processed directly after verifying the global permission, bypassing the page-specific permissions checks defined by the Wagtail permissions engine.
# Vulnerable conceptual implementation
from django.core.exceptions import PermissionDenied
from wagtail.models import Page
def copy_page_for_translation(request, page_id, target_locale):
# Only global permission is checked
if not request.user.has_perm("simple_translation.submit_translation"):
raise PermissionDenied("Missing global translation permission")
# Target page retrieved directly by ID, ignoring object-level hierarchy permissions
page = Page.objects.get(id=page_id)
# The page content is duplicated into the new locale context
new_page = page.copy(to_locale=target_locale)
return new_pageThe patched version corrects this logic gap by enforcing object-level authorization before executing the duplication. The code retrieves the page and requests the user's specific permissions for that object. If the user lacks exploration or editing privileges on the source page, the application raises a permission denied exception.
# Patched conceptual implementation
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from wagtail.models import Page
def copy_page_for_translation(request, page_id, target_locale):
# 1. Enforce global translation permission
if not request.user.has_perm("simple_translation.submit_translation"):
raise PermissionDenied("Missing global translation permission")
# 2. Retrieve the target page safely
page = get_object_or_404(Page, id=page_id)
# 3. CRITICAL FIX: Verify the user has sufficient rights to access the source page
user_page_perms = page.permissions_for_user(request.user)
if not user_page_perms.can_edit() and not user_page_perms.can_describe():
# If the user cannot edit or view the page in the explorer tree, reject the request
raise PermissionDenied("You do not have permission to view or edit this page")
# 4. Proceed with duplication only after passing both validation layers
new_page = page.copy(to_locale=target_locale)
return new_pageThis fix ensures that the user cannot interact with any database object via this API unless their role explicitly authorizes them to access that specific node in the Wagtail tree.
Exploitation of this vulnerability requires an authenticated session belonging to a user account with the globally assigned translation submission permission. An attacker begins by identifying the numerical identifier of a restricted or draft page. While the target page ID is not directly visible to the attacker in the CMS explorer, it can often be obtained through brute-force enumeration, identifier leakage in other public-facing components, or standard application metadata analysis.
Once the target ID is acquired, the attacker submits a direct POST request to the translation copy API endpoint, passing the target page ID and a desired destination locale. Because the endpoint does not validate if the attacker has view or edit rights to the source page, the request completes successfully.
POST /admin/translation/copy/ HTTP/1.1
Host: target-cms.local
Authorization: Session <AuthenticatedSessionToken>
Content-Type: application/json
{
"page_id": 1042,
"target_locale": "fr"
}Following the successful response, the backend creates a duplicate of page 1042 in the French workspace. If the attacker has editing permissions for the French locale tree, they can open the newly generated page. The attacker can then read all sensitive information contained in the original page, which may include unreleased drafts, restricted portal guidelines, or database metadata.
The primary security impact of this vulnerability is the unauthorized exposure of confidential information (CWE-862). Since the duplication process copies all page fields, including draft elements and metadata, an attacker can completely reconstruct restricted pages. This is highly critical in corporate deployments where draft pages are used to prepare pre-release announcements, regulatory filings, or internal policy documents.
Because this vulnerability is restricted to data extraction, it does not directly compromise data integrity or system availability. The attacker cannot modify the source page, nor can they execute arbitrary code on the underlying host. Consequently, the CVSS v3.1 score is calculated as 6.5, reflecting high confidentiality impact with no integrity or availability degradation.
The attack vector is network-based and has low complexity, but is mitigated by the requirement for authenticated access. In organizations that enforce strict privilege segregation, this flaw allows low-privileged editors or external translators to escalate their read privileges horizontally and vertically across the CMS hierarchy.
The primary and recommended remediation is to upgrade Wagtail to the appropriate patched release. Maintenance branches have been updated to address this flaw. Administrators should upgrade to one of the following versions depending on their current release path: 7.0.9, 7.3.4, 7.4.3, or 8.0rc2.
If upgrading immediately is not feasible, organizations can implement manual mitigations to reduce the exposure. The most effective administrative mitigation is to temporarily revoke the translation submission permission from all users who do not require immediate access. Alternatively, organizations can implement custom middleware within their Django configuration to intercept calls to the translation endpoints and validate page-level permissions before handing execution to the view.
Additionally, security teams should review Web Application Firewall (WAF) logs and application audit logs for unauthorized page copy events. Multiple requests to the translation endpoint containing a sequence of page IDs from a single low-privileged account should be treated as potential exploitation and indicator of a manual or automated compromise attempt.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
wagtail Wagtail | < 7.0.9 | 7.0.9 |
wagtail Wagtail | >= 7.1, < 7.3.4 | 7.3.4 |
wagtail Wagtail | >= 7.4, < 7.4.3 | 7.4.3 |
wagtail Wagtail | == 8.0rc1 | 8.0rc2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-862 (Missing Authorization) |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 | 6.5 (Medium) |
| Exploit Status | PoC Concepts Documented |
| Vulnerability Class | IDOR / Broken Object Level Authorization (BOLA) |
| Affected Component | wagtail.contrib.simple_translation |
The software does not perform an authorization check when an actor attempts to access a resource or perform an action.
CVE-2026-109619 (PHP2Shell) is a critical Use-After-Free vulnerability in the core PHP engine caused by improper global state cleanup during out-of-memory exception handling across persistent worker requests.
In Python virtualenv prior to version 21.14.9, activation script generators and configuration writers use Path.exists() before writing files. Because Path.exists() resolves symbolic links, it returns False for dangling symlinks. Consequently, virtualenv skips unlinking pre-planted symbolic links or hard links, writing data directly to target locations outside the intended virtual environment directory.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.
CVE-2026-70035 is a high-severity Remote Code Execution (RCE) vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio caused by improper handling during the deserialization of untrusted data (CWE-502). An unauthenticated attacker can execute arbitrary code within the context of the affected process by delivering a crafted serialized payload.