Aug 21, 2026·6 min read·4 visits
Low-privileged users with 'add' permissions on a snippet model can bypass read restrictions by copying arbitrary snippet IDs, exposing sensitive content.
An authorization bypass vulnerability in Wagtail CMS allows authenticated users with snippet creation privileges ('add') to access and view the contents of restricted snippet instances for which they lack viewing or editing permissions. By invoking the copy endpoint, the application pre-populates form data with the properties of the source snippet, exposing sensitive information to unauthorized users.
Wagtail is an open-source Content Management System built on the Django web framework. It provides a highly customizable content structure through snippets, which are reusable models registered within the Wagtail admin interface. The system uses a granular role-based access control framework mapped to standard Django permissions to restrict operations on these snippets.
The vulnerability exists within the logic governing the snippet duplication action. The copying mechanism is designed to let administrative users copy an existing snippet instance into a new draft. However, the system failed to enforce sufficient read authorization controls on the source model instance during this sequence.
An attacker holding the 'add' permission on a given snippet class can access the copy endpoint for any existing database ID. Because the application processes this copy operation by rendering the creation form with the source snippet's data, the attacker receives the contents of private snippets in the rendered HTML output. This exposure bypasses 'view' or 'change' model restrictions, resulting in unauthorized information disclosure.
The root cause of this vulnerability lies in an incomplete authorization check within Wagtail's administrative views for snippets. To manage a snippet model, Django relies on four explicit permissions: 'add', 'change', 'view', and 'delete'. When a user triggers the copy view, the backend processes a request to create a new record derived from an existing record.
The vulnerable view evaluated only the user's authority to instantiate a new record. Consequently, the check validated that the user possessed the 'add' permission for the snippet class. It did not, however, perform an access check against the source object being cloned to verify if the requesting user was authorized to read it.
By omitting checks for the 'view' or 'change' permissions on the source object, the view implicitly assumed that anyone authorized to create a snippet was also authorized to read all existing snippets of that class. This logical flaw allows horizontal or vertical privilege escalation depending on how permissions are distributed across different user roles within the Wagtail administrative dashboard.
The vulnerability manifests in how the Wagtail snippet views populate the initial data dictionary for the copy form. In a vulnerable setup, the view accepts the model name and the primary key of the target instance. The backend queries the database for the source object and instantiates a model form using the returned instance.
Below is a representation of the vulnerable view logic compared with the patched view implementation.
# Vulnerable Logic
def copy_view(request, app_label, model_name, pk):
model = get_snippet_model(app_label, model_name)
# Only verifies permission to add new instances
if not user_has_perm(request.user, "add", model):
raise PermissionDenied
# Resolves the instance and pre-populates the form
# without verifying if user can read the source instance
instance = get_object_or_404(model, pk=pk)
form = SnippetForm(instance=instance)
return render(request, "copy.html", {"form": form})# Patched Logic
def copy_view(request, app_label, model_name, pk):
model = get_snippet_model(app_label, model_name)
if not user_has_perm(request.user, "add", model):
raise PermissionDenied
# Enforces that the user must hold either view or change
# permissions on the source model class before proceeding.
has_view = user_has_perm(request.user, "view", model)
has_change = user_has_perm(request.user, "change", model)
if not (has_view or has_change):
raise PermissionDenied
instance = get_object_or_404(model, pk=pk)
form = SnippetForm(instance=instance)
return render(request, "copy.html", {"form": form})The patched code introduces a logical disjunction requiring either 'view' or 'change' permission for the snippet model, alongside the 'add' permission. This prevents unauthorized reading while retaining the intended functionality for legitimate administrators.
Exploitation of this vulnerability does not require highly sophisticated attack methods. An attacker needs an active session in the Wagtail administration interface with a role that permits snippet creation for a targeted snippet class. The attacker then targets the endpoint responsible for handling snippet duplication requests.
Because Wagtail utilizes predictable, sequential integer identifiers for database rows by default, the attacker can systematically iterate through numerical values in the URL structure. A standard request constructed by an attacker would resemble the following GET request to the administration control panel.
GET /admin/snippets/app_label/target_model/copy/1024/ HTTP/1.1
If the server yields an HTTP 200 OK status instead of an HTTP 403 Forbidden response, the vulnerability is verified. The returned HTML contains the pre-populated input fields of the form. The attacker can extract the field values programmatically or manually directly from the page source, achieving full read access to the target snippet instance.
The primary security consequence of this vulnerability is the loss of confidentiality. The severity is marked as Medium with a CVSS v3.1 score of 6.5. This classification reflects that exploitation does not affect the integrity of the original snippet and carries no availability impact.
Despite the lack of integrity impact, the confidentiality risk remains substantial if snippets contain sensitive data. In Wagtail environments, snippets are often used to store API configuration tokens, customer information, internal documentation, or metadata. If a low-privileged editor or external contributor can view these assets, the breach could escalate to broader systemic compromise.
Furthermore, the vulnerability requires low privileges and no user interaction. This combination makes it suitable for automated internal enumeration. Because the attack vector is purely network-based, any authenticated user capable of reaching the Wagtail administrative panel poses a risk of data exfiltration.
The recommended resolution is to upgrade Wagtail to a patched version immediately. The security patch is backported to all active release branches. Administrators must deploy version 7.0.9, 7.3.4, 7.4.3, or 8.0rc2 depending on their current major release branch.
For deployments where upgrading packages is temporarily unfeasible, access control modifications must be enforced. Security teams should audit Django group assignments and revoke 'add' permissions for any snippet class containing sensitive data from users who do not require reading access. This minimizes the active attack surface.
To detect potential exploitation, security operations teams should analyze web server access logs. Standard telemetry should be scanned for sequential patterns targeting the /copy/ paths of the Wagtail admin interface. A pattern of rapid GET requests directed at multiple instance IDs from a single low-privileged account indicates active enumeration.
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 CMS Wagtail | >= 0, < 7.0.9 | 7.0.9 |
Wagtail CMS Wagtail | >= 7.1, < 7.3.4 | 7.3.4 |
Wagtail CMS Wagtail | >= 7.4, < 7.4.3 | 7.4.3 |
Wagtail CMS Wagtail | == 8.0rc1 | 8.0rc2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-285 |
| Attack Vector | Network |
| CVSS Score | 6.5 |
| Exploit Status | none |
| KEV Status | Not listed |
| Impact | Confidentiality |
The software does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.
An information disclosure vulnerability in the document serving subsystem of Wagtail CMS allows unauthorized users to verify if private documents match guessed SHA-1 hashes due to improper order of authentication checks.
An improper access control vulnerability in Wagtail's Documents and Images API V2 allows unauthenticated remote attackers to retrieve metadata (including titles and filenames) of files residing inside descendant collections of private parent collections, bypassing inherited view restrictions.
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.
An uncontrolled resource allocation vulnerability (CWE-770) affects Mailpit SMTP server versions 1.30.0 through 1.30.4. The vulnerability is located within the DATA parsing logic, where an unauthenticated remote attacker can stream an endless sequence of bytes devoid of newline characters. Because line size limits are evaluated only after buffer completion, the Go runtime repeatedly allocates memory on the heap to store the single oversized line, causing resource exhaustion and an Out-Of-Memory termination of the service process.
A critical cross-site WebSocket hijacking (CSWSH) vulnerability in Mailpit allows malicious websites to bypass CORS security controls via URL-encoded path mismatches, exposing sensitive development SMTP communications to unauthorized actors.
A critical vulnerability in Dgraph Alpha allows unauthenticated network clients to delete and replace database stores. The public gRPC interface on port 9080 processes external snapshot streams without enforcing authentication or authorization, triggering immediate database destruction via the storage engine's initialization process.