CVE-2026-55501: 9router Login Rate-Limit Bypass via X-Forwarded-For Spoofing
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.
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.
# 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
doneThe 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.
Related research
- critical · 9.9CVE-2026-55500CVE-2026-55500: 9router Unprotected Database Export/Import Credential Theft
- critical · 109router: Unauthenticated CRUD on Provider API and Full API Key Leak
- highCVE-2026-53823: OpenClaw Slack allowFrom Authentication Bypass via Mutable Display Names
- highCVE-2026-53832: openclaw Trusted-Proxy Identity Header Forgery via Same-Host Loopback