Sep 23, 2026·5 min read·19 visits
A stored XSS vulnerability in Nautobot allows low-privileged administrative users to inject malicious scripts into Module Family names or Relationship descriptions, executing arbitrary code when viewed by other users.
CVE-2026-83801 is a stored Cross-Site Scripting (XSS) vulnerability in Nautobot. The vulnerability arises because the application interpolates user-controlled database properties—specifically Relationship descriptions and Module Family names—directly into the help_text parameter of Django form fields. These fields are rendered using Django's |safe filter, bypassing HTML escaping and enabling persistent injection. When an administrative user accesses the affected forms, the payload executes contextually in their browser. This allows attackers to hijack active sessions and perform unauthorized operations. Nautobot versions prior to v2.4.37 and v3.1.8 are affected by this vulnerability. The issue has been patched by implementing contextual HTML escaping and strict markdown sanitization.
Nautobot serves as an open-source Network Source of Truth and Network Automation Platform, managing highly sensitive configuration data. The application utilizes a generic form rendering component to display form fields dynamically across different modules. A critical validation flaw exists in how user-supplied database properties are parsed during this form generation process.
The vulnerability is classified as a stored Cross-Site Scripting (XSS) vulnerability, registered as CWE-79. Specifically, the application interpolates user-controlled database properties directly into the help text parameter of standard Django form fields. These fields are subsequently rendered without appropriate contextual escaping.
An attacker with low-level administrative privileges can inject arbitrary HTML or JavaScript payloads into specific model properties. When a high-privilege administrative user accesses the affected object forms, the browser executes the stored payload. This execution occurs within the context of the victim's authenticated session, introducing significant security risks.
In standard Django applications, template variables are automatically HTML-escaped by default to prevent cross-site scripting vulnerabilities. However, Nautobot utilizes a specialized template file named render_field.html to standardize the visual presentation of form elements. Within this template, the form field's help_text parameter is processed using Django's built-in |safe template filter.
The application of the |safe filter instructs the template engine to bypass standard HTML auto-escaping mechanisms. This behavior assumes that any content passed to help_text has been validated and sanitized. If the backend dynamic form class assigns raw, unescaped, user-controlled model attributes directly to the form's help_text property, a persistent injection path is established.
The vulnerability manifests in two primary code paths. First, in the Module Family initialization logic within nautobot/dcim/forms.py, the system generates help texts using standard Python f-strings that incorporate the unsanitized parent_bay.module_family.name attribute. Second, in nautobot/extras/models/relationships.py, the dynamic relationship form builder directly maps the unsanitized self.description attribute to the field's help text parameter.
The vulnerability in the Module Family component was addressed by replacing standard Python f-string interpolation with Django's format_html utility, which automatically escapes variable inputs.
# Vulnerable Implementation
self.fields["module_family"].help_text = f"The selected parent module bay requires a module in the {parent_bay.module_family.name} family"
# Patched Implementation
from django.utils.html import format_html
self.fields["module_family"].help_text = format_html(
"The selected parent module bay requires a module in the {} family",
parent_bay.module_family.name,
)For the Relationship description component, the developers implemented markdown-based sanitization using the render_markdown helper. This helper leverages the nh3 HTML sanitization library to strip out executable components like <script> tags while retaining benign formatting elements.
# Vulnerable Implementation
def to_form_field(self, side):
field.required = False
field.label = self.get_label(side)
if self.description:
field.help_text = self.description
return field
# Patched Implementation
from nautobot.core.templatetags.helpers import render_markdown
def to_form_field(self, side):
field.required = False
field.label = self.get_label(side)
if self.description:
# Sanitize input and render markdown securely
field.help_text = render_markdown(self.description)
return fieldExploitation of this vulnerability requires an authenticated attacker with permissions to modify either Module Family names or Relationship descriptions. The attacker injects a target payload containing executable JavaScript into these fields. Because the inputs are stored in the database without prior validation, the database persists the malicious payload in its raw form.
The attack remains dormant until an administrative user triggers the rendering of the compromised form. This occurs when the administrator attempts to create or edit objects associated with the malicious Module Family or Relationship. The system loads the record, extracts the raw payload, and renders it through the render_field.html template.
Upon rendering, the administrator's browser interprets the injected tags and executes the script. The script can extract the active session cookies or API tokens and transmit them to an external endpoint controlled by the attacker. This allows the attacker to hijack the session and assume the administrative identity.
The security impact of CVE-2026-83801 is evaluated with a CVSS v3.1 score of 5.4, indicating Medium severity. The attack vector is Network, requiring Low privileges and User Interaction. The scope is changed because the script executes within the context of the victim's session, enabling actions outside the attacker's original permission boundaries.
Successful execution allows an attacker to bypass role-based access control. Since Nautobot serves as a centralized source of truth for network configurations, administrative access can lead to broader infrastructure compromise. The attacker can modify network device definitions, retrieve secrets, or disrupt automation workflows.
The vulnerability does not present direct availability risks to the server hosting Nautobot. The primary consequence is the compromise of session confidentiality and integrity. If the victim holds superuser privileges, the attacker can establish full, persistent administrative control over the Nautobot deployment.
The recommended remediation is to upgrade the Nautobot deployment to the patched releases. For deployments on the v2.x release train, upgrade to version v2.4.37 or later. For deployments on the v3.x release train, upgrade to version v3.1.8 or later.
If immediate upgrading is not possible, organizations can run administrative scripts within the Nautobot Django shell to inspect and clean the database. This script scans for potentially dangerous characters in target models.
# Audit script for the Django shell
from nautobot.dcim.models import ModuleFamily
from nautobot.extras.models import Relationship
for mf in ModuleFamily.objects.filter(name__contains='<'):
print(f'Vulnerable ModuleFamily: {mf.id}')
for rel in Relationship.objects.filter(description__contains='<'):
print(f'Vulnerable Relationship: {rel.id}')Additionally, organizations should enforce the principle of least privilege. Restricting access to the dcim.add_modulefamily, dcim.change_modulefamily, extras.add_relationship, and extras.change_relationship permissions reduces the exposed attack surface.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Nautobot Nautobot | >= 2.0.0, < 2.4.37 | v2.4.37 |
Nautobot Nautobot | >= 3.0.0, < 3.1.8 | v3.1.8 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Severity | 5.4 (Medium) |
| EPSS Status | Not established |
| Impact | Stored Cross-Site Scripting (XSS) |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
CVE-2026-62361 (GHSA-xgjr-7j9q-2h4r) is an authenticated SQL injection and authorization bypass vulnerability in listmonk, an open-source, self-hosted newsletter and mailing list manager written in Go. In versions prior to 6.2.0, the subscriber export endpoint (GET /api/subscribers/export) directly interpolates user-supplied SQL filter expressions into a PostgreSQL query without invoking the table allowlist validation function (validateQueryTables). Consequently, authenticated users possessing the subscribers:sql_query and subscribers:get_all permissions can bypass relational access controls, execute arbitrary subqueries against restricted tables such as users and settings, and modify database state via writable Common Table Expressions (CTEs).
CVE-2026-28385 (GHSA-3gq2-x4qg-p4g6) is a Server-Side Request Forgery (SSRF) vulnerability in Canonical LXD versions 4.12 through 6.9. An authenticated user possessing the can_create_images entitlement can force the LXD daemon to execute arbitrary outbound HTTP and HTTPS requests via the /1.0/images API endpoint. Due to the absence of egress destination validation, an attacker can probe host loopback services, RFC 1918 private network segments, and cloud Instance Metadata Services (IMDS) from the network position of the privileged LXD daemon.
Canonical LXD and LXC Incus contain an arbitrary file write vulnerability (CWE-73 / CWE-59) in the `/1.0/instances/<name>/exec` REST API endpoint. When an instance is unpacked from an image archive, top-level symbolic links are extracted directly onto the host filesystem within the instance storage directory. If a crafted image includes a symbolic link named `exec-output` pointing to an arbitrary host path, invoking the execution endpoint with `record-output: true` causes the privileged host daemon to follow the symlink and write command output files directly to the target host directory. An authenticated attacker with permissions to import images and execute commands can exploit this flaw to write controlled content to sensitive host directories such as `/etc/cron.d`, resulting in arbitrary code execution on the host as `root`.
CVE-2026-48749 (tracked as GHSA-vghh-5rfx-xhq8 for LXD and GHSA-2q3f-q5pq-g8wv for Incus) is a critical container escape vulnerability in the Canonical LXD and Linux Containers Incus system container managers. By importing a specially crafted container image archive containing duplicate tar member headers, an authenticated attacker can replace the instance rootfs directory with a symbolic link pointing to the underlying host root filesystem. When performing file operations on a stopped container, the privileged forkfile helper binary resolves the symbolic link without confinement and chroots directly into the host filesystem, granting arbitrary root-level file read and write access.
GHSA-fmc8-p6q7-75cc (associated with CVE-2026-48755) is a critical argument injection vulnerability in Canonical LXD (github.com/canonical/lxd). The vulnerability occurs during instance and storage volume backup operations when processing the user-supplied compression_algorithm configuration parameter. While LXD validates the primary executable token against an allowlist of supported compression binaries, it fails to sanitize or restrict subsequent tokens parsed via shellquote.Split. These additional arguments are passed directly to exec.Command. An authenticated attacker with privileges to trigger backups can inject command-line flags for utilities like zstd to read staged files from a container filesystem and write them to arbitrary paths on the underlying host filesystem, such as /etc/cron.d/, achieving root-level arbitrary code execution on the host.
A critical path traversal vulnerability caused by improper input validation (CWE-20) exists in the direct image download handler of Canonical LXD and Linux Containers Incus. When an authenticated user requests an image import using the URL source type, the daemon reads the image fingerprint from an untrusted HTTP response header (`Incus-Image-Hash` or `LXD-Image-Hash`) without validating that it conforms to a SHA-256 format. This unvalidated string is concatenated directly into a filesystem path via `filepath.Join` prior to file creation and cryptographic verification, allowing directory traversal sequences to write arbitrary files on the host filesystem as root.