fix(security): sanitize filename in Content-Disposition response header #85

Open
opened 2026-03-27 09:24:19 +01:00 by marcel · 0 comments
Owner

Security Issue — MEDIUM

Found in: backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java (line ~73)

The vulnerable pattern

.header(HttpHeaders.CONTENT_DISPOSITION,
    "inline; filename=\"" + doc.getOriginalFilename() + "\"")

The original filename comes from the client at upload time and is stored in the database verbatim. When it is later embedded into the Content-Disposition header without sanitization, a filename containing a double-quote character (") breaks the header syntax. A filename with a newline (\n) can inject an entirely new HTTP response header.

Example:

  • Filename stored: photo".jpg → header becomes inline; filename="photo".jpg" — malformed
  • Filename stored: photo\r\nX-Injected: evil → splits the HTTP header → header injection

The fix

Strip or encode characters that are unsafe in HTTP header values before embedding them. The simplest safe approach: remove everything except word characters, spaces, dots, and hyphens, then trim to a reasonable length.

// DocumentController.java
private static String safeFilename(String raw) {
    if (raw == null || raw.isBlank()) return "document";
    return raw.replaceAll("[\"\\\\\\r\\n]", "_")  // strip quotes, backslashes, newlines
              .strip()
              .substring(0, Math.min(raw.length(), 200));
}

// In the download endpoint:
.header(HttpHeaders.CONTENT_DISPOSITION,
    "inline; filename=\"" + safeFilename(doc.getOriginalFilename()) + "\"")

For full RFC 5987 compliance with non-ASCII filenames (German umlauts are common in this archive):

String encoded = URLEncoder.encode(doc.getOriginalFilename(), StandardCharsets.UTF_8)
        .replace("+", "%20");
.header(HttpHeaders.CONTENT_DISPOSITION,
    "inline; filename*=UTF-8''" + encoded)

Why

HTTP headers are line-based text. Injecting a newline into a header value lets an attacker append arbitrary headers to the response — including Set-Cookie, Content-Type, or cache-poisoning headers. This is called HTTP header injection (a subset of CRLF injection).

Priority

MEDIUM — requires a malicious filename to be stored first (needs WRITE_ALL permission), but the impact is header injection on any user who downloads the file.

## Security Issue — MEDIUM **Found in:** `backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java` (line ~73) ### The vulnerable pattern ```java .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + doc.getOriginalFilename() + "\"") ``` The original filename comes from the client at upload time and is stored in the database verbatim. When it is later embedded into the `Content-Disposition` header without sanitization, a filename containing a double-quote character (`"`) breaks the header syntax. A filename with a newline (`\n`) can inject an entirely new HTTP response header. **Example:** - Filename stored: `photo".jpg` → header becomes `inline; filename="photo".jpg"` — malformed - Filename stored: `photo\r\nX-Injected: evil` → splits the HTTP header → header injection ### The fix Strip or encode characters that are unsafe in HTTP header values before embedding them. The simplest safe approach: remove everything except word characters, spaces, dots, and hyphens, then trim to a reasonable length. ```java // DocumentController.java private static String safeFilename(String raw) { if (raw == null || raw.isBlank()) return "document"; return raw.replaceAll("[\"\\\\\\r\\n]", "_") // strip quotes, backslashes, newlines .strip() .substring(0, Math.min(raw.length(), 200)); } // In the download endpoint: .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + safeFilename(doc.getOriginalFilename()) + "\"") ``` For full RFC 5987 compliance with non-ASCII filenames (German umlauts are common in this archive): ```java String encoded = URLEncoder.encode(doc.getOriginalFilename(), StandardCharsets.UTF_8) .replace("+", "%20"); .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename*=UTF-8''" + encoded) ``` ### Why HTTP headers are line-based text. Injecting a newline into a header value lets an attacker append arbitrary headers to the response — including `Set-Cookie`, `Content-Type`, or cache-poisoning headers. This is called HTTP header injection (a subset of CRLF injection). ### Priority **MEDIUM — requires a malicious filename to be stored first (needs WRITE_ALL permission), but the impact is header injection on any user who downloads the file.**
marcel added the security label 2026-03-27 12:29:50 +01:00
Sign in to join this conversation.
No Label security
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#85