high · 7.3CVE-2026-55501Jul 6, 2026

CVE-2026-55501: 9router Login Rate-Limit Bypass via X-Forwarded-For Spoofing

Shubham Kandhare
Security Engagement Manager, SecureLayer7

The 9router dashboard login page lets attackers bypass its brute-force lockout entirely by rotating a spoofed X-Forwarded-For header on each request, giving every attempt a fresh rate-limit counter.

Package9router
Ecosystemnpm
Affected<= 0.4.71
Fixed in0.4.77

The problem

The login rate limiter in src/lib/auth/loginLimiter.js identifies clients by reading X-Forwarded-For directly from the incoming request headers. No trusted-proxy validation is performed, so the value is fully attacker-controlled.

Because the in-memory Map keys on this header value, each unique header creates an independent bucket with zero recorded failures. The 5-attempt threshold and the progressive lockout (30 s, 5 min, 30 min) are never reached. On default installations the rate-limit bypass combines with the default dashboard password to allow unauthenticated administrative access.

Proof of concept

A working proof-of-concept for CVE-2026-55501 in 9router, with the exact payload below.

bash
# Rotate X-Forwarded-For on every request; lockout never triggers
for i in $(seq 1 100); do
  curl -s -X POST "http://TARGET:20128/api/auth/login" \
    -H "Content-Type: application/json" \
    -H "X-Forwarded-For: 10.0.0.$i" \
    -d '{"password":"<password-to-brute-force"}'
  echo
done

The root cause is CWE-290 (Authentication Bypass by Spoofing): getClientIp() returns xff.split(',')[0].trim() without checking whether the request arrived from a trusted upstream proxy. Any client can set this header to any value.

The fix in 0.4.77 gates the X-Forwarded-For read behind a trusted-proxy list. When the TCP peer is not in that list, getClientIp() falls back to the socket's remote address instead, which the attacker cannot control. This ensures each attacker IP maps to exactly one rate-limit bucket regardless of what headers they send.

The fix

Upgrade to 9router 0.4.77 or later. If you cannot upgrade immediately, place 9router behind a reverse proxy configured to overwrite or strip X-Forwarded-For before requests reach the application. Also change the default dashboard password if it has not already been rotated.

Reported by decolua.

References: [1][2]

Related research