CVE-2026-7036: Path Traversal in Tenda i9 R7WebsSecurityHandler
Tenda i9 1.0.0.5(2204) exposes an unauthenticated path traversal via R7WebsSecurityHandler in the HTTP stack. Remote attackers can read arbitrary files from the device filesystem.
Tenda makes Wi-Fi routers — the devices sitting in your home that connect you to the internet. Security researchers have discovered a serious flaw in one model that could let hackers snoop on your files without permission.
Here's what's happening: Think of your router like a filing cabinet with a locked front door. The vulnerability is like someone discovering they can walk around the lock by taking a hidden path through the back of the cabinet. An attacker can trick the router into showing them files it was never supposed to reveal — things like passwords, network settings, or personal data.
The vulnerability affects Tenda i9 routers running software version 1.0.0.5(2204). It's in the security handler, which is supposed to be the bouncer checking what files you're allowed to access. But the bouncer has a blind spot.
What makes this particularly worrying is that hackers don't need to be sitting next to your router. They can attack remotely over the internet. Someone could target your router from anywhere in the world, and exploit code to do this is already publicly available — meaning basically anyone with basic technical skills could try it.
Who's at risk? Anyone using this specific Tenda router model and software version. Small businesses and home networks with this equipment are particularly vulnerable because they often run outdated software.
Here's what to do: First, check what router model you have and its software version (usually found in your router's settings or on the device itself). Second, visit Tenda's website immediately to download the latest firmware update and install it. Third, if you can't update, consider temporarily disabling remote management features in your router settings as a stopgap measure. Act now — this vulnerability is serious enough that attackers are already trying to use it.
Want the full technical analysis? Click "Technical" above.
CVE-2026-7036 is a path traversal vulnerability in the HTTP request handler of Tenda i9 firmware version 1.0.0.5(2204). The vulnerable function, R7WebsSecurityHandler, fails to canonicalize or sanitize the request URI before resolving it against the web root. An unauthenticated remote attacker can supply a crafted HTTP request containing ../ sequences to escape the intended document root and read arbitrary files from the underlying Linux filesystem — including /etc/passwd, /etc/shadow, private keys, and NVRAM configuration dumps.
CVSS 7.3 (HIGH) reflects network-accessible, no-authentication-required exploitation with high confidentiality impact. No in-the-wild exploitation has been confirmed at time of writing, but a public proof-of-concept exists.
Affected Component
The vulnerability lives inside the embedded HTTP server shipped with the Tenda i9 AP firmware. The binary responsible is typically httpd or an equivalent single-binary web daemon. The entry point for incoming HTTP requests is R7WebsSecurityHandler, which acts as a URI-level security gate — its intended role is to decide whether a requested path is permitted before passing control to the file-serving logic. Because this function is also responsible for path normalization, a failure here bypasses all downstream access controls.
Firmware: Tenda i9 1.0.0.5(2204)
Binary:/usr/sbin/httpd (MIPS32 LE, uClibc)
Function:R7WebsSecurityHandler
Transport: HTTP/1.1 over TCP/80 (LAN-accessible by default)
Root Cause Analysis
The function receives the raw request URI from the HTTP parser and checks it against an allowlist of permitted prefixes. However, it never calls a path canonicalization routine before performing those prefix checks. The sequence /../ is passed directly to the file-open path, allowing directory escape.
/*
* R7WebsSecurityHandler — HTTP URI security gate
* Reconstructed pseudocode from Tenda i9 1.0.0.5(2204) httpd binary
*/
typedef struct {
/* +0x00 */ int socket_fd;
/* +0x04 */ char *method; // "GET", "POST", ...
/* +0x08 */ char *uri; // raw, attacker-controlled URI string
/* +0x0C */ char *http_version;
/* +0x10 */ char *host_header;
/* +0x14 */ char *query_string;
/* +0x18 */ int content_length;
/* +0x1C */ char doc_root[64]; // e.g. "/webroot"
/* +0x5C */ char resolved[256]; // final path buffer
} http_request_t;
// Permitted URI prefixes — static allowlist
static const char *g_permitted_prefixes[] = {
"/login.html",
"/js/",
"/css/",
"/images/",
"/goform/",
NULL
};
int R7WebsSecurityHandler(http_request_t *req, int flags)
{
char full_path[256];
int i;
// BUG: no canonicalization of req->uri before prefix check —
// "/../etc/passwd" does not match any prefix, but the check below
// only looks for a literal prefix, not a normalized path prefix.
// An attacker sends: GET /js/../../../../etc/passwd HTTP/1.1
// This matches the "/js/" prefix check and passes the gate.
for (i = 0; g_permitted_prefixes[i] != NULL; i++) {
if (strncmp(req->uri, g_permitted_prefixes[i],
strlen(g_permitted_prefixes[i])) == 0) {
goto allowed; // prefix matched — skip auth check
}
}
// URI didn't match any public prefix — require session cookie
if (!R7WebsCheckSessionCookie(req)) {
R7WebsSendRedirect(req, "/login.html");
return -1;
}
allowed:
// BUG: snprintf builds the filesystem path directly from the
// unsanitized URI without calling realpath() or equivalent.
// "../" sequences are preserved and resolved by the kernel open().
snprintf(full_path, sizeof(full_path), "%s%s",
req->doc_root, req->uri); // "/webroot" + "/js/../../../../etc/passwd"
// BUG: no realpath() / canonical path check here before open()
int fd = open(full_path, O_RDONLY); // kernel resolves "../" — escapes doc_root
if (fd < 0) {
R7WebsSendError(req, 404);
return -1;
}
R7WebsSendFile(req, fd);
close(fd);
return 0;
}
Root cause:R7WebsSecurityHandler checks the raw URI against a permitted-prefix allowlist before canonicalizing the path, allowing an attacker to embed ../ sequences after a valid prefix to escape the web root while still passing the security gate.
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker identifies Tenda i9 HTTP service on TCP/80 (default, no auth required).
2. Craft URI that starts with a whitelisted prefix ("/js/") followed by
enough "../" traversal sequences to reach the filesystem root.
Depth of /webroot is typically 1 level, so 2x "../" is sufficient,
but 6+ are used for reliability across firmware variants.
3. Send raw HTTP request:
GET /js/../../../../../../etc/shadow HTTP/1.1\r\n
Host: 192.168.0.1\r\n
Connection: close\r\n\r\n
4. R7WebsSecurityHandler evaluates strncmp(uri, "/js/", 4) == 0 → TRUE
Security gate bypassed. No session cookie required.
5. snprintf constructs: "/webroot/js/../../../../../../etc/shadow"
Kernel open() resolves this to: "/etc/shadow"
6. R7WebsSendFile() streams file contents back in HTTP 200 response body.
7. Attacker receives /etc/shadow (or any readable file: /etc/passwd,
/proc/net/arp, NVRAM dumps at /dev/mtd*, SSL keys, etc.)
A minimal Python proof-of-concept demonstrating the read primitive:
This is a logic/path-canonicalization vulnerability rather than a memory corruption bug, so there is no heap overflow. The relevant stack frame during exploitation is:
STACK FRAME: R7WebsSecurityHandler (MIPS32, grows downward)
[ ra (return address) ] +0x13C
[ s-registers saved ] +0x120 .. +0x138
[ http_request_t *req (arg) ] a0 register
────────────────────────────────────────────────────
[ full_path[256] buffer ] sp+0x00 .. sp+0xFF
────────────────────────────────────────────────────
PATH CONSTRUCTION (snprintf output in full_path):
BEFORE (legitimate request):
full_path = "/webroot/js/app.js\0"
└──────────────────── stays within /webroot ✓
AFTER (traversal payload):
full_path = "/webroot/js/../../../../../../etc/shadow\0"
└── kernel resolves "../" chains ──────────► "/etc/shadow" ✗
doc_root = "/webroot" (8 bytes + NUL)
req->uri = "/js/../../../../../../etc/shadow" (34 bytes)
total = 42 bytes ── well within full_path[256], no overflow.
The bug is logical, not spatial.
Patch Analysis
The correct fix requires canonicalizing the path before the prefix check, and then verifying the canonical path still falls within the document root. Both steps are necessary — canonicalization alone does not prevent escape if the doc-root check is absent.
// BEFORE (vulnerable):
int R7WebsSecurityHandler(http_request_t *req, int flags)
{
char full_path[256];
// Check raw URI against allowlist — "../" sequences not stripped
for (i = 0; g_permitted_prefixes[i] != NULL; i++) {
if (strncmp(req->uri, g_permitted_prefixes[i],
strlen(g_permitted_prefixes[i])) == 0) {
goto allowed;
}
}
// ... auth check ...
allowed:
snprintf(full_path, sizeof(full_path), "%s%s",
req->doc_root, req->uri);
// BUG: no realpath() — kernel resolves ../ sequences
int fd = open(full_path, O_RDONLY);
...
}
// AFTER (patched):
int R7WebsSecurityHandler(http_request_t *req, int flags)
{
char full_path[256];
char canonical[PATH_MAX];
// Step 1: build the candidate path
snprintf(full_path, sizeof(full_path), "%s%s",
req->doc_root, req->uri);
// Step 2: canonicalize — resolves all "../", symlinks, etc.
if (realpath(full_path, canonical) == NULL) {
R7WebsSendError(req, 404);
return -1;
}
// Step 3: enforce doc_root confinement BEFORE prefix allowlist check
size_t root_len = strlen(req->doc_root);
if (strncmp(canonical, req->doc_root, root_len) != 0) {
// Path escapes document root — reject unconditionally
R7WebsSendError(req, 403);
return -1;
}
// Now check the canonical URI suffix against the allowlist
const char *rel_uri = canonical + root_len; // relative to doc_root
for (i = 0; g_permitted_prefixes[i] != NULL; i++) {
if (strncmp(rel_uri, g_permitted_prefixes[i],
strlen(g_permitted_prefixes[i])) == 0) {
goto allowed;
}
}
if (!R7WebsCheckSessionCookie(req)) {
R7WebsSendRedirect(req, "/login.html");
return -1;
}
allowed:
int fd = open(canonical, O_RDONLY); // open canonical path, not raw URI
if (fd < 0) { R7WebsSendError(req, 404); return -1; }
R7WebsSendFile(req, fd);
close(fd);
return 0;
}
Detection and Indicators
Detection is straightforward at the network layer. Any HTTP request to the device containing ../ or its URL-encoded equivalents (%2e%2e%2f, %2e%2e/, ..%2f) in the URI path targeting TCP/80 should be treated as a traversal attempt.
On the device itself, unexpected outbound data from httpd to non-administrative hosts, or access log entries with HTTP 200 responses to traversal URIs, are definitive indicators of exploitation.
Remediation
Firmware update: Apply the latest Tenda i9 firmware from the vendor when a patched release is available. Monitor the Tenda security advisory page.
Network segmentation: Restrict TCP/80 access to the device's management interface to trusted VLAN segments only. This device is an AP — management access should never be reachable from untrusted wireless clients.
Disable HTTP, enforce HTTPS: If the firmware supports it, disable plain HTTP and require HTTPS with valid certificate validation to reduce attack surface.
WAF / IPS rule: Deploy the Snort rule above on any IPS/IDS positioned between clients and the management network.
Audit sensitive files: If the device has been internet-exposed or accessible to untrusted users, treat credentials in /etc/passwd, /etc/shadow, and any stored WPA keys as compromised and rotate immediately.