finalize()-Methode als public deklariert
Beschreibung
finalize()-Methode als public deklariert ist eine Sicherheitsschwachstelle in Java, bei der eine Klasse ihre finalize()-Methode mit public-Zugriff statt dem angemessenen protected-Zugriff deklariert. Die finalize()-Methode sollte nur vom Garbage Collector während der Objektrückgewinnung oder durch super.finalize()-Aufrufe innerhalb einer überschreibenden finalize-Implementierung aufgerufen werden. Ein Produkt sollte finalize() niemals explizit außerhalb dieser Kontexte aufrufen. In Mobile-Code- oder Untrusted-Code-Umgebungen ermöglicht das Deklarieren von finalize() als public bösartigem Code, den Finalizer nach Belieben aufzurufen und potenziell vorzeitige Ressourcenbereinigung, Use-after-free-Bedingungen oder andere sicherheitssensible Operationen zu angreiferkontrollierten Zeitpunkten auszulösen.
Risiko
Eine öffentliche finalize()-Methode erzeugt erhebliche Sicherheitsrisiken. Angreifer können finalize() auf Objekten aufrufen, bevor sie vom Garbage Collector erfasst werden, was vorzeitige Bereinigung von noch verwendeten Ressourcen verursacht. Dies kann zu Use-after-free-Szenarien führen, bei denen Ressourcen gültig erscheinen, aber freigegeben wurden. In sicherheitssensiblen Kontexten können explizite Finalisierungsaufrufe Sperren freigeben, Verbindungen schließen oder Sicherheitstoken zu ungeeigneten Zeitpunkten löschen. Mehrfache Aufrufe von finalize() werden möglich, da der Angreifer den Aufruf kontrolliert. Die Methode kann während der Objektkonstruktion durch Reflection aufgerufen werden und die ordnungsgemäße Initialisierung storen. Jede Bereinigungslogik in finalize() wird ausnutzbar.
Lösung
Deklarieren Sie finalize() immer mit protected-Zugriff, niemals public. Besser noch, vermeiden Sie die Verwendung von finalize() gänzlich - es ist seit Java 9 veraltet. Verwenden Sie stattdessen try-with-resources und AutoCloseable für Ressourcenmanagement. Wenn finalize() verwendet werden muss, machen Sie es protected und stellen Sie sicher, dass es mehrfache Aufrufe sicher behandelt. Erwägen Sie die Verwendung von java.lang.ref.Cleaner (Java 9+) für die Bereinigung nativer Ressourcen. Platzieren Sie niemals sicherheitskritische Operationen in finalize(), da der Ausführungszeitpunkt selbst ohne bösartigen Aufruf unvorhersehbar ist.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Vorzeitige Finalisierung kann sensible Daten exponieren, die hätten gelöscht werden sollen. |
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Angreiferkontrollierte Finalisierung kann Objektzustand korrumpieren oder Ressourcen vorzeitig freigeben. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz, Beenden oder Neustart - Das Finalisieren von noch verwendeten Objekten kann Abstürze oder Ressourcenerschöpfung verursachen. |
Beispielcode
Verwundbarer Code
// Verwundbar: finalize() als public deklariert
import java.applet.Applet;
public final class VulnerableApplet extends Applet {
private Connection dbConnection;
private byte[] sensitiveData;
@Override
public void init() {
dbConnection = openConnection();
sensitiveData = loadSensitiveData();
}
// Verwundbar: Öffentliches finalize kann von bösartigem Code aufgerufen werden
public void finalize() {
// Ressourcen bereinigen
if (dbConnection != null) {
dbConnection.close();
dbConnection = null;
}
// Sensible Daten löschen
if (sensitiveData != null) {
Arrays.fill(sensitiveData, (byte) 0);
sensitiveData = null;
}
}
public byte[] getSensitiveData() {
return sensitiveData; // Kann null nach bösartigem finalize() zurückgeben
}
public void useConnection() {
dbConnection.query("SELECT * FROM data"); // NPE nach finalize()
}
}
// Angreifer kann ausnutzen:
public class Attacker {
public void exploit(VulnerableApplet applet) {
// Vorzeitige Ressourcenbereinigung erzwingen
applet.finalize();
// Jetzt hat das Applet seine Ressourcen freigegeben
// kann aber noch anderswo referenziert und verwendet werden
// Dies wird fehlschlagen oder korrumpierten Zustand exponieren
applet.useConnection(); // NullPointerException!
applet.getSensitiveData(); // Gibt null oder veraltete Daten zurück
}
}
// Verwundbar: Sicherheitssensibles finalize
public class VulnerableSecurityToken {
private String token;
private boolean isValid = true;
public VulnerableSecurityToken(String token) {
this.token = token;
}
// Verwundbar: Öffentlicher Zugriff ermöglicht erzwungene Invalidierung
public void finalize() {
invalidate();
}
private void invalidate() {
token = null;
isValid = false;
}
public boolean isValid() {
return isValid && token != null;
}
public String getToken() {
if (!isValid) {
throw new SecurityException("Token invalidiert");
}
return token;
}
}
// Angriffsszenario
public class TokenAttacker {
public void invalidateOtherUsersToken(VulnerableSecurityToken token) {
// Bösartiger Code kann jedes Token invalidieren, das er referenzieren kann
token.finalize();
// Token ist jetzt ungültig obwohl Benutzer sich nicht abgemeldet hat
// token.isValid() gibt false zurück
}
}
// Verwundbar: Ressourcenmanager mit öffentlichem finalize
public class VulnerableResourceManager {
private FileHandle fileHandle;
private Lock resourceLock;
// Verwundbar: Öffentliches finalize gibt Ressourcen frei
public void finalize() {
if (resourceLock != null && resourceLock.isHeldByCurrentThread()) {
resourceLock.unlock();
}
if (fileHandle != null) {
fileHandle.release();
fileHandle = null;
}
}
public void processFile() {
resourceLock.lock();
try {
// Angreifer könnte finalize() von einem anderen Thread hier aufrufen!
// Dies würde Sperre und Dateihandle freigeben während noch in Verwendung
fileHandle.read();
} finally {
resourceLock.unlock();
}
}
}
Lösungscode
// Behoben: finalize() als protected deklariert (obwohl veraltet)
import java.applet.Applet;
public final class SecureApplet extends Applet {
private Connection dbConnection;
private byte[] sensitiveData;
@Override
public void init() {
dbConnection = openConnection();
sensitiveData = loadSensitiveData();
}
// Behoben: Geschützter Zugriff - nur GC oder Unterklasse kann aufrufen
@Override
protected void finalize() throws Throwable {
try {
cleanup();
} finally {
super.finalize();
}
}
private void cleanup() {
if (dbConnection != null) {
dbConnection.close();
dbConnection = null;
}
if (sensitiveData != null) {
Arrays.fill(sensitiveData, (byte) 0);
sensitiveData = null;
}
}
// Öffentliche cleanup-Methode für explizite Ressourcenfreigabe
public void close() {
cleanup();
}
}
// Besser: AutoCloseable implementieren, finalize gänzlich vermeiden
public class SecureResource implements AutoCloseable {
private Connection dbConnection;
private byte[] sensitiveData;
private boolean closed = false;
public SecureResource() {
dbConnection = openConnection();
sensitiveData = loadSensitiveData();
}
// Öffentliches close() ist der ordnungsgemäße Weg Ressourcen freizugeben
@Override
public synchronized void close() {
if (closed) return;
if (dbConnection != null) {
dbConnection.close();
dbConnection = null;
}
if (sensitiveData != null) {
Arrays.fill(sensitiveData, (byte) 0);
sensitiveData = null;
}
closed = true;
}
public byte[] getSensitiveData() {
checkNotClosed();
return sensitiveData.clone(); // Kopie zurückgeben
}
private void checkNotClosed() {
if (closed) {
throw new IllegalStateException("Ressource wurde geschlossen");
}
}
}
// Verwendung mit try-with-resources
public class SecureUsage {
public void process() {
try (SecureResource resource = new SecureResource()) {
byte[] data = resource.getSensitiveData();
// Daten verwenden
}
// Automatisch geschlossen
}
}
// Behoben: Sicherheitstoken ohne finalize
public class SecureSecurityToken implements AutoCloseable {
private volatile String token;
private volatile boolean isValid = true;
private final Object lock = new Object();
public SecureSecurityToken(String token) {
this.token = token;
}
// Kein öffentliches finalize - stattdessen close() verwenden
@Override
public void close() {
synchronized (lock) {
token = null;
isValid = false;
}
}
public boolean isValid() {
synchronized (lock) {
return isValid && token != null;
}
}
public String getToken() {
synchronized (lock) {
if (!isValid) {
throw new SecurityException("Token invalidiert");
}
return token;
}
}
}
// Behoben: Cleaner für native Ressourcen verwenden (Java 9+)
import java.lang.ref.Cleaner;
public class SecureNativeResource implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private final long nativeHandle;
private final Cleaner.Cleanable cleanable;
public SecureNativeResource() {
this.nativeHandle = allocateNative();
// Bereinigungsaktion registrieren - läuft wenn Objekt phantom erreichbar wird
final long handle = this.nativeHandle;
this.cleanable = cleaner.register(this, () -> {
freeNative(handle);
});
}
@Override
public void close() {
// Explizite Bereinigung
cleanable.clean();
}
private static native long allocateNative();
private static native void freeNative(long handle);
// Kein finalize() nötig - Cleaner behandelt es sicher
}
// Behoben: Thread-sicherer Ressourcenmanager
public class SecureResourceManager implements AutoCloseable {
private FileHandle fileHandle;
private final ReentrantLock resourceLock = new ReentrantLock();
private volatile boolean closed = false;
@Override
public void close() {
resourceLock.lock();
try {
if (!closed) {
if (fileHandle != null) {
fileHandle.release();
fileHandle = null;
}
closed = true;
}
} finally {
resourceLock.unlock();
}
}
public void processFile() {
resourceLock.lock();
try {
if (closed) {
throw new IllegalStateException("Manager ist geschlossen");
}
// Sicher - Sperre schützt vor gleichzeitigem close
fileHandle.read();
} finally {
resourceLock.unlock();
}
}
}
// Hinweis: Wenn Sie finalize verwenden müssen, ist dies das minimale sichere Muster
public class MinimalFinalize {
private Resource resource;
// Protected, behandelt mehrfache Aufrufe, ruft super auf
@Override
protected void finalize() throws Throwable {
try {
if (resource != null) {
resource.release();
resource = null;
}
} finally {
super.finalize();
}
}
}
CVE-Beispiele
Keine spezifischen CVEs werden dieser CWE direkt zugeordnet, obwohl das Schwachstellenmuster in Mobile-Code- und Applet-Sicherheitskontexten erkannt ist.
Referenzen
- MITRE Corporation. "CWE-583: finalize() Method Declared Public." https://cwe.mitre.org/data/definitions/583.html
- Joshua Bloch. "Effective Java" - Item 8: Avoid finalizers and cleaners.
- Oracle. "Object.finalize() - Deprecated since Java 9."