Öffentliche cloneable()-Methode ohne Final ('Object Hijack')
Beschreibung
Öffentliche cloneable()-Methode ohne Final ist eine Schwachstelle, bei der eine Klasse eine cloneable()-Methode implementiert, die nicht als final deklariert ist. Dieser Designfehler ermöglicht Unterklassen, die clone-Methode zu überschreiben, was potenziell Objektinstanziierung ohne Aufruf des ordnungsgemäßen Konstruktors ermöglicht. Angreifer können dies nutzen, um Objekte zu erstellen, die Initialisierungslogik, Validierung oder Sicherheitsprüfungen umgehen, die normalerweise im Konstruktor stattfinden würden, wodurch Objekte in unerwarteten oder kompromittierten Zuständen verbleiben.
Risiko
Nicht-finale clone-Methoden ermöglichen "Object Hijacking"-Angriffe, bei denen Angreifer bösartige Unterklassen erstellen, die die clone-Methode überschreiben, um Objekte in ungültigen Zuständen oder mit manipulierten internen Daten zurückzugeben. Dies umgeht konstruktorbasierte Sicherheitsprüfungen, Eingabevalidierung und ordnungsgemäße Initialisierungssequenzen. In sicherheitskritischen Anwendungen kann dies die Erstellung privilegierter Objekte ohne ordnungsgemäße Autorisierung, Manipulation von Authentifizierungstoken oder Umgehung von Zugriffskontrollen ermöglichen. Das Risiko ist besonders schwerwiegend für Klassen, die Sicherheitsanmeldedaten, Finanztransaktionen oder Autorisierungskontexte repräsentieren.
Lösung
Deklarieren Sie die clone()-Methode als final, um zu verhindern, dass Unterklassen sie überschreiben. Alternativ machen Sie die Klasse selbst final, wenn angemessen. Wenn Sie clone() implementieren, stellen Sie sicher, dass es alle sicherheitssensitiven Felder ordnungsgemäß initialisiert und Objektinvarianten aufrechterhält. Erwägen Sie die Verwendung von Kopierkonstruktoren oder Factory-Methoden als sicherere Alternativen zum Klonen. Wenn Klonen unterstützt werden muss, implementieren Sie defensives Kopieren und validieren Sie den Zustand des geklonten Objekts. Verwenden Sie statische Analysetools, um Klassen mit nicht-finalen clone-Methoden zu identifizieren.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Umfang: Integrität Ausführungslogik ändern - Objekte können in unerwarteten Zuständen existieren, die Anwendungsannahmen über Objektinitialisierung und Konsistenz verletzen. |
| Zugriffskontrolle | Umfang: Zugriffskontrolle Schutzmechanismus umgehen - Angreifer können konstruktorbasierte Sicherheitsprüfungen umgehen, indem sie Objekte durch Klonen instanziieren. |
Beispielcode
Anfälliger Code
// Anfällig: Klonbare Klasse ohne finale clone-Methode
public class VulnerableBankAccount implements Cloneable {
private String accountNumber;
private double balance;
private boolean verified;
public VulnerableBankAccount(String accountNumber) {
// Konstruktor führt Sicherheitsvalidierung durch
if (!validateAccountNumber(accountNumber)) {
throw new SecurityException("Ungültige Kontonummer");
}
this.accountNumber = accountNumber;
this.balance = 0.0;
this.verified = false;
}
// Anfällig: Nicht als final deklariert
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
private boolean validateAccountNumber(String number) {
// Validierungslogik
return number.matches("\\d{10}");
}
}
// Angreifer kann bösartige Unterklasse erstellen
public class MaliciousBankAccount extends VulnerableBankAccount {
public MaliciousBankAccount(String accountNumber) {
super(accountNumber);
}
// Clone überschreiben um Validierung zu umgehen
@Override
public Object clone() throws CloneNotSupportedException {
MaliciousBankAccount account = new MaliciousBankAccount("0000000000");
// Angreifer kann internen Zustand manipulieren
// mit Reflection oder anderen Techniken
return account;
}
}
// Anfällig: Authentifizierungstoken ohne finales clone
public class VulnerableAuthToken implements Cloneable {
private String userId;
private String token;
private Set<String> permissions;
private Date expiration;
public VulnerableAuthToken(String userId) {
// Konstruktor verifiziert dass Benutzer existiert
this.userId = verifyAndGetUser(userId);
this.token = generateSecureToken();
this.permissions = loadUserPermissions(userId);
this.expiration = calculateExpiration();
}
// Anfällig: Nicht-finale clone-Methode
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
private String verifyAndGetUser(String userId) {
// Benutzer in Datenbank verifizieren
return userId;
}
private String generateSecureToken() {
// Kryptographisch sicheren Token generieren
return UUID.randomUUID().toString();
}
private Set<String> loadUserPermissions(String userId) {
// Aus Datenbank laden
return new HashSet<>();
}
}
// Angreifer nutzt nicht-finales clone aus
public class AuthTokenExploit extends VulnerableAuthToken {
public AuthTokenExploit(String userId) {
super(userId);
}
@Override
protected Object clone() throws CloneNotSupportedException {
// Authentifizierung vollständig umgehen
// Token mit erhöhten Privilegien zurückgeben
AuthTokenExploit fakeToken = (AuthTokenExploit) super.clone();
// Reflection verwenden um private Felder zu modifizieren
Field permField = AuthTokenExploit.class
.getSuperclass()
.getDeclaredField("permissions");
permField.setAccessible(true);
Set<String> adminPerms = new HashSet<>();
adminPerms.add("ADMIN");
adminPerms.add("READ_ALL");
adminPerms.add("WRITE_ALL");
permField.set(fakeToken, adminPerms);
return fakeToken;
}
}
Korrigierter Code
// Korrigiert: Finale clone-Methode verhindert Überschreibung
public class SecureBankAccount implements Cloneable {
private final String accountNumber;
private double balance;
private boolean verified;
public SecureBankAccount(String accountNumber) {
if (!validateAccountNumber(accountNumber)) {
throw new SecurityException("Ungültige Kontonummer");
}
this.accountNumber = accountNumber;
this.balance = 0.0;
this.verified = false;
}
// Korrigiert: Als final deklariert - kann nicht überschrieben werden
@Override
public final Object clone() throws CloneNotSupportedException {
SecureBankAccount cloned = (SecureBankAccount) super.clone();
// Sicherstellen dass geklontes Objekt in gültigem Zustand ist
cloned.verified = false; // Erneute Verifizierung erfordern
return cloned;
}
private boolean validateAccountNumber(String number) {
return number.matches("\\d{10}");
}
}
// Korrigiert: Finale Klasse verhindert Unterklassenbildung vollständig
public final class SecureAuthToken implements Cloneable {
private final String userId;
private final String token;
private final Set<String> permissions;
private final Date expiration;
public SecureAuthToken(String userId) {
this.userId = verifyAndGetUser(userId);
this.token = generateSecureToken();
this.permissions = Collections.unmodifiableSet(
loadUserPermissions(userId)
);
this.expiration = calculateExpiration();
}
// Korrigiert: Finale Klasse + finale Methode = keine Überschreibung möglich
@Override
public final Object clone() throws CloneNotSupportedException {
SecureAuthToken cloned = (SecureAuthToken) super.clone();
// Klon ist sicher weil alle Felder final/unveränderlich sind
return cloned;
}
private String verifyAndGetUser(String userId) {
User user = userRepository.findById(userId);
if (user == null) {
throw new SecurityException("Benutzer nicht gefunden");
}
return userId;
}
private String generateSecureToken() {
return TokenGenerator.generateSecure();
}
private Set<String> loadUserPermissions(String userId) {
return permissionService.getPermissions(userId);
}
private Date calculateExpiration() {
return Date.from(Instant.now().plusSeconds(3600));
}
}
// Korrigiert: Kopierkonstruktor anstelle von clone verwenden
public final class SecureCredential {
private final String username;
private final byte[] hashedPassword;
private final Set<String> roles;
public SecureCredential(String username, byte[] hashedPassword,
Set<String> roles) {
this.username = Objects.requireNonNull(username);
this.hashedPassword = hashedPassword.clone(); // Defensive Kopie
this.roles = Collections.unmodifiableSet(new HashSet<>(roles));
}
// Korrigiert: Kopierkonstruktor anstelle von clone
public SecureCredential(SecureCredential original) {
this.username = original.username;
this.hashedPassword = original.hashedPassword.clone();
this.roles = original.roles; // Bereits unveränderlich
}
// Korrigiert: Factory-Methode zum Erstellen von Kopien
public static SecureCredential copyOf(SecureCredential original) {
return new SecureCredential(original);
}
// Keine clone()-Methode - stattdessen Kopierkonstruktor verwenden
}
// Korrigiert: Wenn clone benötigt wird, defensiven Ansatz verwenden
public class DefensiveCloneExample implements Cloneable {
private String[] sensitiveData;
private Map<String, Object> config;
public DefensiveCloneExample() {
this.sensitiveData = new String[10];
this.config = new HashMap<>();
}
@Override
public final Object clone() throws CloneNotSupportedException {
DefensiveCloneExample cloned =
(DefensiveCloneExample) super.clone();
// Korrigiert: Tiefe Kopie veränderlicher Felder
cloned.sensitiveData = this.sensitiveData.clone();
cloned.config = new HashMap<>(this.config);
// Korrigiert: Geklonten Zustand validieren
validateState(cloned);
return cloned;
}
private void validateState(DefensiveCloneExample obj) {
if (obj.sensitiveData == null || obj.config == null) {
throw new IllegalStateException("Ungültiger Klon-Zustand");
}
}
}
CVE-Beispiele
Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgelistet. Das Schwachstellenmuster ist jedoch dokumentiert in:
- CERT Oracle Secure Coding Standard for Java (OBJ07-J)
- Seven Pernicious Kingdoms Taxonomie
Referenzen
- MITRE Corporation. "CWE-491: Public cloneable() Method Without Final ('Object Hijack')." https://cwe.mitre.org/data/definitions/491.html
- CERT Oracle Secure Coding Standard for Java. "OBJ07-J. Sensitive classes must not let themselves be copied."
- Oracle. "Secure Coding Guidelines for Java SE."