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