Verwendung innerer Klassen mit sensiblen Daten

Beschreibung

Verwendung innerer Klassen mit sensiblen Daten ist eine Schwachstelle, bei der innere Klassen verwendet werden, um sensible Informationen zu halten oder darauf zuzugreifen. Javas Compiler transformiert innere Klassen in separate Peer-Klassen mit Paketzugriffsebene, was potenziell Code und Daten offenlegt, die der Programmierer privat halten wollte. Wenn innere Klassen auf private Felder in ihrer umschließenden Klasse zugreifen, kann der Compiler diese privaten Felder in geschützte oder paket-private Felder umwandeln, was Sicherheitslücken erzeugt. Dies geschieht, weil Java-Bytecode kein Konzept von inneren Klassen hat — sie sind rein ein Quellcode-Konstrukt.

Risiko

Innere Klassen erzeugen versteckte Sicherheitsrisiken, weil ihre kompilierte Form sich erheblich von ihrer Quelldarstellung unterscheidet. Private Daten, auf die innere Klassen zugreifen, werden für jede Klasse im selben Paket zugänglich. Angreifer können Klassen im selben Paket erstellen, um auf vermeintlich private Felder und Methoden zuzugreifen, die durch die Kompilierung innerer Klassen offengelegt wurden. Sensible Daten wie Anmeldedaten, kryptographische Schlüssel oder persönliche Informationen, die in inneren Klassen gespeichert oder von ihnen aus umschließenden Klassen abgerufen werden, können von bösartigem Code gelesen oder modifiziert werden. Das Risiko wird verstärkt, weil Entwickler oft annehmen, dass Zugriffsmodifikatoren innerer Klassen Sicherheitsgarantien bieten, die sie tatsächlich nicht bieten.

Lösung

Vermeiden Sie die Verwendung innerer Klassen zum Speichern oder Zugreifen auf sensible Daten. Verwenden Sie versiegelte Klassen wo verfügbar, um die Kapselung zu schützen. Machen Sie innere Klassen statisch, wenn sie keinen Zugriff auf Instanzmitglieder der umschließenden Klasse benötigen, da statische innere Klassen vorhersehbarere Zugriffssemantik haben. Erwägen Sie die Verwendung lokaler innerer Klassen, die innerhalb von Methoden definiert sind, wenn die Klasse nur in einem bestimmten Bereich benötigt wird. Für wirklich sensible Operationen verwenden Sie separate Klassen mit ordnungsgemäßer Kapselung anstatt sich auf Zugriffsbeschränkungen innerer Klassen zu verlassen. Nehmen Sie niemals an, dass innere Klassen Sicherheitsisolation bieten.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitUmfang: Vertraulichkeit

Anwendungsdaten lesen - Die Vertraulichkeit von Daten innerer Klassen kann überwunden werden, was Angreifern ermöglicht, sensible Anwendungsdaten durch den offengelegten Paketzugriff zu lesen.
IntegritätUmfang: Integrität

Anwendungsdaten modifizieren - Angreifer können Daten in inneren Klassen oder Daten, auf die innere Klassen aus äußeren Klassen zugreifen, durch die offengelegten Zugriffspfade modifizieren.

Beispielcode

Anfälliger Code

// Anfällig: Innere Klasse greift auf private sensible Daten zu
public class VulnerableBankingSystem {
    // Privates Feld - aber Zugriff durch innere Klasse macht es paket-zugänglich
    private String masterEncryptionKey = "super-geheimer-schlüssel-12345";
    private Map<String, Double> accountBalances = new HashMap<>();

    // Anfällig: Nicht-statische innere Klasse hat Zugriff auf alle privaten Mitglieder
    public class TransactionProcessor {
        public void processTransaction(String accountId, double amount) {
            // Innere Klasse kann auf private Felder der äußeren Klasse zugreifen
            // Nach Kompilierung wird masterEncryptionKey paket-zugänglich
            String encrypted = encrypt(amount, masterEncryptionKey);

            // accountBalances ist ebenfalls offengelegt
            double currentBalance = accountBalances.get(accountId);
            accountBalances.put(accountId, currentBalance + amount);
        }

        private String encrypt(double amount, String key) {
            // Verschlüsselungslogik
            return "" + amount;
        }
    }
}

// Angreifer im selben Paket kann offengelegte Felder ausnutzen
package com.banking; // Gleiches Paket wie VulnerableBankingSystem

public class Attacker {
    public void exploit() {
        VulnerableBankingSystem system = new VulnerableBankingSystem();

        // Aufgrund der Kompilierung innerer Klassen sind private Felder jetzt zugänglich
        // durch Reflection oder synthetische Accessor-Methoden
        try {
            Field keyField = VulnerableBankingSystem.class
                .getDeclaredField("masterEncryptionKey");
            keyField.setAccessible(true);  // Durch Zugriff innerer Klasse erleichtert
            String key = (String) keyField.get(system);
            System.out.println("Gestohlener Schlüssel: " + key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// Anfällig: Applet mit innerer Klasse, die sensible Daten offenlegt
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

public class VulnerableApplet extends Applet {
    // Private sensible Daten
    private String userPassword;
    private String sessionToken;

    // Anfällig: Anonyme innere Klasse für Ereignisbehandlung
    // greift auf private Felder zu, legt sie auf Bytecode-Ebene offen
    public void init() {
        Button submitButton = new Button("Absenden");

        // Anfällig: Anonyme innere Klasse
        submitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Zugriff auf privates Feld legt es im Bytecode offen
                authenticateUser(userPassword);
                // sessionToken wird ebenfalls zugänglich
                startSession(sessionToken);
            }
        });

        add(submitButton);
    }

    private void authenticateUser(String password) {
        // Authentifizierungslogik
    }

    private void startSession(String token) {
        // Session-Logik
    }
}

// Anfällig: Klasse mit sensibler innerer Klasse Daten
public class VulnerableUserManager {

    // Anfällig: Innere Klasse hält sensible Benutzerdaten
    private class UserCredentials {
        String username;
        String password;
        String ssn;
        String creditCardNumber;

        UserCredentials(String user, String pass, String ssn, String cc) {
            this.username = user;
            this.password = pass;
            this.ssn = ssn;
            this.creditCardNumber = cc;
        }
    }

    private UserCredentials currentUser;

    public void login(String user, String pass, String ssn, String cc) {
        // Anfällig: Sensible Daten in innerer Klasse
        // ist zugänglicher als es im Quellcode erscheint
        currentUser = new UserCredentials(user, pass, ssn, cc);
    }
}

Korrigierter Code

// Korrigiert: Statische innere Klasse oder separate Klasse verwenden
public class SecureBankingSystem {
    private final String masterEncryptionKey;
    private final Map<String, Double> accountBalances;

    public SecureBankingSystem() {
        this.masterEncryptionKey = loadKeyFromSecureStorage();
        this.accountBalances = new ConcurrentHashMap<>();
    }

    // Korrigiert: Statische innere Klasse hat keinen impliziten Zugriff auf äußere Klasse
    public static class TransactionProcessor {
        private final EncryptionService encryptionService;
        private final AccountRepository accountRepository;

        public TransactionProcessor(EncryptionService encryption,
                                   AccountRepository accounts) {
            this.encryptionService = encryption;
            this.accountRepository = accounts;
        }

        public void processTransaction(String accountId, double amount) {
            // Korrigiert: Verwendet injizierte Abhängigkeiten, nicht Felder der äußeren Klasse
            String encrypted = encryptionService.encrypt(amount);
            accountRepository.updateBalance(accountId, amount);
        }
    }

    // Korrigiert: Factory-Methode bietet kontrollierten Zugriff
    public TransactionProcessor createProcessor() {
        return new TransactionProcessor(
            new EncryptionService(masterEncryptionKey),
            new AccountRepository(accountBalances)
        );
    }

    private String loadKeyFromSecureStorage() {
        // Aus HSM oder sicherem Tresor laden
        return SecureKeyVault.getKey("master-key");
    }
}
// Korrigiert: Lokale innere Klasse für begrenzten Bereich verwenden
public class SecureApplet extends Applet {
    private char[] userPassword;  // char[] für Passwörter, nicht String

    public void init() {
        Button submitButton = new Button("Absenden");

        // Korrigiert: Methode behandelt Authentifizierung direkt
        submitButton.addActionListener(this::handleSubmit);

        add(submitButton);
    }

    // Korrigiert: ActionListener direkt auf der Klasse implementieren
    private void handleSubmit(ActionEvent e) {
        try {
            // Passwort lokal verarbeiten
            authenticateUser(userPassword);
        } finally {
            // Sensible Daten nach Verwendung löschen
            Arrays.fill(userPassword, '\0');
        }
    }

    private void authenticateUser(char[] password) {
        // Authentifizierungslogik
    }
}

// Alternative: Schnittstelle direkt implementieren
public class SecureApplet2 extends Applet implements ActionListener {
    private char[] userPassword;

    public void init() {
        Button submitButton = new Button("Absenden");
        submitButton.addActionListener(this);
        add(submitButton);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // Korrigiert: Keine innere Klasse, direkte Implementierung
        authenticateUser(userPassword);
    }

    private void authenticateUser(char[] password) {
        // Authentifizierungslogik
    }
}
// Korrigiert: Separate Klasse für sensible Daten mit ordnungsgemäßer Kapselung
public final class SecureUserManager {

    // Korrigiert: Anmeldedaten in separater, versiegelter Klasse gespeichert
    private SecureCredentials currentUser;

    public void login(String user, char[] pass) {
        // Korrigiert: Ordnungsgemäß gekapseltes Anmeldedatenobjekt verwenden
        currentUser = SecureCredentials.create(user, pass);
        // Passwort-Array nach Verwendung löschen
        Arrays.fill(pass, '\0');
    }

    public boolean isAuthenticated() {
        return currentUser != null && currentUser.isValid();
    }
}

// Korrigiert: Separate finale Klasse für Anmeldedaten (keine innere Klasse)
public final class SecureCredentials {
    private final String username;
    private final byte[] hashedPassword;
    private final Instant createdAt;

    private SecureCredentials(String username, byte[] hashedPassword) {
        this.username = username;
        this.hashedPassword = hashedPassword;
        this.createdAt = Instant.now();
    }

    public static SecureCredentials create(String username, char[] password) {
        byte[] hashed = hashPassword(password);
        // Ursprüngliches Passwort löschen
        Arrays.fill(password, '\0');
        return new SecureCredentials(username, hashed);
    }

    public boolean isValid() {
        // Prüfen ob Anmeldedaten nicht abgelaufen sind
        return createdAt.plusSeconds(3600).isAfter(Instant.now());
    }

    public boolean verifyPassword(char[] password) {
        byte[] hashed = hashPassword(password);
        boolean matches = MessageDigest.isEqual(hashedPassword, hashed);
        Arrays.fill(hashed, (byte) 0);
        return matches;
    }

    private static byte[] hashPassword(char[] password) {
        // Ordnungsgemäßes Passwort-Hashing verwenden (bcrypt, Argon2, etc.)
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] bytes = new String(password).getBytes(StandardCharsets.UTF_8);
            byte[] hash = md.digest(bytes);
            Arrays.fill(bytes, (byte) 0);
            return hash;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
}

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgelistet. Das Schwachstellenmuster ist jedoch dokumentiert in:

  • Seven Pernicious Kingdoms Taxonomie
  • Java-Bytecode-Sicherheitsforschung

Referenzen

  1. MITRE Corporation. "CWE-492: Use of Inner Class Containing Sensitive Data." https://cwe.mitre.org/data/definitions/492.html
  2. Oracle. "Secure Coding Guidelines for Java SE."
  3. McGraw, Gary und Felten, Edward. "Securing Java."