Ö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
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: 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. |
| Zugriffskontrolle | Bereich: 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
- MITRE Corporation. "CWE-496: Public Data Assigned to Private Array-Typed Field." https://cwe.mitre.org/data/definitions/496.html
- CERT Oracle Secure Coding Standard for Java. "OBJ06-J. Defensively copy mutable inputs and mutable internal components."
- Bloch, Joshua. "Effective Java" - Item 50: Make defensive copies when needed.