- "details": "While testing Litestar's RateLimitMiddleware, it was discovered that rate limits can be completely bypassed by manipulating the X-Forwarded-For header. This renders IP-based rate limiting ineffective against determined attackers.\n\n## The Problem\n\nLitestar's RateLimitMiddleware uses `cache_key_from_request()` to generate cache keys for rate limiting. When an X-Forwarded-For header is present, the middleware trusts it unconditionally and uses its value as part of the client identifier.\n\nSince clients can set arbitrary X-Forwarded-For values, each different spoofed IP creates a separate rate limit bucket. An attacker can rotate through different header values to avoid hitting any single bucket's limit.\n\nLooking at the relevant code in `litestar/middleware/rate_limit.py` around [line 127](https://github.com/litestar-org/litestar/blob/26f20ac6c52de2b4bf81161f7560c8bb4af6f382/litestar/middleware/rate_limit.py#L127), there's no validation of proxy headers or configuration for trusted proxies.\n\n## Reproduction Steps\n\nHere's a minimal test case:\n\n```python\nfrom litestar import Litestar, get\nfrom litestar.middleware.rate_limit import RateLimitConfig\nimport uvicorn\n\n@get(\"/api/data\")\ndef get_data() -> dict:\n return {\"message\": \"sensitive data\"}\n\nrate_config = RateLimitConfig(rate_limit=(\"minute\", 2))\n\napp = Litestar(\n route_handlers=[get_data],\n middleware=[rate_config.middleware]\n)\n\nif __name__ == \"__main__\":\n uvicorn.run(app, host=\"0.0.0.0\", port=8000)\n```\n\nTesting the bypass:\n\n```bash\n# Normal requests get rate limited after 2 requests\ncurl http://localhost:8000/api/data # 200 OK\ncurl http://localhost:8000/api/data # 200 OK \ncurl http://localhost:8000/api/data # 429 Too Many Requests\n\n# But spoofing X-Forwarded-For bypasses the limit entirely\ncurl -H \"X-Forwarded-For: 192.168.1.100\" http://localhost:8000/api/data # 200 OK\ncurl -H \"X-Forwarded-For: 192.168.1.101\" http://localhost:8000/api/data # 200 OK\ncurl -H \"X-Forwarded-For: 192.168.1.102\" http://localhost:8000/api/data # 200 OK\n```\n\n## Security Impact\n\nThis vulnerability has several concerning implications:\n\nBrute Force Protection Bypass: Authentication endpoints protected by rate limiting become vulnerable to credential stuffing attacks. An attacker can attempt thousands of login combinations from a single source.\n\nAPI Abuse: Public APIs relying on rate limiting for abuse prevention can be scraped or hammered without restriction.\n\nResource Exhaustion: While not a traditional DoS, the ability to bypass rate limits means attackers can consume more server resources than intended.\n\nThe issue is particularly problematic because many developers deploy Litestar applications directly (not behind a proxy) during development or in containerized environments, making this attack vector accessible.\n\n## Potential Solutions\n\nAfter reviewing how other frameworks handle this:\n\n- Default to socket IP only: Don't trust proxy headers unless explicitly configured\n- Trusted proxy configuration: Add settings to specify which proxy IPs are allowed to set forwarded headers\n- Header validation: Implement basic validation of forwarded IP formats\n\nDjango handles this through `SECURE_PROXY_SSL_HEADER` and trusted proxy lists. Express.js has similar trusted proxy configurations.\n\nFor immediate mitigation, applications can deploy behind a properly configured reverse proxy that strips/overwrites client-controllable headers before they reach Litestar.\n\n## Environment Details\n\n- Litestar version: 2.17.0\n- Python: 3.11\n\nThis affects any Litestar application using RateLimitMiddleware with default settings, which likely includes most applications that implement rate limiting.",
0 commit comments