[Security/High] Bild-Upload ohne Groessen- und Typpruefung, Dateien oeffentlich abrufbar #37

Open
opened 2026-09-09 20:16:57 +00:00 by Aceless · 1 comment
Owner

Problem

Controller/RecipeImagesController.cs akzeptiert jede Datei, sobald der vom Client gesendete Content-Type mit image/ beginnt:

if (!file.ContentType.StartsWith("image/"))
    return BadRequest("File is not an image.");
  • Der Content-Type kommt vom Client und sagt nichts ueber den Inhalt aus.
  • Keine Endungs-Whitelist, keine Groessenbegrenzung, keine Pruefung der Magic Bytes.
  • Die Dateien landen unter wwwroot/images/recipes und werden von app.UseStaticFiles() ohne Authentifizierung ausgeliefert.

Auswirkung

  • Eine hochgeladene .html oder .svg wird von derselben Origin ausgeliefert und kann Skripte ausfuehren (Stored XSS gegen ein spaeteres Web-Frontend).
  • Beliebig grosse Dateien fuellen die Platte.
  • Bilder privater Spaces sind fuer jeden mit der URL sichtbar.

Fix

  • Endungs-Whitelist (.jpg, .jpeg, .png, .webp) und dazu passenden Content-Type erzwingen, Dateinamen selbst vergeben statt den Originalnamen anzuhaengen.
  • Magic Bytes pruefen oder das Bild serverseitig neu encodieren.
  • [RequestSizeLimit] setzen und FormOptions.MultipartBodyLengthLimit konfigurieren.
  • Bilder ueber einen authentifizierten Controller-Endpunkt ausliefern statt ueber wwwroot, oder mindestens Content-Disposition: attachment und einen eigenen Static-File-Pfad mit ServeUnknownFileTypes = false verwenden.
## Problem `Controller/RecipeImagesController.cs` akzeptiert jede Datei, sobald der vom Client gesendete `Content-Type` mit `image/` beginnt: ```csharp if (!file.ContentType.StartsWith("image/")) return BadRequest("File is not an image."); ``` - Der Content-Type kommt vom Client und sagt nichts ueber den Inhalt aus. - Keine Endungs-Whitelist, keine Groessenbegrenzung, keine Pruefung der Magic Bytes. - Die Dateien landen unter `wwwroot/images/recipes` und werden von `app.UseStaticFiles()` **ohne Authentifizierung** ausgeliefert. ## Auswirkung - Eine hochgeladene `.html` oder `.svg` wird von derselben Origin ausgeliefert und kann Skripte ausfuehren (Stored XSS gegen ein spaeteres Web-Frontend). - Beliebig grosse Dateien fuellen die Platte. - Bilder privater Spaces sind fuer jeden mit der URL sichtbar. ## Fix - Endungs-Whitelist (`.jpg`, `.jpeg`, `.png`, `.webp`) und dazu passenden Content-Type erzwingen, Dateinamen selbst vergeben statt den Originalnamen anzuhaengen. - Magic Bytes pruefen oder das Bild serverseitig neu encodieren. - `[RequestSizeLimit]` setzen und `FormOptions.MultipartBodyLengthLimit` konfigurieren. - Bilder ueber einen authentifizierten Controller-Endpunkt ausliefern statt ueber `wwwroot`, oder mindestens `Content-Disposition: attachment` und einen eigenen Static-File-Pfad mit `ServeUnknownFileTypes = false` verwenden.
Author
Owner

Upload-Prüfung erledigt, Auslieferung offen.

Geändert (neu: Infrastructure/RecipeImageStorage.cs)

  • Endungs-Whitelist aus der Konfiguration, Standard .jpg, .jpeg, .png, .webp.
  • Prüfung der Magic Bytes für JPEG, PNG und WebP. Der Content-Type des Clients wird nicht mehr als Nachweis akzeptiert.
  • Größenlimit, Standard 5 MB, zusätzlich als FormOptions.MultipartBodyLengthLimit.
  • Der Originaldateiname wird verworfen, gespeichert wird unter {guid}{endung}. Damit sind weder Pfadanteile noch Doppelendungen möglich.
  • Löschpfade werden gegen das Upload-Verzeichnis validiert, ein manipulierter Pfad aus der Datenbank trifft keine fremden Dateien.

Damit ist der Weg über eine hochgeladene .html oder .svg zu, das war das eigentliche XSS-Risiko.

Offen
Bilder werden weiter über wwwroot ohne Authentifizierung ausgeliefert. Eine Umstellung auf einen geschützten Endpunkt braucht auch eine Anpassung in der App, weil Image.network keinen Authorization-Header mitschickt. Deshalb bewusst nicht in diesem Schritt.


Branch backend/issue-fixes, Commits 026544c.

Upload-Prüfung erledigt, Auslieferung offen. **Geändert** (neu: `Infrastructure/RecipeImageStorage.cs`) - Endungs-Whitelist aus der Konfiguration, Standard `.jpg`, `.jpeg`, `.png`, `.webp`. - Prüfung der Magic Bytes für JPEG, PNG und WebP. Der Content-Type des Clients wird nicht mehr als Nachweis akzeptiert. - Größenlimit, Standard 5 MB, zusätzlich als `FormOptions.MultipartBodyLengthLimit`. - Der Originaldateiname wird verworfen, gespeichert wird unter `{guid}{endung}`. Damit sind weder Pfadanteile noch Doppelendungen möglich. - Löschpfade werden gegen das Upload-Verzeichnis validiert, ein manipulierter Pfad aus der Datenbank trifft keine fremden Dateien. Damit ist der Weg über eine hochgeladene `.html` oder `.svg` zu, das war das eigentliche XSS-Risiko. **Offen** Bilder werden weiter über `wwwroot` ohne Authentifizierung ausgeliefert. Eine Umstellung auf einen geschützten Endpunkt braucht auch eine Anpassung in der App, weil `Image.network` keinen Authorization-Header mitschickt. Deshalb bewusst nicht in diesem Schritt. --- Branch `backend/issue-fixes`, Commits 026544c.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Aceless/RecipePlanner#37
No description provided.