Joomla-Medienmanager: Upload als Base64 in JSON, Hintergrund und Nachteile aus Hoster-Sicht

  • Joomla Version
    Ab Joomla 5
    PHP Version
    Unbekannt
    Hoster
    fc-hosting.de

    Hi!

    ich habe eine Frage/Hinweis zum Medienmanager ab Joomla 4. Es tauchte ein Problem auf, welches leider nicht so einfach erkannt, analysiert und gelöst werden konnte.

    Anlass
    Ein Kunde konnte im Medienmanager eine Audiodatei von 3,2 MB nicht hochladen. Der Request

    POST /administrator/index.php?option=com_media&format=json&task=api.files&path=local-images:/...

    wurde mit HTTP 413 abgewiesen, bevor er PHP erreicht hat. Kleinere Dateien liefen durch.

    Ursache: Der Medienmanager schickt die Datei nicht mehr als multipart/form-data, sondern Base64-kodiert in einem application/json-Body. Aus 3,2 MB Datei werden rund 4,27 MB Body. Unsere WAF (ModSecurity 2) begrenzt Request-Bodies ohne Datei-Anteile auf 4 MB (SecRequestBodyNoFilesLimit). Ein JSON-Body enthält für die WAF keine Datei, der gesamte Upload zählt also gegen ein Limit, das für Formulardaten gedacht ist.

    Warum wir dieses Limit nicht einfach anheben?
    Die Begrenzung ist kein Versehen, sondern eine bewusste Schutzmaßnahme. Bodies ohne Datei-Anteile (Formulardaten, JSON, XML) sind genau die Daten, die eine WAF inhaltlich prüft: Sie werden vollständig gepuffert, in einzelne Parameter zerlegt und von jeder Regel per Regex durchsucht. Der Aufwand an RAM und CPU wächst mit der Body-Größe. Hochgeladene Dateien in einem multipart-Request sind davon ausgenommen, sie werden nur auf Platte gestreamt und nicht durchsucht. Wir haben den Wert vor kurzem im Zuge einer Härtung unserer WAF-Konfiguration bewusst auf 4 MB festgelegt.

    Ohne dieses Limit könnte jeder unangemeldet beliebig große Formular- oder JSON-Bodies an jede Website auf dem Server schicken und damit die WAF beschäftigen, bevor die Anwendung überhaupt gefragt wird. Das ist ein leichter Hebel für Überlastungsangriffe. Legitime Formulardaten liegen fast immer im Kilobyte-Bereich. Der Default der Engine ist deshalb 1 MB, die mitgelieferte Beispielkonfiguration setzt 128 KB. Unsere 4 MB sind bereits großzügig.

    Das Problem entsteht erst dadurch, dass der Medienmanager eine Datei so verpackt, dass sie für die WAF wie Formulardaten aussieht. Wir haben dafür jetzt eine Ausnahme für option=com_media&task=api.files eingerichtet. Das muss aber jeder Hoster mit einer WAF einzeln tun, und er muss die Ursache erst einmal finden.

    Frage
    Warum wurde der multipart-Upload aus Joomla 3 durch Base64 in JSON ersetzt? Meine Vermutung: Es ergibt sich aus der neuen Architektur (einheitliche JSON-API für den Vue-Client, gleicher Weg für Uploads und für im Browser bearbeitete Bilder, Speicher-Adapter). Stimmt das, oder gab es weitere Gründe, etwa Sicherheit?

    Nachteile aus unserer Sicht

    1. Die PHP-Upload-Limits greifen nicht mehr.
    upload_max_filesize gilt nur für multipart-Uploads. post_max_size wird bei einem JSON-Body nicht durchgesetzt: PHP schreibt bei Überschreitung nur eine Warnung, php://input liefert den Body trotzdem vollständig. Joomla selbst prüft in ApiController::checkFileSize() nur den eigenen Wert upload_maxsize aus den Optionen der Medienverwaltung, und zwar erst nach dem Dekodieren. Die Werte, mit denen Hoster Uploads üblicherweise begrenzen, sind für den Medienmanager damit wirkungslos. Eine Begrenzung in der Vhost-Konfiguration (LimitRequestBody) wäre möglich, damit arbeiten wir wie viele Hoster aber nicht, die Limits stehen in den PHP-Einstellungen.

    2. Einzige wirksame PHP-Grenze ist memory_limit.
    Der JSON-Body, der Base64-String und die dekodierte Datei liegen gleichzeitig im Arbeitsspeicher. In einem einfachen Test lag der Spitzenverbrauch beim rund Vierfachen der Dateigröße. Der RAM wird gefüllt, bis memory_limit erreicht ist, dann bricht PHP mit einem Fatal Error ab. Ein multipart-Upload wird dagegen von PHP auf Platte gestreamt und braucht kaum Skript-Speicher. Wenn ich sehe das es immer mehr Tarife gibt die irrsinnige Werte für ein memory_limit bereitstellen oder man das sogar selbst einstellen kann, dann wird es immer leider einen Server in den RAM-Tod zu schicken bzw. zu überlasten.

    3. Ressourcenverbrauch auf dem Server.
    Jeder Upload belegt ein Mehrfaches der Dateigröße an RAM im PHP-Prozess. Auf Shared-Hosting-Systemen mit vielen parallelen Prozessen ist das ein spürbarer Unterschied zum Streaming auf Platte.

    4. Rund ein Drittel mehr Datenvolumen.
    Base64 vergrößert jede Datei um etwa 33 %, entsprechend länger dauert die Übertragung. Dazu kommen Kodieren im Browser und Dekodieren in PHP.

    5. Speicherbedarf im Browser.
    Die Datei wird vor dem Absenden komplett gelesen und kodiert. Bei sehr großen Dateien startet der Upload gar nicht (GitHub-Issue #44326).

    6. WAFs und Reverse-Proxys erkennen den Request nicht als Datei-Upload.
    Limits und Prüfregeln für Bodies ohne Datei-Anteile gelten für den gesamten Upload. Die mitgelieferte Beispielkonfiguration von ModSecurity setzt dieses Limit auf 128 KB, der Default der Engine ist 1 MB. Auf vielen Servern scheitern Uploads deshalb schon bei wenigen hundert KB bis wenigen MB. Zusätzlich muss die WAF den gesamten Body puffern und prüfen, was bei Base64-Daten nutzlos ist.

    7. Schlechte Fehlermeldungen.
    Die Ablehnung passiert vor Joomla (413 von WAF oder Webserver) oder als PHP Fatal Error. Der Anwender bekommt keine verständliche Meldung, und der Betreiber sucht in den PHP- und Joomla-Einstellungen, wo alles korrekt aussieht.

    8. Der Medienmanager ist faktisch auf kleine Dateien beschränkt.
    Audio und Video, die der Medienmanager ausdrücklich als Medientypen unterstützt, überschreiten schnell, was das Verfahren verkraftet. In #44326 heißt es von Maintainer-Seite, er sei für relativ kleine Dateien ausgelegt.
    Bezug: GitHub joomla/joomla-cms, Issue #44326 (große Uploads starten nicht) und Pull Request #36023 (Prüfung der Upload-Limits).

    9. Kein erkennbarer Sicherheitsgewinn.
    Dateiendung, MIME-Typ und Größe werden in beiden Verfahren serverseitig geprüft.


    PS:
    Meine selbst erstellte eigentliche Anfrage mit vielen Tippfehlern habe ich mit KI "verfeinert", damit die Struktur besser und inhaltlich verständliche wird. Ich hoffe das hat nun nicht das Gegenteil bewirkt :)

  • Es ergibt sich aus der neuen Architektur (einheitliche JSON-API für den Vue-Client, gleicher Weg für Uploads und für im Browser bearbeitete Bilder, Speicher-Adapter). Stimmt das, oder gab es weitere Gründe, etwa Sicherheit?

    Ist in der Tat eine Architekturentscheidung: der payload aus dem Request lässt sich so einheitlich aus php://input lesen und dekodieren.