Ö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

AuswirkungDetails
IntegritätUmfang: Integrität

Ausführungslogik ändern - Objekte können in unerwarteten Zuständen existieren, die Anwendungsannahmen über Objektinitialisierung und Konsistenz verletzen.
ZugriffskontrolleUmfang: 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

  1. MITRE Corporation. "CWE-491: Public cloneable() Method Without Final ('Object Hijack')." https://cwe.mitre.org/data/definitions/491.html
  2. CERT Oracle Secure Coding Standard for Java. "OBJ07-J. Sensitive classes must not let themselves be copied."
  3. Oracle. "Secure Coding Guidelines for Java SE."