Der Ladybird Browser, eine noch junge Browser-Engine, die aus dem SerenityOS-Projekt hervorgeht, zieht Aufmerksamkeit durch seine originelle Architektur und quelloffene Entwicklung auf sich. Trotz ihres vielversprechenden Designs befindet sich Ladybird noch in der Pre-Alpha-Phase. Die frühe Entwicklungsstufe hat ihre Chancen und Risiken: Einerseits können Entwickler und Forscher Sicherheitslücken offenlegen und adressieren, andererseits ergeben sich Schwachstellen, die potenziell von Angreifern ausgenutzt werden können. Im Zentrum der Analyse steht die JavaScript-Engine LibJS, die für die Ausführung von Skripten innerhalb des Browsers verantwortlich ist und besonders anfällig für Fehler ist, da sie noch keine Kompilierungsebenen implementiert hat, sondern ausschließlich interpretiert. Die Architektur von LibJS kombiniert moderne Optimierungen mit tiefgehenden Verifikationsebenen, etwa zum Schutz vor Integerüberläufen und Speicherzugriffsfehlern.
Trotz dieser Sorgfalt wurden mehrere Fehler entdeckt, von harmlosen Überlastungen bis hin zu Speicherkorruptionen, die den Angriff auf den Browser ermöglichen. Um LibJS systematisch auf Schwachstellen zu prüfen, wurde die bekannte JavaScript-Fuzzer-Software Fuzzilli eingesetzt, die dynamisch generierten Code mutiert und testet, um Fehlerzustände und Abstürze zu provozieren. Nach rund zehn Tagen intensiven Fuzzings konnten verschiedene Fehler isoliert werden. Einige davon waren nur kurzfristige Abstürze oder Ressourcenüberbelastungen, doch besonders die Fehler im Bereich Heap-Pufferüberläufe und Use-After-Free (UAF) erwiesen sich als sicherheitskritisch. Ein Use-After-Free-Fehler in Ladybirds Interpreter-Argumentpuffer bildete die Grundlage für einen vielversprechenden Exploit, der durch die Manipulation einer Proxy-Funktion mit einem bösartigen [[Get]]-Handler ausgelöst wurde.
Konkret konnte dieser Fehler ausgenutzt werden, wenn ein Konstruktor mit einem Proxied-Function-Object aufgerufen wurde. Ladybirds interne Abläufe erzeugen dabei eine Situation, in der ein Vektor von Argumenten vorzeitig freigegeben wird, aber dennoch anschließend verwendet wird – klassisch ein Use-After-Free. Der Kern der Schwachstelle liegt in der Abfolge der Ausführung innerhalb der Methode internal_construct, die zunächst einen Zeiger auf die Argumentliste erhält und anschließend ein neues Objekt mit dem Prototyp des Konstruktors erstellt. Wenn die Zwischenschritte die Freigabe des Speicherbereichs verursachen, nutzt die spätere Verwendung noch den bereits freigegebenen Speicher – was zu undefiniertem Verhalten führt. Besonders kritisch ist, dass die Funktion ordinary_create_from_constructor den Prototyp über einen internen [[Get]]-Mechanismus abruft, was im Fall einer Proxy-Funktion die Ausführung willkürlichen JavaScript-Codes ermöglicht.
Über diesen Mechanismus kann also ein Angreifer die Freigabe und Neuzuweisung des Argumentpuffers herbeiführen und so den Use-After-Free-Fehler ausnutzen. Um die genaue Auswirkung dieses Fehlers zu verstehen, lohnt es sich, die Struktur des Argumentpuffers zu beleuchten. LibJS verwendet zur Speicherung der Funktionsargumente intern einen dynamisch vergrößerbaren Vektor, der je nach Funktionsaufruf unterschiedliche Größen annimmt. Wird beispielsweise ein Funktionsaufruf mit mehr Parametern ausgeführt, als der bestehende Puffer aufnehmen kann, muss der Vektor neu allokiert werden. Ein Angriff kann dies ausnutzen, um einen bereits referenzierten Speicherbereich frühzeitig freizugeben und mit eigenen Daten zu überschreiben.
Diese Manipulation eröffnet ein breites Spektrum an Exploitationstechniken, angefangen bei einem Adressable-Object (addrof) Primitive, das erlaubt, Speicheradressen von Objekten zu erfahren, bis hin zu vollständigen Arbitrary Read/Write Fähigkeiten. Über die Verwendung von JavaScript-FinalizationRegistry können Überbleibsel interner Strukturen auf dem Heap gezielt platziert und genutzt werden, um einen fortlaufenden Leckage-Mechanismus zu schaffen. Die Kampagne zur nahezu vollständigen Kontrolle über den Speicher beinhaltet das Erzeugen gefälschter Objekte und Strukturen innerhalb des Speichers, die Ladybird dazu bringen, auf manipulierte interne Speicherbereiche zuzugreifen. Dies erlaubt nicht nur das Lesen, sondern auch das Verändern von Speicherinhalten und damit die Manipulation des Programmflusses. Technisch besonders elegant ist hierbei, dass manche intern genutzten Strukturen keine virtuellen Methoden (vtables) besitzen, was die Konstruktion gefälschter Objekte stark erleichtert.
Sobald Arbitrary Read/Write Zugriff besteht, kann eine tiefergehende Analyse mit dem Ziel der Codeausführung in Betracht gezogen werden. Ein möglicher Weg führt über das Auffinden und Überschreiben von Rücksprungadressen auf dem Stack, um so die Kontrolle über den Kontrollfluss des Programms zu übernehmen. Beginnend mit einem Leaken von vtable-Pointern und verschiedenen Global Offset Table (GOT) Einträgen kann die Adresse des Stacks ermittelt werden, trotz Adressrandomisierung (ASLR). Daraufhin werden gezielt Rücksprungadressen überschrieben, um eine Return-Oriented-Programming (ROP)-Chain zu initiieren. Als Demonstration wurde ein Proof-of-Concept erstellt, das mittels ROP-Chain ein Ausführen eines Rabattbefehls (execve) ermöglicht.
Konkret wurde ein Aufruf von „/calc“ simuliert, um eine Rechenanwendung zu starten. Ein solcher Exploit zeigt das hohe Risiko, das bei frühen Entwicklungsstufen von Softwareprodukten besteht, vor allem wenn umfangreiche Sprachen wie JavaScript interpretiert werden. Für Nutzer und Entwickler bedeutet dies eine Mahnung, Sicherheitslücken früh zu identifizieren, verantwortungsvoll zu melden und schnell zu beheben. Die Analyse von Ladybirds Sicherheitsproblem illustriert prägnant, wie moderne Browser-Engines mit teilweise komplexen und vernetzten Speicherverwaltungsmechanismen angreifbar werden können – ein Thema, das auch bei etablierten Produkten regelmäßig für Schlagzeilen sorgt. Neben der technischen Tiefe bietet dieser Fall zudem einen Einblick in die Funktionsweise moderner Interpreter-Engines und wie Entwickler-Tools wie Fuzzilli bei der Schwachstellenfindung helfen.
Fuzzilli beispielsweise ist ein auf JavaScript zugeschnittener Fuzzer, der Eingaben über ein eigenes Zwischenformat („FuzzIL“) generiert und mutiert, um codebasierte Abstürze zuverlässig auszulösen. Die Kombination aus Open-Source-Einblick, reproduzierbaren Crashes und der Offenlegung von Schwachstellen fördern nicht nur die Stabilität von Ladybird, sondern fördern auch den Erfahrungsaustausch in der Sicherheitscommunity. Abschließend lässt sich sagen, dass Ladybirds Sicherheitslücke exemplarisch die Herausforderungen moderner Softwareentwicklung widerspiegelt. Die Balance zwischen Innovationsgeschwindigkeit und Sicherheitsstabilität erfordert eine kontinuierliche Aufmerksamkeit für potenzielle Fehlstellen – insbesondere, wenn Produkte wie Browser das Tor zum gesamten Internet bilden und damit eine besonders sensible Angriffsfläche darstellen. Entwickler sind gut beraten, von Anfang an sichere Speicherverwaltung und strikte Reihenfolgen bei Operationen wie der Konstruktion von Objekten zu implementieren, um Use-After-Free und ähnliche Probleme auszuschließen.
Parallel lehrt der Fall die Bedeutung abschließender Prüfungen, wie es etwa mit Address Sanitizer (ASan) möglich ist. Dies unterstreicht die Notwendigkeit, bei neuen Engines nicht nur auf Performance und Funktionalität zu setzen, sondern auch Sicherheitsmechanismen systematisch zu integrieren. Mit der vorgestellten Analyse und den daraus gewonnenen Erkenntnissen können eine verbesserte Codebasis und robustere Browser entstehen, die sichere JavaScript-Ausführung garantieren – und so die Nutzer vor potenziellen Angriffen schützen.