home intel cve-2026-6988-tenda-hg10-nexthop-buffer-overflow
CVE Analysis 2026-04-25 · 8 min read

CVE-2026-6988: Tenda HG10 formRoute nextHop Stack Overflow via Boa

A stack buffer overflow in Tenda HG10's formRoute handler allows unauthenticated remote attackers to corrupt the stack via an oversized nextHop argument. CVSS 8.8.

#buffer-overflow#remote-code-execution#tenda-router#boa-service#network-device
Technical mode — for security professionals
▶ Attack flow — CVE-2026-6988 · Buffer Overflow
ATTACKERRemote / unauthBUFFER OVERFLOWCVE-2026-6988Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-6988 is a remotely exploitable stack buffer overflow in the Tenda HG10 home gateway (firmware HG7_HG9_HG10re_300001138_en_xpon). The vulnerability lives in formRoute, the CGI handler registered under /boaform/formRouting within the embedded Boa HTTP server. An unauthenticated attacker on the LAN — or, depending on WAN management exposure, from the internet — can submit a crafted POST request with an oversized nextHop parameter and overflow a fixed-size stack buffer, gaining control of the saved return address.

CVSS 8.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) reflects the trivial trigger path: one HTTP POST, no authentication prerequisite documented in the advisory, no interaction required.

Affected Component

The Boa web server embedded in Tenda HG10 firmware processes form submissions by dispatching to registered handler functions. The routing form at /boaform/formRouting is handled by formRoute. This function reads POST parameters — including nextHop, an IPv4 next-hop address string — using websGetVar (Tenda's Boa-derived parameter extraction wrapper) and copies them into stack-allocated character buffers without length validation.

Relevant binary paths in the firmware image:


Firmware : HG7_HG9_HG10re_300001138_en_xpon
Web root : /usr/web/
CGI bin  : /usr/sbin/boa  (statically linked, MIPS32 big-endian)
Handler  : formRoute  @ symbol table / string x-ref "/boaform/formRouting"
Boa ver  : 0.94.14rc21 (Tenda fork, no stack canaries, no ASLR)

Root Cause Analysis

The handler allocates a fixed 32-byte stack buffer for the next-hop IPv4 address. A valid IPv4 address fits comfortably in 16 bytes. The bug is that websGetVar returns a pointer directly into the raw POST body, and the subsequent strcpy — or in some Tenda variants sprintf — performs no length check before writing into the stack slot.


// Decompiled pseudocode — formRoute() in /usr/sbin/boa
// MIPS32 BE, reconstructed from binary analysis of HG7_HG9_HG10re_300001138_en_xpon

int formRoute(webs_t wp, char *path, char *query)
{
    char  destIp[32];      // stack: sp+0x18
    char  netmask[32];     // stack: sp+0x38
    char  nextHop[32];     // stack: sp+0x58  <-- target buffer
    char  metric[16];      // stack: sp+0x78
    char  ifname[32];      // stack: sp+0x88
    char  cmd[256];        // stack: sp+0xa8  <-- overwritten after nextHop
    char *pNextHop;
    int   ret;

    pNextHop = websGetVar(wp, "nextHop", "");   // returns ptr into POST body
    // BUG: no strlen() check — attacker controls len(pNextHop) up to POST body limit
    strcpy(nextHop, pNextHop);                  // BUG: unbounded copy into 32-byte stack slot

    // ... remaining parameters parsed similarly ...

    snprintf(cmd, sizeof(cmd),
             "route add -net %s netmask %s gw %s metric %s dev %s",
             destIp, netmask, nextHop, metric, ifname);
    system(cmd);

    websRedirect(wp, "routing.asp");
    return 0;
}

The Boa server on this device is compiled without -fstack-protector and the firmware loads at a fixed virtual address with no ASLR, making straight saved-ra hijacking viable after a single overflow.

Root cause: formRoute copies the attacker-controlled HTTP POST parameter nextHop via strcpy into a 32-byte stack buffer with no length validation, enabling a full saved-return-address overwrite on MIPS32.

Memory Layout

Stack frame layout for formRoute on MIPS32 (big-endian, no canary):


// Stack frame — formRoute(), grows downward
// Frame size: ~0x1b0 bytes (reconstructed)

struct formRoute_frame {
    /* sp+0x00 */  uint8_t  _padding[0x18];
    /* sp+0x18 */  char     destIp[32];     // 0x20 bytes
    /* sp+0x38 */  char     netmask[32];    // 0x20 bytes
    /* sp+0x58 */  char     nextHop[32];    // 0x20 bytes  <-- overflow starts here
    /* sp+0x78 */  char     metric[16];     // 0x10 bytes
    /* sp+0x88 */  char     ifname[32];     // 0x20 bytes
    /* sp+0xa8 */  char     cmd[256];       // 0x100 bytes
    /* sp+0x1a8 */ uint32_t saved_s0;
    /* sp+0x1ac */ uint32_t saved_s1;
    /* sp+0x1b0 */ uint32_t saved_ra;       // <-- saved return address, overwrite target
};

STACK STATE BEFORE OVERFLOW (nextHop = "192.168.1.1\x00"):
  sp+0x58  [ nextHop[0..31]  ] = "192.168.1.1\x00.................."
  sp+0x78  [ metric[0..15]   ] = "1\x00.............."
  sp+0x88  [ ifname[0..31]   ] = "eth0\x00........................"
  sp+0xa8  [ cmd[0..255]     ] = (uninitialized)
  sp+0x1b0 [ saved_ra        ] = 0x806f23c4  (legitimate return into websFormHandlerDispatch)

STACK STATE AFTER OVERFLOW (nextHop = "A"*0x158 + "\xde\xad\xbe\xef"):
  sp+0x58  [ nextHop[0..31]  ] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
  sp+0x78  [ metric — SMASHED] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ...
  sp+0x88  [ ifname — SMASHED] = ...
  sp+0xa8  [ cmd    — SMASHED] = ...
  sp+0x1a8 [ saved_s0 SMASHED] = 0x41414141
  sp+0x1ac [ saved_s1 SMASHED] = 0x41414141
  sp+0x1b0 [ saved_ra SMASHED] = 0xdeadbeef  <-- attacker-controlled PC

Offset from the start of nextHop to saved_ra: 0x1b0 - 0x58 = 0x158 (344 bytes). A POST body well within Boa's default MAX_CONTENT_LENGTH of 65535 bytes is sufficient.

Exploitation Mechanics

Because the device runs MIPS32 big-endian without ASLR and without stack canaries, exploitation is deterministic. ROP is not strictly required — the attacker can jump directly to a command-injection gadget already present in cmd or use a system() trampoline.


EXPLOIT CHAIN:
1. Enumerate target: confirm /boaform/formRouting responds to POST on port 80
   (default config; some ISP deployments expose management on port 8080 WAN-side)

2. Craft POST body:
     destIp=1.1.1.1&netmask=255.255.255.0
     &nextHop=[32 bytes legitimate] + [0x138 bytes padding] + [4-byte ra overwrite]
     &metric=1&ifName=eth0

3. Compute saved_ra target:
     - No ASLR: boa loads at fixed base each boot
     - Locate 'jalr $t9' / 'move $a0,$sp' gadget sequence in boa .text
       that pivots to stack payload embedded earlier in cmd[] buffer
     - Alternatively: overwrite ra with address of system() PLT stub,
       with $a0 pointing to cmd[] which contains attacker cmd string
       placed via the cmd snprintf that runs BEFORE the function returns

4. The snprintf(cmd,...) call executes before the return:
     cmd[] is filled with: "route add -net 1.1.1.1 netmask 255.255.255.0
                            gw [attacker padding — benign looking] ..."
     Instead, craft nextHop so that after strcpy, the subsequent snprintf
     writes a valid shell command into cmd[] (e.g., inject via destIp
     with semicolons), ensuring system(cmd) fires the payload
     BEFORE the corrupted ra is even reached — dual impact path.

5. system(cmd) executes attacker command (e.g., wget backdoor, busybox telnetd)

6. Corrupted saved_ra causes clean jump to controlled address on return,
   providing a secondary shell or crash-based DoS if (5) fails.

7. Persistent access: write dropbear keys to /etc/dropbear/ via shell,
   or add cron entry via /var/spool/cron/

Step 4 is notable: the system(cmd) call is reachable even if the attacker only partially overflows — meaning partial-overwrite DoS is possible with payloads as short as 33 bytes, while full RCE requires the 344-byte overwrite to land saved_ra.

Patch Analysis

The correct fix bounds-checks the parameter before copying, or replaces strcpy with strncpy/snprintf. The minimal viable patch:


// BEFORE (vulnerable — HG7_HG9_HG10re_300001138_en_xpon):
pNextHop = websGetVar(wp, "nextHop", "");
strcpy(nextHop, pNextHop);   // no length check, 32-byte destination

// AFTER (patched — recommended form):
pNextHop = websGetVar(wp, "nextHop", "");
if (strlen(pNextHop) >= sizeof(nextHop)) {
    websError(wp, 400, "Invalid nextHop parameter");
    return -1;
}
strncpy(nextHop, pNextHop, sizeof(nextHop) - 1);
nextHop[sizeof(nextHop) - 1] = '\0';

// STRONGER: validate that nextHop is actually a dotted-quad IPv4 address
// before any copy occurs:
if (!is_valid_ipv4(pNextHop)) {   // regex or inet_aton() check
    websError(wp, 400, "Invalid nextHop parameter");
    return -1;
}
strncpy(nextHop, pNextHop, sizeof(nextHop) - 1);
nextHop[sizeof(nextHop) - 1] = '\0';

// Suggested is_valid_ipv4 helper (absent from vulnerable firmware):
static int is_valid_ipv4(const char *s) {
    struct in_addr addr;
    return inet_aton(s, &addr) != 0;  // rejects anything > 15 chars + non-numeric
}

The same class of unbounded strcpy from websGetVar output is likely present in sibling handlers (formDhcp, formWan, formDns) — all should be audited for identical patterns. Tenda has a historical pattern of copy-pasting this handler skeleton across product lines.

Detection and Indicators

Network-level detection — look for POST requests to /boaform/formRouting where the nextHop field exceeds 15 characters (maximum valid IPv4 length):


Snort / Suricata rule (draft):
alert http $EXTERNAL_NET any -> $HOME_NET [80,8080] (
    msg:"CVE-2026-6988 Tenda HG10 formRoute nextHop overflow attempt";
    flow:to_server,established;
    http.method; content:"POST";
    http.uri; content:"/boaform/formRouting";
    http.request_body; content:"nextHop=";
    pcre:"/nextHop=[^\x26]{33,}/";
    classtype:attempted-admin;
    sid:20266988; rev:1;
)

Crash indicators on device:
  - Boa process exits unexpectedly (watchdog respawn visible in /var/log/messages)
  - Last syslog entry: "boa: segfault" or kernel "do_page_fault" trap
  - HTTP service becomes unavailable for 2–5 seconds (watchdog restart window)
  - Unexpected telnetd/dropbear process spawned post-exploit

Remediation

  • Firmware update: Apply any Tenda-released firmware revision that addresses CVE-2026-6988. At time of writing no patched firmware was listed in the NVD entry; contact Tenda support directly.
  • Disable remote management: Confirm that port 80/8080 is not exposed on the WAN interface. Navigate to Advanced → Remote Management and ensure it is disabled.
  • LAN-side mitigation: Even LAN-only exposure is dangerous in shared-network environments. Apply firewall ACLs restricting access to the management interface to a single trusted host.
  • Network monitoring: Deploy the Snort rule above at your perimeter or inline IPS. Alert on any POST to /boaform/formRouting with nextHop values longer than 15 bytes.
  • Device replacement: If Tenda does not release a patch, consider replacing with a device that receives active security maintenance. The HG10 firmware codebase shows systemic absence of stack hardening (-fstack-protector, ASLR, PIE) making it a poor security baseline regardless of this specific CVE.
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 →