Aug 21, 2026·6 min read·19 visits
Unauthenticated users can exploit Wagtail's API V2 to enumerate and leak metadata of protected images and documents by leveraging a logical flaw where descendant collections fail to inherit parent access restrictions during API queries.
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.
Wagtail CMS contains a security vulnerability in its media management architecture that exposes sensitive metadata to unauthorized clients. The platform permits administrators to structure media assets hierarchically through a collections subsystem built on Django models. This layout enforces organizational isolation and access control boundaries. The system exposes these assets via public endpoints in the Wagtail Documents and Images API V2.\n\nUnder default configurations, endpoints designed to output public media do not validate ancestral tree structures when assessing view restrictions. While parent collections can be explicitly restricted to authenticated groups or specific passwords, child collections fail to inherit these boundaries when evaluated by the API. This vulnerability is classified as an improper access control issue under CWE-200, CWE-284, and CWE-276.\n\nThis systemic failure allows unauthenticated remote attackers to query the API and list administrative properties of assets inside protected hierarchies. The exposed data includes document names, asset titles, and download path strings. Although the actual file binary access might require separate authentication, leaking asset directories weakens the organizational security posture.
The root cause of this vulnerability lies in the logical implementation of the collection query filters within Wagtail's API viewsets. Wagtail structures collections hierarchically using the django-treebeard library, which implements a materialized path tree pattern. To implement security boundaries, Wagtail stores restriction parameters in the CollectionViewRestriction model, specifying which collection nodes require authentication.\n\nWhen a client sends a request to the Documents or Images API, the backend filters the target queryset to present only public entities. The vulnerable query filtered collections using a direct relational check, specifically searching for collections that had no direct view restriction entry (restrictions__isnull=True). This direct query ignores the tree taxonomy, meaning it checks only for a direct database relation on that specific node.\n\nBecause nested descendant collections do not typically contain direct duplicate CollectionViewRestriction records, they evaluated to True for the restrictions__isnull lookup. This execution bypasses the expected recursive inheritance logic of the tree. Consequently, the API treats descendant collections as fully public, allowing administrative filenames and metadata to be leaked to external API consumers.
The vulnerable code path utilized an overly simplistic approach to identify public files. It filtered media based on whether their direct collections lacked explicit restrictions. The code failed to traverse the ancestor path chain to check if a parent collection was marked restricted.\n\nTo resolve this, the patch introduces recursive tree-path checking. By querying the CollectionViewRestriction model, Wagtail retrieves the paths of all directly restricted collections. It then constructs a Django query that excludes any collection whose tree path starts with any of those restricted paths. Because django-treebeard uses materialized path strings, checking if a path starts with a parent's path matches the parent and all nested descendants.\n\npython\n# Patched implementation in Wagtail collections query framework\nfrom django.db import models\nfrom wagtail.models import CollectionViewRestriction, Collection\n\ndef public(self):\n # Extract paths of collections with active restrictions\n restricted_paths = CollectionViewRestriction.objects.values_list(\n 'collection__path', flat=True\n )\n \n if not restricted_paths:\n return self\n \n # Dynamically build Q object to match restricted paths and descendants\n query = models.Q()\n for path in restricted_paths:\n # Using tree path starts-with pattern to isolate the tree branch\n query |= models.Q(path__startswith=path)\n \n # Exclude all matched paths from the public collection queryset\n return self.exclude(query)\n\n\nmermaid\ngraph LR\n Parent["Restricted Parent Collection\nPath: '00010001'"] --> Child["Nested Child Collection\nPath: '000100010002'"]\n Child --> File["Confidential Document"]\n API["API V2 Query"] -.->|Bypasses restriction| Child\n style API stroke:#f66,stroke-width:2px\n\n\nWe assess this patch as highly robust and complete. Utilizing the tree's materialized path properties guarantees that no depth of nesting can bypass the constraint. This mechanism relies on Django's low-level database query compiler, which prevents logical race conditions and ensures that the restriction filter is executed directly at the database level before serialization.
Exploitation of this vulnerability requires no prior authentication and minimal network complexity. The attacker only needs network access to the public API endpoints. This setup is common in headless Wagtail architectures where the frontend queries the Wagtail backend via API calls.\n\nTo execute the attack, an unauthorized agent sends an HTTP GET request to the target API endpoints. The default routes are /api/v2/images/ or /api/v2/documents/. Under a vulnerable installation, the server returns a complete list of documents or images that reside within nested collections, irrespective of their parent restrictions.\n\nAn exemplary attack request and JSON payload output displays the structural leak of internal documents:\n\njson\n{\n \"meta\": {\n \"total_count\": 1\n },\n \"items\": [\n {\n \"id\": 109,\n \"meta\": {\n \"type\": \"wagtaildocs.Document\",\n \"detail_url\": \"https://example.com/api/v2/documents/109/\",\n \"download_url\": \"/documents/109/q4_merger_strategy_confidential.pdf\"\n },\n \"title\": \"Q4 Merger Strategy Confidential\"\n }\n ]\n}\n\n\nAlthough downloading the file binary from /documents/109/ may prompt an authentication challenge if separate page-level restrictions or document-view middlewares are active, the metadata exposure itself is highly valuable. Attackers learn sensitive titles, structure, and operational classifications, exposing internal activities and business intelligence.
The impact of GHSA-C2XX-CJMH-9Q8F is characterized as moderate-severity information disclosure. In enterprise CMS contexts, Wagtail is often used to manage internal intranet wikis, sensitive documentation pipelines, or staging environments for upcoming products. Exposing filenames and project directories reveals proprietary schedules, personal data, and corporate structure.\n\nAccording to the CVSS v3.1 system, this issue receives a base score of 5.3 (Medium). The vector breakdown highlights key parameters: Network vector (AV:N), Low complexity (AC:L), No permissions required (PR:N), No user interaction (UI:N), and Partial Confidentiality impact (C:L) with No integrity (I:N) or availability (A:N) impact.\n\nAlthough there is no current evidence of active, weaponized exploits in public database systems, verification of the issue remains trivial. The vulnerability can be exposed with standard diagnostic tools, putting organizations at risk of passive automated indexing if their endpoints remain open to the public internet.
The primary remediation path is upgrading the Wagtail core package to the designated secure versions. Maintainers have backported patches across all active release branches. For deployments utilizing the 7.0 series, upgrade to 7.0.9. Deployments on versions 7.1 through 7.3 must update to 7.3.4. For installations running 7.4, upgrade to 7.4.3. Implementations on the 8.0 release candidate series must transition to 8.0rc2.\n\nIf upgrading immediately is not technically feasible due to dependency lockups, administrators can implement a coding workaround. Overriding the default API viewsets inside the API router configuration allows engineers to enforce Django Rest Framework authentication permission classes. Requiring authentication blocks anonymous client enumeration entirely.\n\npython\n# Configuration workaround in api.py\nfrom wagtail.api.v2.router import WagtailAPIRouter\nfrom wagtail.images.api.v2.views import ImagesAPIViewSet\nfrom rest_framework.permissions import IsAuthenticated\n\nclass SecuredImagesAPIViewSet(ImagesAPIViewSet):\n # Restrict endpoint to authenticated users only\n permission_classes = [IsAuthenticated]\n\napi_router = WagtailAPIRouter('wagtailapi')\napi_router.register_endpoint('images', SecuredImagesAPIViewSet)\n\n\nAlternatively, security teams can implement traffic filtration policies on their reverse proxies or Web Application Firewalls (WAF). By configuring rules that block external access to /api/v2/images/ and /api/v2/documents/ while permitting access only to trusted internal microservices, the exposure window is completely closed until application-level patches are deployed.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/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 | 8.0rc2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-200 / CWE-284 / CWE-276 |
| Attack Vector | Network |
| CVSS v3.1 | 5.3 (Medium) |
| Exploit Status | None / Unproven |
| KEV Status | Not Listed |
| Impact | Information Disclosure (Metadata Leakage) |
The product exposes sensitive information to an actor who is not authorized to have access to that information.
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.