11 Min. Lesezeit
Hoch
Von

CVE-2026-48489: Symfony Firewall-Bypass über failure_forward — wenn ein interner Subrequest die access_control-Linie umgeht

31. Mai 2026. Symfony hat am 27. Mai mit CVE-2026-48489 eine Firewall-Bypass-Lücke in der Security-HTTP-Komponente geschlossen. Wenn ein form-login-Firewall mit failure_forward: true läuft, übernimmt der DefaultAuthenticationFailureHandler den vom Client gelieferten _failure_path als Ziel eines internen Subrequests — und weil Symfony Subrequests in Firewall::onKernelRequest bewusst überspringt, läuft der AccessListener mit den access_control-Regeln nicht. Ein unauthentifizierter POST mit _failure_path=/admin/whatever liest damit jede GET-Route hinter einer breiten ^/admin-Regel aus — ohne Fehlkonfiguration, ohne Debug-Modus, ohne state-changing Handler. Für jeden Symfony- oder Sylius-Stack, der administrative Bereiche per access_control schützt und dort Read-only-Exporte oder interne APIs anbietet, ist der Befund operativ; Fix-Stände sind 5.4.53, 6.4.41, 7.4.13 und 8.0.13.

TL;DR — die 90-Sekunden-Zusammenfassung

Was wurde veröffentlicht?

CVE-2026-48489 (Symfony-Security-Advisory 27.05.2026). Klasse: Authorization-Bypass / Local Request Forgery über einen internen Subrequest. Mechanismus: Bei einer Firewall mit form-login (oder einem Authenticator, der den DefaultAuthenticationFailureHandler nutzt) und failure_forward: true liest der Handler den vom Client gelieferten _failure_path aus dem fehlschlagenden Login-Request und dispatcht ihn als HttpKernelInterface::SUB_REQUEST. Der Firewall-Listener überspringt Subrequests bewusst, also läuft der AccessListener (Auswertung von access_control) nicht.

Wie schwer?

Hoch (operative Einschätzung — Symfony vergibt keinen CVSS, NVD Stand 31.05. noch RESERVED). Reichweite: unauthentifizierter Lesezugriff auf GET-Routen hinter einer access_control-Regel. Eintrittsschwelle: (a) eine Firewall mit form-login / DefaultAuthenticationFailureHandler und (b) failure_forward: true (nicht Default — der Default ist die Redirect-Variante failure_forward: false, die nicht betroffen ist).

Welche Symfony-Versionen sind betroffen?

Security-HTTP-Komponente <5.4.53, >=6 <6.4.41, >=7 <7.4.13 und >=8 <8.0.13. Behoben in 5.4.53, 6.4.41, 7.4.13 und 8.0.13.

Bin ich als Moselwal-Kunde betroffen?

Betroffen sind Sie, wenn auf einer Ihrer Symfony- oder Sylius-Plattformen ein Firewall mit form-login und failure_forward: true konfiguriert ist und hinter einer breiten access_control-Regel (z. B. ^/admin) Read-only-GET-Endpunkte liegen. Die häufigste Konfiguration nutzt die Redirect-Variante (failure_forward: false) und ist nicht betroffen.

Sofort-Mitigation?

Zwei Schritte. Erstens, prüfen, ob in security.yaml irgendein Firewall failure_forward: true gesetzt hat (grep -r "failure_forward" config/). Wenn nein: nicht betroffen. Zweitens, wenn ja: auf den Fix-Stand heben (5.4.53 / 6.4.41 / 7.4.13 / 8.0.13). Stopgap: failure_forward auf false stellen — schließt den Pfad sofort.

Kritikalität?

Hero-Badge high (operative Einschätzung, kein offizieller CVSS). Aktive Ausnutzung Stand 31.05. nicht öffentlich dokumentiert, keine CISA-KEV-Aufnahme. Der Exploit-Pfad ist trivial nachvollziehbar (ein POST mit gesetztem _failure_path) — wer die Voraussetzung erfüllt, sollte zeitnah handeln.

 

Was ist passiert

Symfony hat am 27. Mai 2026 ein Security-Advisory zu CVE-2026-48489 veröffentlicht — einem Firewall-Bypass in der Security-HTTP-Komponente. Der Bug sitzt in einem Pfad, den die meisten Entwickler nie bewusst betreten: dem Forward-Branch der Login-Fehlerbehandlung.

Wer einen Firewall mit form-login konfiguriert (oder allgemeiner einen Authenticator, der den DefaultAuthenticationFailureHandler benutzt), kann die Option failure_forward: true setzen. Damit wird nach einem fehlgeschlagenen Login nicht auf den Login-Pfad weitergeleitet (Redirect), sondern intern ein Subrequest auf den Fehler-Pfad dispatcht und dessen Antwort direkt zurückgegeben. Der DefaultAuthenticationFailureHandler las dafür den _failure_path-Parameter — und zwar aus dem fehlschlagenden Login-Request, also aus client-kontrollierten Daten. Dieser Pfad wurde als Ziel eines HttpKernelInterface::SUB_REQUEST verwendet.

Der eigentliche Sicherheitsbruch entsteht durch eine zweite, für sich genommen korrekte Designentscheidung: Symfonys Firewall::onKernelRequest-Listener überspringt Subrequests absichtlich, unter der Annahme, dass Subrequests intern generiert und damit vertrauenswürdig sind. Genau diese Annahme bricht hier: weil der Angreifer das Ziel des Subrequests über _failure_path kontrolliert, läuft der AccessListener — der Listener, der die access_control-Regeln auswertet — für diesen Subrequest nicht. Ein unauthentifizierter POST auf den Check-Pfad mit _failure_path=/admin/whatever führt damit eine Local Request Forgery aus: der Ziel-Controller wird außerhalb des Firewall-Perimeters ausgeführt und seine Antwort an den Aufrufer zurückgegeben.

Das Symfony-Advisory ist hier ungewöhnlich deutlich: Anwendungen, die der empfohlenen Best Practice folgen und administrative Bereiche mit breiten access_control-Regeln schützen (z. B. ^/admin erfordert ROLE_ADMIN) und unter diesem Bereich Read-only-GET-Endpunkte anbieten (Daten-Exporte, interne APIs, Account-Views), sind vollständig exponiert: jede solche GET-Route ist von einem unauthentifizierten Angreifer lesbar — ohne Entwicklerfehler, ohne Debug-Modus und ohne dass ein state-changing GET-Handler existieren müsste. Der Fix sorgt dafür, dass der DefaultAuthenticationFailureHandler den request-gelieferten _failure_path bei aktivem failure_forward nicht mehr berücksichtigt; der Subrequest geht immer auf den vom Anwendungsbetreiber konfigurierten failure_path (Default: login_path). Der Redirect-Branch (failure_forward: false) bleibt unverändert.

Technische Einordnung

Strukturell ist CVE-2026-48489 ein Lehrstück über Trust Boundaries an der Subrequest-Grenze. Das Subrequest-Konzept von Symfonys HttpKernel ist mächtig und korrekt: ESI-Fragmente, render()-Controller-Forwards und interne Sub-Renders sollen nicht jedes Mal die volle Request-Verarbeitungs-Kette inklusive Firewall durchlaufen, weil sie aus vertrauenswürdigem Anwendungscode stammen. Die Annahme „Subrequest == intern == vertrauenswürdig“ ist deshalb in Firewall::onKernelRequest fest verdrahtet. Sie hält genau so lange, wie das Ziel des Subrequests ausschließlich von Anwendungscode bestimmt wird. Sobald ein einziger Pfad existiert, in dem ein client-kontrollierter Wert in das Subrequest-Ziel fließt, kippt die Annahme — und mit ihr die komplette Autorisierungs-Schicht für diesen Request.

Der zweite methodische Punkt ist die Verwandtschaft zur Klasse Local Request Forgery / „confused deputy“. Der DefaultAuthenticationFailureHandler agiert hier als verwirrter Stellvertreter: er hat die Berechtigung, interne Subrequests zu dispatchen, und führt im Auftrag eines unauthentifizierten Aufrufers eine Operation aus, die dieser selbst nicht ausführen dürfte. Das ist dieselbe Grundform wie SSRF (nur dass das Ziel hier intern und nicht extern ist) und wie klassische Forward-/Include-Authorization-Bypässe in anderen Frameworks. Die Lehre ist generisch: jede Komponente, die im Auftrag eines Aufrufers eine privilegierte interne Operation auslöst, muss das Operationsziel aus einer vertrauenswürdigen Quelle beziehen — nicht aus dem Request, den sie gerade bearbeitet.

Drittens, die Eintrittsschwelle ist wichtig für die Triage. failure_forward: true ist nicht der Default. Die übliche, in der Symfony-Dokumentation als Standard gezeigte Konfiguration nutzt die Redirect-Variante (failure_forward: false), die nicht betroffen ist. Der Forward-Branch wird typischerweise in zwei Situationen gesetzt: erstens in Single-Page-artigen Login-Flows, die die Fehlerantwort direkt im selben Response-Zyklus rendern wollen, und zweitens in gewachsenen Setups, in denen die Option aus einem alten Tutorial oder einem kopierten security.yaml-Block stammt und nie hinterfragt wurde. Genau die zweite Klasse ist im Pflege-Drift gefährlich, weil niemand mehr weiß, dass sie gesetzt ist. Ein grep -r "failure_forward" config/ über den Bestand beantwortet die Frage in Sekunden.

Viertens, die Einordnung in die Symfony-Mai-2026-Release-Welle. CVE-2026-48489 ist Teil desselben koordinierten Release-Stands (5.4.53 / 6.4.41 / 7.4.13 / 8.0.13), der auch CVE-2026-48736 (SSRF-Bypass in NoPrivateNetworkHttpClient über IPv6-Transition-Formen) und mehrere weitere Komponenten-Fixes mitliefert. Der Patch-Pfad ist geteilt: wer auf den Fix-Stand hebt, schließt beide Symfony-Core-Lücken in einem Schritt. Die parallele Twig-Sandbox-Welle (Twig 3.27.0) ist eine separate Komponente und betrifft nur Stacks, die nutzergesteuerte Twig-Template-Source verarbeiten.

Wer ist betroffen

BetroffenNicht betroffenBedingung
Symfony-Apps mit Security-HTTP <5.4.53 / <6.4.41 / <7.4.13 / <8.0.13Apps auf den Fix-Ständen 5.4.53 / 6.4.41 / 7.4.13 / 8.0.13 und höherVersionsstand der symfony/security-http-Komponente
Firewalls mit form-login bzw. DefaultAuthenticationFailureHandler und failure_forward: trueFirewalls mit failure_forward: false (Default, Redirect-Variante)failure_forward-Wert in security.yaml
Apps mit breiten access_control-Regeln (z. B. ^/admin) und Read-only-GET-Endpunkten dahinterApps mit zusätzlicher Controller-Autorisierung (Voters / #[IsGranted] im Controller)Schutz-Modell: access_control allein vs. zusätzliche Controller-Autorisierung
Sylius-Shops und Symfony-direct-Apps mit Custom-Admin-LoginTYPO3 (eigene Auth-Schicht, nicht Symfony-Security-Firewall)Auth-Architektur des CMS/Shops

Ein wichtiger Punkt für die Einordnung: Routen, die zusätzlich auf Controller-Ebene autorisiert sind — etwa mit #[IsGranted('ROLE_ADMIN')] direkt am Controller oder über einen Security-Voter im Controller-Code — sind durch diese zweite Schicht geschützt, weil sie unabhängig vom access_control-Listener greift. Wer also Defense-in-Depth fährt (Firewall-access_controlund Controller-Autorisierung), hat hier einen Puffer. Wer sich allein auf die access_control-Regel verlässt — die von Symfony empfohlene und sehr verbreitete Praxis —, ist exponiert. Das ist die unangenehme Pointe: die Best-Practice-Konfiguration ist die verwundbare.

Bedeutung für den Mittelstand

Symfony ist im DACH-Mittelstand die unsichtbare Infrastruktur unter sehr vielen Webanwendungen — direkt als Symfony-App, transitiv unter Sylius-Shops, unter API-Plattformen und unter zahllosen Inhouse-Tools. Die Frage für Sie als Betreiber ist konkret: läuft auf einer Ihrer Plattformen ein Login-Firewall mit failure_forward: true, und liegen hinter einer ^/admin-artigen Regel GET-Endpunkte, die Daten ausliefern?

In der Praxis sind die exponierten Endpunkte fast immer die unscheinbaren: ein CSV-Export der Kundenliste unter /admin/export/customers, ein internes Status-JSON unter /admin/api/orders, eine Account-Detail-View unter /admin/users/{id}. Genau diese Read-only-Routen werden beim Threat-Modeling oft übersehen, weil sie „nur lesen“ und „eh hinter dem Admin-Login“ liegen. CVE-2026-48489 macht aus diesem „eh hinter dem Login“ ein „mit einem POST auslesbar“.

Compliance-seitig trifft die Lücke mehrere Achsen. DSGVO Art. 32 verlangt „dem Risiko angemessene technische Maßnahmen“; ein unauthentifizierter Lesezugriff auf einen Endpunkt, der personenbezogene Daten ausliefert (Kundenexporte, Account-Views), ist hier ein klarer Befund, und nach Art. 33/34 kann eine nachweisbare Ausnutzung meldepflichtig werden. NIS-2 Art. 21 fordert für die betroffenen mittelgroßen Unternehmen konkrete Patch-Disziplin und ein belastbares Asset-/Dependency-Inventar — eine symfony/security-http-Version unterhalb des Fix-Stands, die im SBOM nicht geführt wird, ist an dieser Stelle ein Prüfungsbefund. Eine Rechtseinschätzung nehmen wir hier nicht vor (wir sind keine Kanzlei); die Datenschutz-Folgenabschätzung gehört zu Ihrem DSB.

Operativ ist die gute Nachricht, dass die Triage billig ist. Ein grep -r "failure_forward" config/ über das Plattform-Inventar trennt in Minuten die betroffenen von den nicht betroffenen Stacks. Die schlechte Nachricht ist die übliche: wer kein Dependency-Inventar und keinen reproduzierbaren Build hat, weiß nicht zuverlässig, welche symfony/security-http-Version in welcher Anwendung steckt — und genau dort sitzt die eigentliche Arbeit.

Bedeutung für die technische Entwicklung

Architektonisch ist CVE-2026-48489 eine Erinnerung an drei Disziplinen.

Erstens, Subrequest-Ziele sind Trust-Boundary-Entscheidungen, keine Implementierungsdetails. Jede Stelle im eigenen Code, an der ein Subrequest, ein internes Forward, ein Sub-Render oder ein forward() ausgelöst wird, muss die explizite Frage beantworten: woher kommt das Ziel? Kommt es aus Anwendungs-Konstanten, Routing-Konfiguration oder fest verdrahtetem Code — gut. Kommt auch nur ein Bestandteil des Ziels aus dem Request — Query-Parameter, Body-Feld, Header —, gehört eine Allowlist oder eine feste Bindung dazwischen. Das gilt nicht nur für Symfony: dieselbe Klasse existiert in jedem Framework mit internem Dispatch.

Zweitens, Defense-in-Depth ist hier nicht optional, sondern der Puffer. Wer Admin-Routen ausschließlich über access_control schützt, hat genau eine Autorisierungs-Schicht — und wenn diese eine Schicht durch einen Framework-Bug ausfällt, fällt der gesamte Schutz aus. Wer zusätzlich auf Controller-Ebene autorisiert (#[IsGranted], Voters), hat eine zweite, unabhängige Schicht, die diesen konkreten Bypass auffängt. Das ist kein Argument gegen access_control — die Firewall-Regel bleibt die richtige erste Linie —, sondern eines für die zweite Linie an sensiblen Endpunkten.

Drittens, „intern == vertrauenswürdig“ ist eine Annahme mit Verfallsdatum. Die Subrequest-Vertrauensannahme war über Jahre korrekt und ist es im Normalfall weiterhin. Der Bug zeigt, dass solche fundamentalen Annahmen einen Audit-Eintrag verdienen: einmal pro größerem Release die Frage stellen „welche Pfade können ein als-intern-markiertes Konstrukt mit extern-kontrolliertem Inhalt füllen“. Für die eigene Codebase heißt das konkret: ein grep über SUB_REQUEST, forward(, HttpKernelInterface:: und vergleichbare interne Dispatch-Stellen, und an jeder Fundstelle die Ziel-Herkunft prüfen.

Konkrete Handlungsempfehlung

Operational Decision Block

In dieser Reihenfolge. Erstens, Inventur heute: über das Plattform-Inventar jeden Symfony-/Sylius-Stack identifizieren und grep -r "failure_forward" config/ fahren; parallel den symfony/security-http-Versionsstand aus composer.lock ziehen (composer show symfony/security-http). Zweitens, Patch-Lauf für die getroffenen Hosts: auf den Fix-Stand der jeweiligen Branche heben — composer update symfony/security-http auf 5.4.53 / 6.4.41 / 7.4.13 / 8.0.13 (bzw. das passende symfony/symfony-Meta-Paket), Build neu durchlaufen, deployen. Für Container-Images den Base-/App-Image-Tag neu builden, nicht nur den laufenden Container patchen. Drittens, Stopgap für Hosts, die nicht sofort patchen können: failure_forward: false setzen (Redirect-Variante, nicht betroffen) — das ändert das UX-Verhalten beim Login-Fehler, schließt aber den Pfad sofort. Viertens, Logging-Sweep über die letzten 30 Tage: Access-Logs auf POST-Requests an Login-Check-Pfade mit gesetztem _failure_path-Parameter prüfen, insbesondere mit Werten, die auf /admin-Pfade zeigen. Fünftens, mittelfristig: an sensiblen Admin-Endpunkten eine zweite Autorisierungs-Schicht auf Controller-Ebene ergänzen (#[IsGranted] / Voter), damit ein künftiger Firewall-Layer-Bug nicht erneut die gesamte Schutzlinie aushebelt.

Wenn diese Schritte aus eigener Kraft nicht laufen, sprechen Sie mit uns: Moselwal hält Symfony- und Sylius-Plattformen in einem laufenden SBOM- und Patch-Prozess, prüft Firewall-/access_control-Konfigurationen im Architektur-Review und validiert Mitigationen gegen den dokumentierten Exploit-Pfad — Plattformbetrieb statt Beratung-on-paper.

Dieser Beitrag spiegelt unsere technische und strategische Einschätzung. Er ersetzt keine Rechtsberatung und keine Datenschutz-Folgenabschätzung.

Fazit

CVE-2026-48489 ist kein RCE und keine Critical-Massenwelle — und gerade deshalb operativ heikel, weil er die empfohlene Konfiguration trifft: wer Admin-Bereiche brav per access_control schützt und dort harmlose Read-only-GET-Routen anbietet, ist mit failure_forward: true exponiert, ohne irgendetwas falsch gemacht zu haben. Die Triage ist billig (grep über failure_forward), der Fix ist ein normales Composer-Update, der Stopgap (failure_forward: false) ist ein Einzeiler. Die wichtigste Empfehlung ist die mittelfristige: sensible Admin-Endpunkte verdienen eine zweite, controllernahe Autorisierungs-Schicht, damit der nächste Firewall-Layer-Bug nicht erneut die ganze Linie aushebelt. Risiko nüchtern: hoch für die betroffene Konfiguration, irrelevant für alle anderen — die Kunst liegt im sauberen Trennen der beiden Klassen.

Quellen

Über den Autor

Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Founder · Moselwal Digitalagentur · OnlyOle

Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht, heute Moselwal. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität.