Mar 20, 2026·6 min read·3 visits
A misconfiguration in Vikunja's Labstack Echo framework implementation allows attackers to bypass rate limits by injecting spoofed IP headers, facilitating high-speed credential stuffing and brute-force attacks.
Vikunja versions prior to 2.2.0 contain a rate-limit bypass vulnerability due to improper validation of client IP addresses. Unauthenticated remote attackers can bypass IP-based rate limiting by spoofing HTTP headers such as X-Forwarded-For, enabling unlimited brute-force attacks against authentication endpoints.
Vikunja relies on the Labstack Echo web framework for routing and middleware execution. The application implements rate-limiting middleware to restrict the frequency of requests to critical endpoints, such as the /api/v1/login route. This mechanism identifies unique clients by extracting an IP address from incoming HTTP requests and using it as the key for a token bucket rate-limiting algorithm.
Vulnerable versions of Vikunja prioritize user-supplied HTTP headers over transport-level connection data when determining this client IP address. The application extracts IP addresses from headers like X-Forwarded-For without verifying whether the underlying network connection originates from a trusted reverse proxy. This behavior constitutes CWE-807 (Reliance on Untrusted Inputs in a Security Decision).
Because the rate limit token bucket is keyed by this untrusted input, an attacker can trivially manipulate the header value to generate an infinite number of unique keys. This completely nullifies the rate-limiting protections, exposing the application to uninhibited automated attacks against authentication interfaces.
The vulnerability originates in the default configuration of the Echo web framework used within the Vikunja backend. The application utilized the (echo.Context).RealIP() method to determine the client's identity for the rate-limiting middleware. This method evaluates HTTP headers such as X-Forwarded-For and X-Real-IP before examining the actual remote TCP address of the network socket.
By default, Echo's RealIP() implementation trusts the first valid IP address found in these headers. The framework does not independently maintain state regarding trusted proxy infrastructure. If an external attacker connects directly to the application—or passes through a proxy that merely appends to an existing spoofed header—the application processes the attacker-supplied IP address as the authoritative client identity.
The rate-limiting middleware in pkg/routes/rate_limit.go uses the returned string as the primary key for its state bucket. When an attacker modifies the X-Forwarded-For header, RealIP() returns the modified value. The middleware checks its storage backend for a token bucket associated with this new value, finds none, and creates a fresh bucket with a full quota of requests.
In vulnerable versions, the rate-limiting logic directly invoked the unverified RealIP() function to set the key. The implementation assumed that the environment would sanitize incoming headers, which is often not true in default deployments or direct-exposure scenarios.
// Vulnerable Implementation in pkg/routes/rate_limit.go
switch limitType {
case "ip":
// The key is derived directly from the untrusted RealIP() function
rateLimitKey = c.RealIP()
// ...
}The patch introduced in commit a498dd69915a006c07e9d82660a2185d7e8136ee implements a zero-trust model for IP extraction. It modifies pkg/routes/routes.go to explicitly configure Echo's IPExtractor. The fix introduces two new configuration settings: service.ipextractionmethod and service.trustedproxies.
// Patched Implementation in pkg/routes/routes.go
switch config.ServiceIPExtractionMethod.GetString() {
case "xff":
trustOptions := parseTrustedProxies(config.ServiceTrustedProxies.GetString())
e.IPExtractor = echo.ExtractIPFromXFFHeader(trustOptions...)
case "realip":
trustOptions := parseTrustedProxies(config.ServiceTrustedProxies.GetString())
e.IPExtractor = echo.ExtractIPFromRealIPHeader(trustOptions...)
default:
// Secure fallback: directly uses the remote TCP socket address
e.IPExtractor = echo.ExtractIPDirect()
}If no trusted proxies are configured, the application falls back to ExtractIPDirect(). This function relies exclusively on the socket's remote TCP address, a value determined by the operating system's network stack which cannot be spoofed via HTTP application-layer headers.
Attackers exploit this vulnerability by manipulating HTTP request headers during interaction with rate-limited endpoints. The attack begins with standard requests targeting an endpoint like /api/v1/login. The server responds with rate-limit tracking headers, including X-Ratelimit-Remaining, which decrements with each subsequent request.
Once the rate limit approaches exhaustion, the attacker modifies the X-Forwarded-For header in their request. The payload requires only a syntactically valid IPv4 or IPv6 address. The attacker automates this process, iterating through an arbitrary IP space to continuously provision fresh rate-limit buckets on the backend.
POST /api/v1/login HTTP/1.1
Host: vikunja.example.com
X-Forwarded-For: 198.51.100.1
Content-Type: application/json
{"username":"admin","password":"password123"}The server processes the request and responds with a reset X-Ratelimit-Remaining counter. The attacker can sustain a high-volume credential stuffing campaign by incrementing the rightmost octet of the spoofed IP address on every request, successfully bypassing all application-layer throttling.
The primary consequence of this vulnerability is the complete nullification of application-layer brute-force protections. Attackers can execute credential stuffing campaigns and password guessing attacks against the /api/v1/login endpoint without arbitrary time restrictions. This significantly increases the probability of account compromise for users with weak or reused passwords.
The vulnerability is classified with a CVSS v3.1 score of 5.3 (Medium). The score reflects the network attack vector, low complexity, and lack of required privileges. The impact is primarily categorized under Availability and Confidentiality, though the vulnerability itself directly impacts Availability (of the rate-limiting mechanism).
Secondary impacts include potential resource exhaustion. By forcing the application to continuously allocate new rate-limit tracking buckets in memory or the database cache, an attacker can induce elevated memory consumption. While not a direct Denial of Service, this behavior degrades overall application performance during active exploitation.
The definitive mitigation requires upgrading Vikunja to version 2.2.0 or later. This release incorporates the configuration parameters necessary to establish a strict IP extraction policy. Upgrading resolves the insecure default behavior by requiring explicit trust declarations for reverse proxies.
Administrators must explicitly configure the service.trustedproxies setting if the application operates behind a reverse proxy. This setting accepts a comma-separated list of CIDR blocks corresponding to the internal load balancers or proxies. If this setting is omitted while relying on proxy headers, the application will default to the secure, but potentially functionally incorrect, TCP remote address.
For environments where immediate patching is not feasible, mitigation can be applied at the edge network layer. Administrators must configure the upstream reverse proxy (e.g., Nginx, HAProxy) to strip all incoming X-Forwarded-For and X-Real-IP headers originating from external clients. The proxy must be configured to exclusively set these headers based on the verified client connection data before forwarding requests to the Vikunja backend.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
vikunja go-vikunja | >= 0.8, < 2.2.0 | 2.2.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-807 |
| Attack Vector | Network |
| CVSS v3.1 | 5.3 |
| Impact | Rate Limit Bypass, Credential Stuffing |
| Exploit Status | Proof of Concept |
| KEV Status | Not Listed |
Reliance on Untrusted Inputs in a Security Decision