Unzureichende Kapselung

Beschreibung

Unzureichende Kapselung tritt auf, wenn ein Softwareprodukt es versäumt, interne Datenrepräsentation und Implementierungsdetails angemessen vor externen Komponenten zu verbergen. Dies ermöglicht externem Code den direkten Zugriff auf oder die Modifikation interner Daten, wodurch möglicherweise Validierung, Sicherheitskontrollen oder beabsichtigte Schnittstellen umgangen werden. Schlechte Kapselung verletzt das Prinzip des Information Hiding und erzeugt enge Kopplung zwischen Komponenten, was das System schwerer zu warten und zu sichern macht.

Risiko

Unzureichende Kapselung hat direkte Sicherheitsimplikationen. Exponierte interne Daten können von Angreifern modifiziert werden und Validierungs- oder Autorisierungsprüfungen umgehen. Sicherheitssensible Felder (Passwörter, Tokens, interner Zustand) können direkt zugänglich sein. Geschäftslogik kann umgangen werden, wenn interne Methoden öffentlich exponiert werden. Die enge Kopplung macht es schwierig, später Sicherheitskontrollen hinzuzufügen, ohne externe Abhängigkeiten zu brechen. Angreifer können exponierte Implementierungsdetails ausnutzen, um gezieltere Angriffe zu erstellen. Schlechte Kapselung erschwert auch Sicherheitsaudits, da die tatsächlichen Zugriffsmuster unklar sind.

Lösung

Wenden Sie ordnungsgemäße Zugriffsmodifikatoren (private, protected) an, um den Zugriff auf interne Daten einzuschränken. Verwenden Sie Getter/Setter-Methoden mit Validierung für kontrollierten Zugriff. Verbergen Sie Implementierungsdetails hinter Interfaces. Wenden Sie das Prinzip der minimalen Rechte auf Klassenmitglieder an. Machen Sie Klassen wo möglich unveränderlich. Verwenden Sie Kapselung, um Invarianten und Validierungsregeln durchzusetzen. Vermeiden Sie es, veränderbare interne Collections direkt zurückzugeben - geben Sie Kopien oder unveränderliche Ansichten zurück. Führen Sie Code-Reviews durch, die sich auf Zugriffskontrolle und Kapselung konzentrieren. Verwenden Sie statische Analysetools, um Kapselungsverstöße zu erkennen.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Angreifer können auf Daten oder Methoden zugreifen, die nicht öffentlich verfügbar sein sollten.
IntegritätBereich: Integrität

Anwendungsdaten modifizieren - Direkter Zugriff auf interne Daten umgeht Validierung und Sicherheitskontrollen.
AndereBereich: Ändere

Reduzierte Wartbarkeit - Schlechte Kapselung erzeugt enge Kopplung, die Sicherheitsverbesserungen erschwert.

Beispielcode

Anfälliger Code

// Anfällig: Öffentliche Member-Variablen, die sensible Daten exponieren
class VulnerableUser {
public:
    // Anfällig: Alle Felder public - keine Kapselung
    std::string username;
    std::string password;      // KRITISCH: Passwort direkt zugänglich!
    std::string email;
    std::string authToken;     // KRITISCH: Token direkt zugänglich!
    bool isAdmin;              // Kann direkt modifiziert werden!
    int failedLoginAttempts;
    std::vector<std::string> permissions;  // Veränderliche Collection exponiert

    void login(const std::string& pwd) {
        if (pwd == password) {  // Direkter Vergleich - kein Hashing!
            // Token generieren
        }
    }
};

// Angriffsszenario:
// VulnerableUser user;
// user.password = "bekannt";     // Authentifizierung umgehen
// user.isAdmin = true;           // Rechteausweitung
// user.failedLoginAttempts = 0;  // Sperrung zurücksetzen
// Anfällig: Veränderlichen internen Zustand exponieren
public class VulnerableAccount {

    // Anfällig: Öffentliche Felder
    public String accountNumber;
    public double balance;  // Kann direkt modifiziert werden!
    public List<Transaction> transactions = new ArrayList<>();

    // Anfällig: Gibt veränderliche interne Collection zurück
    public List<Transaction> getTransactions() {
        return transactions;  // Externer Code kann modifizieren!
    }

    // Anfällig: Keine Validierung bei Abhebung
    public void setBalance(double balance) {
        this.balance = balance;  // Keine Validierung - kann negativ setzen!
    }
}

// Angriffsszenario:
// account.balance = 1000000;  // Kontostand direkt setzen
// account.getTransactions().clear();  // Audit-Trail löschen
# Anfällig: Keine Kapselung in Python
class VulnerablePaymentProcessor:
    def __init__(self):
        # Anfällig: Alle Attribute direkt zugänglich
        self.api_key = "sk_live_secret123"  # Exponiert!
        self.pending_charges = []
        self.processed_amount = 0
        self.config = {
            'max_amount': 10000,
            'retry_count': 3
        }

    def process_payment(self, amount):
        if amount > self.config['max_amount']:
            return False
        self.processed_amount += amount
        return True


# Angriffsszenario:
# processor = VulnerablePaymentProcessor()
# print(processor.api_key)  # API-Schlüssel stehlen
# processor.config['max_amount'] = 999999999  # Limit umgehen
# processor.processed_amount = 0  # Audit-Summe zurücksetzen

Korrigierter Code

// Korrigiert: Ordnungsgemäße Kapselung mit privaten Membern
class FixedUser {
private:
    std::string username_;
    std::string passwordHash_;  // Hash speichern, nicht Klartext-Passwort
    std::string email_;
    std::string authToken_;
    bool isAdmin_;
    int failedLoginAttempts_;
    std::vector<std::string> permissions_;

public:
    // Korrigiert: Konstruktor validiert und initialisiert
    FixedUser(const std::string& username, const std::string& email)
        : username_(username)
        , email_(email)
        , isAdmin_(false)
        , failedLoginAttempts_(0) {
        // Eingaben validieren
        if (username.empty()) {
            throw std::invalid_argument("Benutzername darf nicht leer sein");
        }
    }

    // Korrigiert: Getter gibt Kopie sensibler Daten zurück
    std::string getUsername() const { return username_; }
    std::string getEmail() const { return email_; }
    bool isAdmin() const { return isAdmin_; }

    // Korrigiert: Kein direkter Setter für isAdmin - muss kontrollierte Methode verwenden
    void promoteToAdmin(const FixedUser& promoter) {
        if (!promoter.isAdmin()) {
            throw std::runtime_error("Nur Admins können Benutzer befördern");
        }
        isAdmin_ = true;
    }

    // Korrigiert: Passwort wird gehasht, nie exponiert
    void setPassword(const std::string& password) {
        validatePasswordStrength(password);
        passwordHash_ = hashPassword(password);
    }

    bool verifyPassword(const std::string& password) const {
        return verifyHash(password, passwordHash_);
    }

    // Korrigiert: Unveränderliche Ansicht der Berechtigungen zurückgeben
    std::vector<std::string> getPermissions() const {
        return permissions_;  // Gibt Kopie zurück
    }
};
// Korrigiert: Unveränderliches Design mit ordnungsgemäßer Kapselung
public final class FixedAccount {

    private final String accountNumber;
    private double balance;
    private final List<Transaction> transactions;
    private final Object balanceLock = new Object();

    public FixedAccount(String accountNumber, double initialBalance) {
        if (accountNumber == null || accountNumber.isEmpty()) {
            throw new IllegalArgumentException("Kontonummer erforderlich");
        }
        if (initialBalance < 0) {
            throw new IllegalArgumentException("Anfangssaldo darf nicht negativ sein");
        }
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
        this.transactions = new ArrayList<>();
    }

    // Korrigiert: Unveränderliches Feld
    public String getAccountNumber() {
        return accountNumber;
    }

    // Korrigiert: Thread-sicherer Saldozugriff
    public double getBalance() {
        synchronized (balanceLock) {
            return balance;
        }
    }

    // Korrigiert: Kontrollierte Saldomodifikation mit Validierung
    public void deposit(double amount, String source) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Einzahlungsbetrag muss positiv sein");
        }
        synchronized (balanceLock) {
            balance += amount;
            transactions.add(new Transaction("EINZAHLUNG", amount, source));
        }
    }

    public boolean withdraw(double amount, String reason) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Abhebungsbetrag muss positiv sein");
        }
        synchronized (balanceLock) {
            if (balance < amount) {
                return false;
            }
            balance -= amount;
            transactions.add(new Transaction("ABHEBUNG", amount, reason));
            return true;
        }
    }

    // Korrigiert: Unveränderliche Ansicht der Transaktionen zurückgeben
    public List<Transaction> getTransactions() {
        return Collections.unmodifiableList(new ArrayList<>(transactions));
    }
}
# Korrigiert: Python-Kapselung mit Properties und Validierung
class FixedPaymentProcessor:
    def __init__(self, api_key: str):
        # Korrigiert: Private Attribute (Konvention mit Unterstrich)
        self._api_key = api_key
        self._pending_charges: list = []
        self._processed_amount: float = 0
        self._config = {
            'max_amount': 10000,
            'retry_count': 3
        }
        self._config_frozen = False

    # Korrigiert: Nur-Lese-Property - kein Setter exponiert
    @property
    def processed_amount(self) -> float:
        return self._processed_amount

    # Korrigiert: API-Schlüssel nie exponiert
    # Kein Getter für _api_key überhaupt

    # Korrigiert: Config als unveränderliche Kopie exponiert
    @property
    def config(self) -> dict:
        return dict(self._config)  # Kopie zurückgeben

    def freeze_config(self):
        """Konfiguration nach Setup sperren"""
        self._config_frozen = True

    def update_config(self, key: str, value, admin_token: str):
        """Kontrollierte Config-Aktualisierung mit Autorisierung"""
        if self._config_frozen:
            raise RuntimeError("Konfiguration ist gesperrt")
        if not self._verify_admin(admin_token):
            raise PermissionError("Admin-Zugriff erforderlich")
        if key not in self._config:
            raise KeyError(f"Unbekannter Config-Schlüssel: {key}")
        self._config[key] = value

    def process_payment(self, amount: float) -> bool:
        if amount <= 0:
            raise ValueError("Betrag muss positiv sein")
        if amount > self._config['max_amount']:
            return False

        # _api_key intern verwenden ohne es zu exponieren
        result = self._call_payment_api(amount)

        if result:
            self._processed_amount += amount

        return result

    def _call_payment_api(self, amount: float) -> bool:
        # Interne Methode verwendet _api_key
        # API-Schlüssel verlässt diese Klasse nie
        return True  # Vereinfacht

CVE-Beispiele

  • CVE-2010-3860: Exponierte interne Konfiguration ermöglichte unbefugten Zugriff.
  • CVE-2019-0232: Unzureichende Kapselung im CGI-Servlet führte zu Command Injection.

Verwandte CWEs

  • CWE-710: Improper Adherence to Coding Standards (Eltern)
  • CWE-766: Critical Data Element Declared Public (Kind)
  • CWE-1227: Encapsulation Issues (Kategoriemitglied)

Referenzen

  1. MITRE Corporation. "CWE-1061: Insufficient Encapsulation." https://cwe.mitre.org/data/definitions/1061.html

  2. Oracle. "Java Tutorials - Controlling Access to Members of a Class."

  3. Martin, Robert C. "Clean Code: A Handbook of Agile Software Craftsmanship."