Secure Coding: Probleme durch CWE 416 – Use After Free – in Java vermeiden
Die Common Weakness Enumeration CWE-416 beschreibt Sicherheitslücken beim Speichermanagement, die auch Java betreffen – Abhilfe schafft die Cleaner-API.
(Bild: erstellt mit Chat-GPT / DALL-E)
- Sven Ruppert
Die in Common Weakness Enumeration CWE-416 beschriebene Schwachstelle Use After Free (UAF) tritt auf, wenn ein Programm einen Zeiger weiterhin verwendet, nachdem dieser freigegeben wurde. Das kann Sicherheitslücken öffnen und zu undefiniertem Verhalten führen, einschließlich Abstürzen bis hin zur Datenbeschädigung. Das Problem entsteht dadurch, dass der vom Zeiger referenzierte Speicher gegebenenfalls für andere Zwecke neu zugewiesen wird. Das wiederum können Angreifer ausnutzen.
Ursachen für CWE-416 – Use After Free (UAF)
Falsche Speicherverwaltung: Vergessen, Zeiger zu annullieren, nachdem Speicher freigegeben wurde.
Baumelnde Zeiger: Verweise auf freigegebenen Speicher beibehalten und später darauf zugreifen.
Doppelte Freigabe: Speicher wird mehr als einmal freigegeben, was zu mehrfachen Vorgängen am gleichen Speicherort führen kann.
Mögliche Folgen durch CWE-416
Sicherheitslücken: Angreifer können UAF ausnutzen, um beliebigen Code auszuführen, Berechtigungen zu erweitern oder einen Denial-of-Service (DoS) auszulösen.
Datenkorruption: UAF kann zu unerwarteten Datenänderungen führen, die gegebenenfalls Programminstabilität oder fehlerhaftes Verhalten nach sich ziehen.
Programm stürzt ab: Der Zugriff auf freigegebenen Speicher kann zu Segmentierungs- oder anderen Laufzeitfehlern führen.
Videos by heise
Die Bedeutung von CWE-416 in Java
Durch die automatische Garbage Collection schützt Java von sich aus vor vielen Arten von Speicherverwaltungsproblemen, einschließlich Use-After-Free-Schwachstellen. Allerdings können dennoch in Java UAF-ähnliche Probleme auftreten, wenn andere Ressourcen als der Speicher (etwa Datei-Handles oder Netzwerkverbindungen) falsch verwaltet werden. Nachfolgend finden Sie einige Szenarien in Java, die UAF ähneln, sowie Strategien zu deren Entschärfung.
Szenario 1: Missmanagement externer Ressourcen
Obwohl der Speicher in Java vom Garbage Collector (GC) verwaltet wird, können andere Ressourcen wie Datei-Handles oder Netzwerk-Sockets falsch verwaltet werden, was zu Problemen bei der Verwendung nach dem Schließen führt.
Codebeispiel:
import java.io.*;
public class UseAfterFreeExample {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("example.txt");
// Use the file input stream...
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Attempt to use the file input stream after it has been closed
try {
int data = fis.read(); // This will throw an IOException
} catch (IOException e) {
System.out.println("Attempted to read from a closed stream.");
}
}
}
In diesem Beispiel führt der Versuch, aus der Ressource zu lesen, nach dem Schließen des FileInputStream zu einer IOException.
Automatisches Ressourcenmanagement mit Try-With-Resources:
Verwenden Sie die in Java 7 eingeführte Try-With-Resources-Anweisung, die sicherstellt, dass jede Ressource am Ende der Anweisung geschlossen wird.
import java.io.*;
public class UseAfterFreeFixed {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("example.txt")) {
// Use the file input stream...
} catch (IOException e) {
e.printStackTrace();
}
// fis is automatically closed here
// Attempting to use fis here will cause a compilation error
}
}
Explizite Nichtig-Erklärung:
Setzen Sie Referenzen nach dem Schließen auf Null, um eine versehentliche Wiederverwendung zu vermeiden.
import java.io.*;
public class UseAfterFreeWithNull {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("example.txt");
// Use the file input stream...
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
fis = null; // Prevent reuse
}
}
}
// fis is now null, so the following attempt
// to use it will fail at compile-time
if (fis != null) {
try {
int data = fis.read(); // This will not compile since fis is null
} catch (IOException e) {
System.out.println("Attempted to read from a closed stream.");
}
}
}
}
Szenario 2: Unangemessene Verwendung schwacher Referenzen
Auch wenn es sich hierbei nicht unmittelbar um ein UAF-Problem handelt, kann der Missbrauch schwacher Referenzen dennoch negative Folgen haben, wenn auf Objekte zugegriffen wird, nachdem sie vom Garbage Collector zurückgefordert wurden.
import java.lang.ref.WeakReference;
public class WeakReferenceExample {
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
obj = null; // Eligible for garbage collection
System.gc(); // Suggest garbage collection
// Attempt to use the object after it may have been collected
if (weakRef.get() != null) {
System.out.println("Object is still alive.");
} else {
System.out.println("Object has been garbage collected.");
}
}
}
In diesem Beispiel kann der Zugriff auf die schwache Referenz nach dem Annullieren der starken Referenz und dem Vorschlagen der Speicherbereinigung zu Null führen, was darauf hinweist, dass das Objekt vom GC bereits eingesammelt wurde. Vermeiden Sie die Verwendung schwacher Referenzen für kritische Objekte, die noch benötigt werden.
Überprüfen Sie immer, ob eine schwache Referenz gelöscht wurde, bevor Sie sie verwenden:
if (weakRef.get() != null) {
// Safe to use the object
Object obj = weakRef.get();
// Use obj
} else {
// Handle the case where the object has been collected
System.out.println("Object has been garbage collected.");
}
Während die automatische Speicherverwaltung von Java das Risiko herkömmlicher UAF-Schwachstellen verringert, müssen Entwicklerinnen und Entwickler dennoch bei der Ressourcenverwaltung und schwachen Referenzen vorsichtig sein, um vergleichbare Probleme zu vermeiden. Moderne Java-Funktionen wie Try-with-Ressourcen und die Berücksichtigung von Objektreferenzen können dabei helfen, robusten und fehlerfreien Code aufrechtzuerhalten.
Welche CVEs basieren auf CWE-416 in Java
Hier sind einige im Zusammenhang mit CWE-416-Schwachstellen in Java zu beachtende CVEs:
CVE-2022-45146
Diese Sicherheitslücke findet sich in der FIPS-Java-API von Bouncy Castle BC-FJA vor Version 1.0.2.4. Änderungen im JVM-Garbage Collector in Java 13 und späteren Versionen können dieses Problem auslösen und dazu führen, dass vom Modul verwendete temporäre Schlüssel auf Null gesetzt werden, während sie noch verwendet werden. Dies führt zu Fehlern oder potenziellem Informationsverlust. Benutzer der FIPS-Konformität sind davon nicht betroffen, da dies für Java 7, 8 und 11 gilt.
CVE-2023-38703
Diese CVE betrifft die PJSIP-Multimedia-Kommunikationsbibliothek, die in mehreren Sprachen, einschließlich Java, geschrieben ist. Das Problem tritt auf, wenn die Synchronisierung zwischen SRTP-Medien auf höherer Ebene und Transport auf niedrigerer Ebene, wie UDP, nicht aufrechterhalten wird – was zu einer UAF-Schwachstelle führt. Dies kann zu einer unerwarteten Beendigung der Anwendung oder einer Speicherbeschädigung führen.
Die beschriebenen Beispiele verdeutlichen die Bedeutung einer ordnungsgemäßen Speicher- und Ressourcenverwaltung in Java-Anwendungen, auch wenn die Sprache den Speicher durch Garbage Collection verwaltet. Weitere Informationen zu diesen Schwachstellen finden Sie in der National Vulnerability Database (NVD) oder in spezifischen Hinweisen auf Plattformen wie GitHub und mvnrepository.