CVE-2026-55501
Summary
The 9router dashboard login rate limiter derives the client identity from the attacker-controlled X-Forwarded-For HTTP header. When 9router is directly exposed, or deployed behind a reverse proxy that does not overwrite untrusted forwarding headers, a remote attacker can rotate the X-Forwarded-For value on each login attempt and receive a fresh rate-limit bucket every time.
This bypasses the dashboard brute-force protection and makes the login lockout mechanism ineffective.
Details
| Component | File | Note |
| ---------------------------- | --------------------------------- | --------------------------------------------------------------------------- |
| Dashboard login rate limiter | src/lib/auth/loginLimiter.js | Uses X-Forwarded-For as the client identity without a trusted-proxy check |
| Dashboard login route | src/app/api/auth/login/route.js | Calls checkLock() and recordFail() using the spoofable client identity |
Vulnerable Code
src/lib/auth/loginLimiter.js:
export function getClientIp(request) {
const xff = request.headers.get("x-forwarded-for");
if (xff) return xff.split(",")[0].trim();
return request.headers.get("x-real-ip") || "unknown";
}
The returned value is used as the key for the in-memory rate-limit state:
const attempts = new Map(); // ip -> { fails, lockUntil, lockLevel, lastFailAt }
The login route uses this value when checking and recording failed login attempts:
export async function POST(request) {
const ip = getClientIp(request);
const lock = checkLock(ip);
if (lock.locked) {
return NextResponse.json(
{ error: Too many failed attempts. Try again in ${lock.retryAfter}s. },
{ status: 429 }
);
}
// ... password validation ...
recordFail(ip);
}
Because X-Forwarded-For is accepted directly from the request, each unique header value creates a new rate-limit bucket with zero previous failures. An attacker can therefore bypass both the 5-attempt threshold and the progressive lockout durations.
PoC
Step 1 — Baseline: rate lim
⚡ Watch CVE-2026-55501
Get an email if CVE-2026-55501 is added to CISA KEV, gains public exploit code, or a new advisory cites it — max one per day, one-click unsubscribe.
Advisory coverage (2)
External references
Embed the live status
— this badge updates automatically when the KEV or exploit status changes. How to embed it →
[](https://www.csirts.com/cve/CVE-2026-55501)