Jul 13, 2026·6 min read·28 visits
Unsanitized user-controlled metadata fields rendered via the safe filter in GeoNode templates allow unauthenticated or low-privilege users to store malicious script payloads. Upon administrative review, the script executes within the victim session, permitting silent account takeover.
A comprehensive security analysis of CVE-2024-27091, a stored cross-site scripting (XSS) vulnerability in the GeoNode geospatial content management system. Unsanitized metadata fields rendered with Django's safe template filter permit stored JavaScript execution, leading directly to admin account takeover via CSRF token theft and silent profile email modification.
GeoNode is a specialized geospatial content management system used globally to manage, catalog, and publish spatial databases. Because GeoNode hosts administrative functions and map publishing controls, its users often possess elevated system permissions. The application exposes an extensive metadata interface allowing users to document layers, maps, and documents. These metadata parameters accept rich-text formatting to facilitate highly structured documentation.
This documentation structure exposes a distinct attack surface. Unauthenticated or low-privilege registered users can submit custom metadata configurations via the platform's REST API or UI forms. Fields such as abstracts, purposes, and keywords are saved directly to the underlying PostgreSQL database. The application's failure to sanitize these parameters before database insertion or HTML rendering creates a stored cross-site scripting (XSS) pathway.
When a high-privilege user or administrator browses to the metadata details of a poisoned resource, the payload executes. Since the script executes within the victim's validated origin, it bypasses authorization restrictions without relying on cookie extraction. This vulnerability is cataloged as CWE-79, representing improper neutralization of input during web page generation.
The root cause of CVE-2024-27091 resides in GeoNode's integration of Django's template engine. Django templates are secure by default because they automatically HTML-escape dynamic variables. However, developers can override this safety feature using the |safe filter. The |safe filter explicitly instructs the rendering engine that the variable contents are pre-sanitized and can be rendered directly as raw markup.
In affected versions, the template file geonode/templates/metadata_detail.html used the |safe filter on multiple user-controlled attributes. This included resource.abstract, keyword.name, resource.constraints_other, resource.purpose, resource.data_quality_statement, and resource.supplemental_information. No backend sanitization step occurred before the templates rendered these fields, making them highly vulnerable to payload injection.
Because the rich-text WYSIWYG editor's client-side protections were the only barrier against script injection, attackers could easily bypass them. An attacker could issue direct API requests or bypass frontend input validation entirely. The backend did not enforce server-side validation or tag parsing, allowing database tables to store dangerous raw HTML wrappers.
The vulnerability was patched in commit e53bdeff331f4b577918927d60477d4b50cca02f by implementing a server-side HTML sanitization layer. The developers introduced a custom Django template filter named sanitize_html in geonode/base/templatetags/sanitize_html.py. This filter relies on the nh3 Python library, which leverages the Rust-based ammonia library to guarantee performant and secure HTML purification.
Below is the implementation of the custom filter from the official patch:
from django import template
from django.conf import settings
import nh3
from django.utils.encoding import force_str
register = template.Library()
@register.filter(name="sanitize_html")
def sanitize_html(value):
value = force_str(value)
nh3_config = getattr(
settings,
"NH3_DEFAULT_CONFIG",
{
"tags": {"b", "a", "img", "p", "ul", "li", "strong", "em", "span"},
},
)
return nh3.clean(value, **nh3_config)The custom filter parses the HTML structure and strips all tags and attributes not defined in the safe whitelist. Elements like <script>, <iframe>, and event handlers (e.g., onerror) are completely removed. In the metadata templates, all instances of user-controlled variables were modified to chain this sanitization filter ahead of the |safe filter:
<!-- Vulnerable Template Implementation -->
<dd>{{ resource.abstract|safe }}</dd>
<!-- Patched Template Implementation -->
<dd>{{ resource.abstract|sanitize_html|safe }}</dd>An attacker can exploit this vulnerability to achieve full account takeover of any platform administrator who views the compromised metadata page. Although GeoNode sets session cookies with the HttpOnly and Secure attributes, which blocks direct extraction via document.cookie, the execution of arbitrary JavaScript within the application's origin allows attackers to perform complex state-changing actions on behalf of the victim.
The attack begins when the victim loads the detail page of a modified resource. The malicious script executes and immediately reads the anti-CSRF token directly from the DOM, specifically targeting the csrfmiddlewaretoken hidden input field or parsing it from the local cookie store. Because the browser automatically attaches the victim's session cookies to requests sent to the same origin, the script is authorized to perform state-changing HTTP requests.
Once the CSRF token is obtained, the script initiates a background POST request to the email management endpoint (e.g., /account/email/change/). The payload contains the stolen CSRF token and updates the primary email address to an inbox controlled by the attacker. Since this request executes seamlessly in the background, the administrator receives no immediate alert. The attacker then triggers a standard password reset request for the admin account, receiving the reset token via the newly registered email address to complete the takeover.
The security impact of CVE-2024-27091 is classified as medium-severity with a CVSS v3.1 base score of 6.1. This score reflects an attack vector requiring low complexity and zero initial privileges, but relying heavily on user interaction. The scope change (S:C) highlights that the script execution context can compromise resource parameters beyond the immediate data layer.
A successful compromise can lead to a full takeover of administrative sessions. In a geospatial environment, an administrative takeover provides the adversary with total access to maps, imagery datasets, secure databases, and internal infrastructure configurations. Attackers could manipulate geographical boundary data, delete spatial databases, or use the compromised platform to launch downstream attacks against public GIS map users.
While the EPSS score of 0.00376 indicates a low probability of immediate automated exploitation in the wild, targeted environments face a high threat. This flaw acts as a silent privilege escalation bottleneck. Security operations should treat it as high risk, particularly in federal, academic, or corporate spatial systems where GeoNode serves as a central data repository.
The primary remediation path for CVE-2024-27091 is upgrading all GeoNode deployments to version 4.2.3 or later. This upgrade modifies the system dependencies to require the Rust-backed nh3 HTML sanitizer, ensuring all stored user-supplied metadata fields are processed before rendering. For systems running older, unpatched releases where an immediate upgrade is not feasible, administrators must apply manual code-level mitigations.
To implement a manual hotfix, security engineers should copy the custom sanitize_html.py tag filter file from the official patch commit and install the nh3 library (pip install nh3). All template files referencing |safe filters must be reviewed. Apply the |sanitize_html filter preceding any |safe filter as demonstrated in the patch diff.
In addition to patching, deploying a strong Content Security Policy (CSP) is highly recommended. Enforcing a policy such as Content-Security-Policy: default-src 'self'; script-src 'self'; prevents inline scripts and event handlers from executing. This defense-in-depth measure completely neutralizes the threat of stored XSS, even if sanitization is bypassed or omitted on future templates.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
GeoNode GeoSolutions Group | >= 3.2.0, < 4.2.3 | 4.2.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 (Improper Neutralization of Input During Web Page Generation) |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 | 6.1 (Medium) |
| Exploit Status | Proof of Concept / Code Analysis Available |
| EPSS Score | 0.00376 (Percentile: 29.75%) |
| KEV Status | Not Listed |
The software does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page that is served to other users.
A Server-Side Template Injection (SSTI) vulnerability in the Silverstripe Advanced Workflow module allows authenticated attackers with workflow authoring permissions to achieve arbitrary code execution. By manipulating the NotifyUsersWorkflowAction.EmailTemplate field, attackers can inject template code that dynamically executes arbitrary PHP commands via the core translation helper interpolation path.
A path traversal and arbitrary file write vulnerability exists in the libreoffice-convert Node.js package in all versions prior to 1.8.2. The convertWithOptions function fails to validate or sanitize the caller-controlled options.fileName parameter, allowing directory traversal sequences to write files outside the temporary directory.
Crossplane's runtime package manager engine contains a Time-of-Check to Time-of-Use (TOCTOU) race condition in its container signature verification pipeline. When Crossplane parses package definitions using dynamic tag-based references, it resolves the tag on the remote OCI registry twice: once during the signature verification step (the 'Check' phase) and once during the fetch and install step (the 'Use' phase). An attacker controlling the destination OCI registry can exploit this vulnerability by serving a validly signed benign image for the verification phase, and then dynamically swapping the tag to point to an unsigned, malicious package during the fetch phase.
A Server-Side Template Injection (SSTI) vulnerability in the Silverstripe UserForms module allows authenticated CMS users with basic form configuration privileges to achieve remote code execution (RCE). The flaw resides in the processing of the email recipient subject field, where user-supplied template translation tags are evaluated by the template engine, leading to arbitrary PHP execution via dynamic variable interpolation.
CVE-2026-54356 is a missing authorization vulnerability (CWE-862) within the backend component of the Budibase low-code platform. The vulnerability exists inside the `@budibase/server` package in versions prior to 3.41.3. An authenticated user with the lowest privilege level can invoke the attachment upload URL endpoint directly and obtain an S3 pre-signed PutObject URL signed with the server's S3 credentials.
CVE-2026-54556 is a high-severity Denial of Service (DoS) vulnerability impacting the Ember HTTP/2 backend of http4s, a popular functional Scala interface for HTTP services. The vulnerability arises from an improper handling of highly compressed HPACK header blocks, which enables unauthenticated remote attackers to trigger severe memory amplification and crash the JVM runtime via an OutOfMemoryError.