CVE-2024-27091: GeoNode Stored XSS Patch Review — Partial fix
Summary
GeoNode previously rendered multiple user-controlled metadata fields with Django's safe filter, enabling stored XSS that could execute in an administrator's browser and lead to session compromise or account takeover. The patch introduces a sanitize_html template filter backed by nh3 and applies it to several vulnerable fields in metadata_detail.html. This materially reduces exploitability for the patched rendering path, but the remediation is template-local and depends on developers consistently replacing raw safe usage elsewhere, so the fix is best characterized as partial rather than a systemic root-cause elimination.
Analysis
Vulnerability
CVE-2024-27091 describes a stored cross-site scripting issue in GeoNode where user-controlled metadata fields were rendered directly with Django's safe filter. The vulnerable template fragment in geonode/templates/metadata_detail.html included fields such as resource.abstract, keyword.name, resource.constraints_other, resource.purpose, resource.data_quality_statement, and resource.supplemental_information rendered as trusted HTML. Because these values are attacker-influenced and were not sanitized before being marked safe, arbitrary script payloads could be stored and later executed in the browser of a reviewing user, including an administrator. The impact stated in the advisory context is consistent with session theft or privileged action execution leading to account takeover. See the upstream patch commit and CVE records for context: upstream commit, CVE record.
<dd>{{ resource.abstract|safe }}</dd>
{{ keyword.name| safe }}
{{ resource.constraints_other|safe }}
<dd>{{ resource.purpose|safe }}</dd>
<dd>{{ resource.data_quality_statement|safe }}</dd>
<dd>{{ resource.supplemental_information|safe }}</dd>The core weakness is not merely missing escaping at one sink, but a trust-boundary failure: untrusted metadata was allowed to flow into HTML rendering with an explicit bypass of Django's auto-escaping.
Patch
The patch in e53bdeff331f4b577918927d60477d4b50cca02f adds a new Django template filter, sanitize_html, implemented in geonode/base/templatetags/sanitize_html.py. The filter converts lazy values with force_str and sanitizes HTML using the nh3 library. A default allowlist of tags is provided, and behavior can be tuned through the NH3_DEFAULT_CONFIG Django setting. The vulnerable template is updated to apply |sanitize_html|safe to the previously unsafe metadata fields. The patch also adds a regression test proving that a <script> tag is removed, and introduces the new dependency nh3==0.2.15 in packaging metadata.
from django import template
from django.conf import settings
import nh3
from django.utils.encoding import force_str
@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)At the sink level, the template change is straightforward:
{% load sanitize_html %}
<dd>{{ resource.abstract|sanitize_html|safe }}</dd>
{{ keyword.name|sanitize_html|safe }}
{{ resource.constraints_other|sanitize_html|safe }}
<dd>{{ resource.purpose|sanitize_html|safe }}</dd>
<dd>{{ resource.data_quality_statement|sanitize_html|safe }}</dd>
<dd>{{ resource.supplemental_information|sanitize_html|safe }}</dd>Review
Pros
The patch directly addresses the demonstrated exploit path by removing raw safe-only rendering for the identified metadata fields and inserting a sanitizer before trust is asserted. Using nh3 is a sound implementation choice for HTML sanitization, and the configuration hook via NH3_DEFAULT_CONFIG gives operators a controlled allowlist mechanism. The explicit regression test for script stripping is useful as a minimum guardrail. Converting lazy objects with force_str also avoids type-related bypasses or inconsistent behavior at render time.
Cons
The remediation appears scoped to one template and a fixed set of fields rather than eliminating the broader anti-pattern of applying safe to untrusted content across the codebase. If other templates or views still render attacker-controlled metadata with safe, the root cause persists elsewhere. The default allowlist includes tags such as a and img, but the provided snippet does not show explicit attribute or protocol restrictions; those may be safely handled by nh3, but the security posture depends on the effective runtime configuration. The test coverage is narrow: it validates removal of a script tag, but does not demonstrate handling of event-handler attributes, dangerous URLs, malformed HTML, SVG/MathML payloads, or other common XSS vectors. Finally, retaining |safe after sanitization is acceptable only if every future call site consistently sanitizes first; this pattern remains easy to misuse.
Verdict
Partial fix.
This patch materially mitigates the known exploit path in metadata_detail.html and is a meaningful security improvement grounded in a dedicated sanitizer. However, based on the supplied diff, it does not establish a systemic invariant that untrusted rich text is sanitized before any trusted rendering throughout GeoNode. For engineering follow-up, the recommended next steps are a repository-wide audit for |safe on user-controlled fields, broader sanitizer regression tests, and ideally centralization of rich-text rendering so that sanitization is enforced by default rather than by template author discipline. References: patch commit, NVD, CVE.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- XSS Store.py
Best match for CVE-2024-27091 because the GeoNode issue is a stored XSS in a Python/Django context. This lab is hands-on and defensive, focusing on finding and fixing persisted XSS patterns that can lead to admin-session compromise and account takeover when malicious content is later rendered.
- XSS.py
Strong language/framework fit for GeoNode’s Python/Django stack. Useful for practicing core server-side output encoding and template-safe rendering decisions, which are central to preventing unsafe rendering such as misuse of a safe filter on user-controlled metadata.
- Calculator.py
Another Python/Django XSS-focused lab that reinforces defensive remediation patterns in templated web applications. Recommended as a follow-up to broaden patch-review skills around input handling, output encoding, and avoiding trust in stored user content before admin review workflows.