Öffentliche Daten werden privatem Array-Feld zugewiesen

Beschreibung

Öffentliche Daten werden privatem Array-Feld zugewiesen ist eine Schwachstelle, bei der eine Klasse öffentlich bereitgestellte Daten direkt einem privaten Array-Feld zuweist, ohne eine defensive Kopie zu erstellen. Dies gibt dem Aufrufer äquivalenten Zugriff auf das Array, als wäre es öffentlich, da er eine Referenz auf dasselbe Array-Objekt behält. Alle Modifikationen, die der Aufrufer an seinem ursprünglichen Array vornimmt, werden im privaten Feld reflektiert und umgekehrt, was die beabsichtigte Kapselung und den Datenschutz vollständig untergräbt.

Risiko

Die direkte Zuweisung externer Arrays zu privaten Feldern schafft ernsthafte Integritäts- und Sicherheitsschwachstellen. Aufrufer behalten die Fähigkeit, vermeintlich private Daten jederzeit nach der Zuweisung zu modifizieren. Im Sicherheitskontext erlaubt dies Angreifern, Berechtigungs-Arrays, Rollenlisten oder Konfigurationsdaten nach dem Bestehen der Validierung zu modifizieren. Der Timing-Aspekt macht dies besonders gefährlich - Daten können zum Zeitpunkt der Zuweisung gültig sein, aber später modifiziert werden, um Sicherheitsüberprüfungen zu umgehen. Sammlungen sensibler Daten wie erlaubte Operationen, vertrauenswürdige Hosts oder Benutzerberechtigungen werden anfällig für unbefugte Modifikation.

Lösung

Weisen Sie externe Array-Referenzen niemals direkt privaten Feldern zu. Erstellen Sie immer defensive Kopien von Arrays, die aus externen Quellen empfangen werden. Verwenden Sie clone() oder Arrays.copyOf(), um unabhängige Kopien zu erstellen. Für Collections verwenden Sie Copy-Konstruktoren oder Collections.copy(). Validieren Sie den Array-Inhalt vor dem Speichern. Erwägen Sie die Verwendung unveränderlicher Datenstrukturen, wenn keine Modifikation benötigt wird. Wenden Sie dasselbe defensive Kopieren beim Zurückgeben privater Arrays an (verwandt mit CWE-495). Dokumentieren Sie das Kopierverhalten in der API-Dokumentation.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Anwendungsdaten modifizieren - Der Inhalt des privaten Arrays kann von außerhalb des vorgesehenen Bereichs modifiziert werden, was die Datenintegrität gefährdet, da externer Code eine Referenz auf die internen Daten behält.
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Sicherheitsüberprüfungen, die zum Zeitpunkt der Zuweisung durchgeführt werden, können umgangen werden, indem der Array-Inhalt nach der Zuweisung modifiziert wird.

Beispielcode

Verwundbarer Code

// Verwundbar: Direkte Zuweisung von externem Array zu privatem Feld
public class VulnerableUserProfile {
    private String[] permissions;
    private String[] trustedDomains;
    private byte[] encryptionKey;

    // Verwundbar: Direkte Zuweisung
    public void setPermissions(String[] permissions) {
        this.permissions = permissions;  // Aufrufer behält Referenz!
    }

    // Verwundbar: Direkte Zuweisung
    public void setTrustedDomains(String[] domains) {
        this.trustedDomains = domains;  // Externe Referenz gespeichert!
    }

    // Verwundbar: Direkte Zuweisung sensibler Daten
    public void setEncryptionKey(byte[] key) {
        this.encryptionKey = key;  // Schlüssel kann extern modifiziert werden!
    }

    public boolean hasPermission(String perm) {
        for (String p : permissions) {
            if (p.equals(perm)) {
                return true;
            }
        }
        return false;
    }
}

// Angreifer nutzt die Schwachstelle aus
public class PermissionExploit {
    public void exploit() {
        VulnerableUserProfile profile = new VulnerableUserProfile();

        // Array übergeben und Referenz behalten
        String[] perms = {"READ"};
        profile.setPermissions(perms);

        // Validierung bestanden - Benutzer hat nur READ
        System.out.println(profile.hasPermission("ADMIN")); // false

        // Später: Das Array modifizieren, das wir noch haben
        perms[0] = "ADMIN";

        // Jetzt ist das private Feld korrumpiert!
        System.out.println(profile.hasPermission("ADMIN")); // true!
    }

    public void exploitEncryptionKey() {
        VulnerableUserProfile profile = new VulnerableUserProfile();

        byte[] key = generateValidKey();
        profile.setEncryptionKey(key);

        // Später: Schlüssel korrumpieren
        Arrays.fill(key, (byte) 0);  // Schlüssel ist jetzt komplett Nullen!
        // Profils Verschlüsselung ist kaputt
    }
}

// Verwundbar: Konstruktor akzeptiert Arrays
public class VulnerableConfig {
    private String[] allowedHosts;
    private int[] portNumbers;
    private Object[] handlers;

    // Verwundbar: Konstruktor speichert Referenzen direkt
    public VulnerableConfig(String[] hosts, int[] ports, Object[] handlers) {
        this.allowedHosts = hosts;      // Verwundbar
        this.portNumbers = ports;        // Verwundbar
        this.handlers = handlers;        // Verwundbar
    }

    public boolean isAllowedHost(String host) {
        return Arrays.asList(allowedHosts).contains(host);
    }
}

// Ausnutzung über Konstruktor
public class ConfigExploit {
    public void exploit() {
        String[] hosts = {"trusted.com"};
        int[] ports = {443};
        Object[] handlers = {new SafeHandler()};

        VulnerableConfig config = new VulnerableConfig(hosts, ports, handlers);

        // Nach der Konstruktion modifizieren
        hosts[0] = "evil.com";           // Bösartigen Host hinzufügen
        ports[0] = 22;                   // Zu SSH-Port ändern
        handlers[0] = new MaliciousHandler();  // Handler ersetzen

        // Config ist jetzt kompromittiert
        config.isAllowedHost("evil.com"); // true!
    }
}
// Verwundbar: C++ speichert externe Array-Referenz
class VulnerableSettings {
private:
    int* allowedPorts;
    size_t portCount;
    char* secretKey;

public:
    // Verwundbar: Speichert Pointer auf externes Array
    void setAllowedPorts(int* ports, size_t count) {
        this->allowedPorts = ports;  // Externer Pointer gespeichert!
        this->portCount = count;
    }

    // Verwundbar: Speichert Pointer auf externe Daten
    void setSecretKey(char* key) {
        this->secretKey = key;  // Externer Pointer gespeichert!
    }

    bool isPortAllowed(int port) {
        for (size_t i = 0; i < portCount; i++) {
            if (allowedPorts[i] == port) {
                return true;
            }
        }
        return false;
    }
};

// Ausnutzung
void exploit() {
    VulnerableSettings settings;

    int ports[] = {80, 443};
    settings.setAllowedPorts(ports, 2);

    // Externes Array modifizieren
    ports[0] = 22;  // Jetzt ist SSH erlaubt!

    char key[] = "secret123";
    settings.setSecretKey(key);

    // Schlüssel korrumpieren
    memset(key, 0, sizeof(key));  // Schlüssel ist jetzt leer!
}

Lösungscode

// Behoben: Defensive Kopierung bei Zuweisung
public class SecureUserProfile {
    private String[] permissions;
    private String[] trustedDomains;
    private byte[] encryptionKey;

    // Behoben: Defensive Kopie erstellen
    public void setPermissions(String[] permissions) {
        if (permissions == null) {
            this.permissions = new String[0];
        } else {
            // Behoben: Array klonen
            this.permissions = permissions.clone();
        }
    }

    // Behoben: Defensive Kopie mit Validierung
    public void setTrustedDomains(String[] domains) {
        if (domains == null) {
            this.trustedDomains = new String[0];
            return;
        }

        // Behoben: Kopieren und jedes Element validieren
        this.trustedDomains = new String[domains.length];
        for (int i = 0; i < domains.length; i++) {
            if (!isValidDomain(domains[i])) {
                throw new IllegalArgumentException("Ungültige Domain: " + domains[i]);
            }
            this.trustedDomains[i] = domains[i];
        }
    }

    // Behoben: Defensive Kopie und Quelle für sensible Daten löschen
    public void setEncryptionKey(byte[] key) {
        if (key == null || key.length < 16) {
            throw new IllegalArgumentException("Ungültiger Schlüssel");
        }
        // Behoben: Schlüssel kopieren
        this.encryptionKey = key.clone();
        // Quell-Array löschen, um externen Zugriff zu verhindern
        Arrays.fill(key, (byte) 0);
    }

    public boolean hasPermission(String perm) {
        for (String p : permissions) {
            if (p.equals(perm)) {
                return true;
            }
        }
        return false;
    }

    private boolean isValidDomain(String domain) {
        return domain != null &&
               domain.matches("^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$");
    }
}

// Behoben: Konstruktor mit defensiver Kopierung
public class SecureConfig {
    private final String[] allowedHosts;
    private final int[] portNumbers;
    private final Object[] handlers;

    // Behoben: Konstruktor erstellt defensive Kopien
    public SecureConfig(String[] hosts, int[] ports, Object[] handlers) {
        // Behoben: Arrays klonen
        this.allowedHosts = hosts != null ? hosts.clone() : new String[0];
        this.portNumbers = ports != null ? ports.clone() : new int[0];

        // Behoben: Tiefe Kopie für veränderbare Objekte
        if (handlers != null) {
            this.handlers = new Object[handlers.length];
            for (int i = 0; i < handlers.length; i++) {
                // Handler-Typ vor dem Speichern validieren
                if (handlers[i] instanceof Handler) {
                    this.handlers[i] = handlers[i];  // Unveränderliche Handler-Referenz
                } else {
                    throw new IllegalArgumentException("Ungültiger Handler-Typ");
                }
            }
        } else {
            this.handlers = new Object[0];
        }
    }

    public boolean isAllowedHost(String host) {
        return Arrays.asList(allowedHosts).contains(host);
    }

    // Behoben: Auch defensive Kopie zurückgeben
    public String[] getAllowedHosts() {
        return allowedHosts.clone();
    }
}

// Behoben: Collections statt Arrays verwenden
public class SecureConfigWithCollections {
    private final List<String> allowedHosts;
    private final Set<Integer> allowedPorts;

    public SecureConfigWithCollections(List<String> hosts, Set<Integer> ports) {
        // Behoben: Unveränderbare Kopien erstellen
        this.allowedHosts = hosts != null
            ? Collections.unmodifiableList(new ArrayList<>(hosts))
            : Collections.emptyList();

        this.allowedPorts = ports != null
            ? Collections.unmodifiableSet(new HashSet<>(ports))
            : Collections.emptySet();
    }

    public boolean isAllowedHost(String host) {
        return allowedHosts.contains(host);
    }

    public boolean isAllowedPort(int port) {
        return allowedPorts.contains(port);
    }
}
// Behoben: C++ mit defensiver Kopierung
class SecureSettings {
private:
    std::vector<int> allowedPorts;
    std::string secretKey;

public:
    // Behoben: Daten in internen Speicher kopieren
    void setAllowedPorts(const int* ports, size_t count) {
        allowedPorts.clear();
        if (ports != nullptr && count > 0) {
            // Behoben: Elemente in Vector kopieren
            allowedPorts.assign(ports, ports + count);
        }
    }

    // Behoben: Per const-Referenz akzeptieren, Kopie speichern
    void setAllowedPorts(const std::vector<int>& ports) {
        allowedPorts = ports;  // Kopierzuweisung
    }

    // Behoben: String-Daten kopieren
    void setSecretKey(const char* key) {
        if (key != nullptr) {
            secretKey = std::string(key);  // Erstellt Kopie
        } else {
            secretKey.clear();
        }
    }

    // Behoben: String per Wert akzeptieren (Aufrufer kann moven wenn gewünscht)
    void setSecretKey(std::string key) {
        secretKey = std::move(key);  // Ownership übernehmen
    }

    bool isPortAllowed(int port) const {
        return std::find(allowedPorts.begin(), allowedPorts.end(), port)
               != allowedPorts.end();
    }
};

// Behoben: Smart Pointers und RAII verwenden
class SecureConfig {
private:
    std::unique_ptr<std::vector<std::string>> hosts;

public:
    // Behoben: Eigene Kopie erstellen
    void setHosts(const std::vector<std::string>& newHosts) {
        hosts = std::make_unique<std::vector<std::string>>(newHosts);
    }

    // Behoben: Nur Const-Zugriff
    const std::vector<std::string>& getHosts() const {
        static const std::vector<std::string> empty;
        return hosts ? *hosts : empty;
    }
};

CVE-Beispiele

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

  • CERT Secure Coding Standards
  • Java-Sicherheitsrichtlinien zur Kapselung

Referenzen

  1. MITRE Corporation. "CWE-496: Public Data Assigned to Private Array-Typed Field." https://cwe.mitre.org/data/definitions/496.html
  2. CERT Oracle Secure Coding Standard for Java. "OBJ06-J. Defensively copy mutable inputs and mutable internal components."
  3. Bloch, Joshua. "Effective Java" - Item 50: Make defensive copies when needed.