Private Datenstruktur wird von öffentlicher Methode zurückgegeben
Beschreibung
Private Datenstruktur wird von öffentlicher Methode zurückgegeben ist eine Schwachstelle, bei der eine öffentlich deklarierte Methode eine Referenz auf eine private veränderbare Datenstruktur zurückgibt, was unbefugte externe Modifikationen ermöglicht. Dies verletzt Kapselungsprinzipien, indem der interne Zustand für Aufrufer offengelegt wird, die dann die Datenstruktur direkt modifizieren können und dabei jegliche Validierung oder Zugriffskontrollen umgehen, die die Klasse eigentlich durchsetzen sollte. Die ursprünglichen privaten Daten sind nicht mehr geschützt, da die zurückgegebene Referenz direkten Zugriff darauf bietet.
Risiko
Das Zurückgeben von Referenzen auf private Datenstrukturen schafft ernsthafte Integritäts- und Sicherheitsrisiken. Aufrufer erhalten direkten Zugriff auf den internen Zustand und können ihn modifizieren, ohne validierte Setter-Methoden zu durchlaufen oder notwendige Nebeneffekte auszulösen. Im Sicherheitskontext kann dies Privilegieneskalation durch Modifikation von Benutzerrollen ermöglichen, Validierung durch direktes Ändern von Werten umgehen, die eigentlich bereinigt werden sollten, oder den internen Zustand korrumpieren, was zu Anwendungsfehlern führt. Die Schwachstelle ist besonders gefährlich für Sammlungen sensibler Daten wie Berechtigungen, Konfigurationen oder Audit-Logs, die nur über kontrollierte Schnittstellen modifiziert werden sollten.
Lösung
Geben Sie niemals direkte Referenzen auf veränderbare private Datenstrukturen zurück. Stattdessen sollten defensive Kopien der Daten zurückgegeben oder Sammlungen in unveränderbare Wrapper eingepackt werden. Für Arrays verwenden Sie clone() oder Arrays.copyOf(). Für Sammlungen verwenden Sie Collections.unmodifiableList(), Collections.unmodifiableSet() oder ähnliche Wrapper. Erwägen Sie die Rückgabe unveränderlicher Typen oder schreibgeschützter Ansichten der Daten. Wenn Clients Daten modifizieren müssen, stellen Sie explizite Setter-Methoden bereit, die die Modifikationen validieren und kontrollieren können. Dokumentieren Sie, ob zurückgegebene Daten eine Kopie oder Ansicht sind.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Der Inhalt der Datenstruktur kann von außerhalb des vorgesehenen Bereichs modifiziert werden, was die Datenintegrität gefährdet und möglicherweise Sicherheitskontrollen umgeht. |
| Zugriffskontrolle | Bereich: Zugriffskontrolle Schutzmechanismus umgehen - Aufrufer können den internen Zustand direkt modifizieren und dabei Setter-Validierung, Zugriffsüberprüfungen oder Audit-Logging umgehen, die normalerweise durchgesetzt würden. |
Beispielcode
Verwundbarer Code
// Verwundbar: Gibt Referenz auf privates Array zurück
public class VulnerableUserManager {
private String[] allowedRoles = {"USER", "VIEWER"};
private String[] adminUsers = {"admin1", "admin2"};
// Verwundbar: Gibt Referenz auf privates Array zurück
public String[] getAllowedRoles() {
return allowedRoles; // Direkte Referenz!
}
// Verwundbar: Aufrufer kann Admin-Liste modifizieren
public String[] getAdminUsers() {
return adminUsers; // Direkte Referenz!
}
public boolean isAllowedRole(String role) {
for (String allowed : allowedRoles) {
if (allowed.equals(role)) {
return true;
}
}
return false;
}
}
// Angreifer nutzt die Schwachstelle aus
public class RoleExploit {
public void exploit(VulnerableUserManager manager) {
// Referenz auf privates Array holen
String[] roles = manager.getAllowedRoles();
// Privates Array direkt modifizieren!
roles[0] = "ADMIN"; // Jetzt ist ADMIN erlaubt
// Das ursprüngliche private Feld ist nun korrumpiert
System.out.println(manager.isAllowedRole("ADMIN")); // true!
// Uns selbst zur Admin-Liste hinzufügen
String[] admins = manager.getAdminUsers();
admins[0] = "attacker"; // Wir sind jetzt Admin!
}
}
// Verwundbar: Gibt Referenz auf private Collection zurück
public class VulnerableSecurityConfig {
private List<String> trustedHosts = new ArrayList<>();
private Map<String, Integer> permissions = new HashMap<>();
private Set<String> blockedIPs = new HashSet<>();
public VulnerableSecurityConfig() {
trustedHosts.add("api.trusted.com");
permissions.put("read", 1);
permissions.put("write", 2);
permissions.put("admin", 3);
}
// Verwundbar: Gibt veränderbare List-Referenz zurück
public List<String> getTrustedHosts() {
return trustedHosts;
}
// Verwundbar: Gibt veränderbare Map-Referenz zurück
public Map<String, Integer> getPermissions() {
return permissions;
}
// Verwundbar: Gibt veränderbare Set-Referenz zurück
public Set<String> getBlöckedIPs() {
return blockedIPs;
}
}
// Angreifer modifiziert Sicherheitskonfiguration
public class ConfigExploit {
public void exploit(VulnerableSecurityConfig config) {
// Bösartigen Host zur vertrauenswürdigen Liste hinzufügen
config.getTrustedHosts().add("evil-hacker.com");
// Berechtigungsstufen ändern
config.getPermissions().put("admin", 0); // Jeder ist Admin!
// Unsere IP von der Blocklist entfernen
config.getBlöckedIPs().remove("attacker-ip");
}
}
// Verwundbar: C++ gibt Referenz auf private Daten zurück
class VulnerableColorPalette {
private:
int colorArray[256];
std::vector<std::string> colorNames;
public:
VulnerableColorPalette() {
for (int i = 0; i < 256; i++) {
colorArray[i] = i;
}
}
// Verwundbar: Gibt Referenz auf privates Array zurück
int* getColors() {
return colorArray; // Direkter Pointer!
}
// Verwundbar: Gibt Referenz auf privaten Vector zurück
std::vector<std::string>& getColorNames() {
return colorNames; // Direkte Referenz!
}
};
// Ausnutzung
void exploit() {
VulnerableColorPalette palette;
// Privates Array über zurückgegebenen Pointer modifizieren
int* colors = palette.getColors();
colors[0] = 0xDEADBEEF; // Korrumpiert!
// Privaten Vector modifizieren
auto& names = palette.getColorNames();
names.clear(); // Alle Namen gelöscht!
}
# Verwundbar: Gibt Referenz auf private Liste zurück
class VulnerableAuditLog:
def __init__(self):
self._entries = [] # "Privat" per Konvention
self._admins = ["admin"]
# Verwundbar: Gibt Referenz auf private Liste zurück
def get_entries(self):
return self._entries # Direkte Referenz!
def get_admins(self):
return self._admins
def add_entry(self, entry):
# Diese Validierung kann umgangen werden
if self._validate_entry(entry):
self._entries.append(entry)
def _validate_entry(self, entry):
return len(entry) > 0
# Ausnutzung
def exploit():
log = VulnerableAuditLog()
# Referenz holen und direkt modifizieren
entries = log.get_entries()
entries.clear() # Audit-Trail löschen!
entries.append("Gefälschter Eintrag") # Gefälschte Einträge hinzufügen!
# Uns selbst als Admin hinzufügen
admins = log.get_admins()
admins.append("attacker") # Jetzt sind wir Admin!
Lösungscode
// Behoben: Defensive Kopien zurückgeben
public class SecureUserManager {
private String[] allowedRoles = {"USER", "VIEWER"};
private String[] adminUsers = {"admin1", "admin2"};
// Behoben: Kopie des Arrays zurückgeben
public String[] getAllowedRoles() {
return allowedRoles.clone(); // Gibt Kopie zurück
}
// Behoben: Kopie mit Arrays.copyOf zurückgeben
public String[] getAdminUsers() {
return Arrays.copyOf(adminUsers, adminUsers.length);
}
// Behoben: Unveränderbare Ansicht als Alternative bereitstellen
public List<String> getAllowedRolesAsList() {
return Collections.unmodifiableList(Arrays.asList(allowedRoles));
}
public boolean isAllowedRole(String role) {
for (String allowed : allowedRoles) {
if (allowed.equals(role)) {
return true;
}
}
return false;
}
// Behoben: Modifikation nur durch kontrollierte Methode
public void addAllowedRole(String role, AdminCredential cred) {
if (!cred.hasPermission("MANAGE_ROLES")) {
throw new SecurityException("Nicht autorisiert");
}
// Validieren und hinzufügen
if (isValidRole(role)) {
allowedRoles = Arrays.copyOf(allowedRoles, allowedRoles.length + 1);
allowedRoles[allowedRoles.length - 1] = role;
auditLog("Rolle hinzugefügt: " + role);
}
}
}
// Behoben: Unveränderbare Collections zurückgeben
public class SecureSecurityConfig {
private final List<String> trustedHosts;
private final Map<String, Integer> permissions;
private final Set<String> blockedIPs;
public SecureSecurityConfig() {
List<String> hosts = new ArrayList<>();
hosts.add("api.trusted.com");
this.trustedHosts = Collections.unmodifiableList(hosts);
Map<String, Integer> perms = new HashMap<>();
perms.put("read", 1);
perms.put("write", 2);
perms.put("admin", 3);
this.permissions = Collections.unmodifiableMap(perms);
this.blockedIPs = new CopyOnWriteArraySet<>();
}
// Behoben: Gibt unveränderbare Ansicht zurück
public List<String> getTrustedHosts() {
return trustedHosts; // Bereits unveränderbar
}
// Behoben: Gibt unveränderbare Map zurück
public Map<String, Integer> getPermissions() {
return permissions; // Bereits unveränderbar
}
// Behoben: Gibt defensive Kopie zurück
public Set<String> getBlöckedIPs() {
return new HashSet<>(blockedIPs); // Kopie
}
// Behoben: Kontrollierte Modifikationsmethoden
public void addBlöckedIP(String ip, AdminContext ctx) {
if (!ctx.isAuthorized("MANAGE_BLOCKED_IPS")) {
throw new SecurityException("Nicht autorisiert");
}
blockedIPs.add(ip);
auditLog("IP gesperrt: " + ip);
}
}
// Behoben: Verwendung unveränderlicher Typen
public final class SecureConfiguration {
private final ImmutableList<String> servers;
private final ImmutableMap<String, String> settings;
private final ImmutableSet<String> features;
private SecureConfiguration(Builder builder) {
this.servers = ImmutableList.copyOf(builder.servers);
this.settings = ImmutableMap.copyOf(builder.settings);
this.features = ImmutableSet.copyOf(builder.features);
}
// Behoben: Unveränderliche Collections sind sicher zurückzugeben
public ImmutableList<String> getServers() {
return servers; // Kann nicht modifiziert werden
}
public ImmutableMap<String, String> getSettings() {
return settings; // Kann nicht modifiziert werden
}
public ImmutableSet<String> getFeatures() {
return features; // Kann nicht modifiziert werden
}
// Behoben: Builder für kontrollierte Konstruktion
public static class Builder {
private final List<String> servers = new ArrayList<>();
private final Map<String, String> settings = new HashMap<>();
private final Set<String> features = new HashSet<>();
public Builder addServer(String server) {
servers.add(validateServer(server));
return this;
}
public SecureConfiguration build() {
return new SecureConfiguration(this);
}
private String validateServer(String server) {
if (!server.matches("^[a-zA-Z0-9.-]+$")) {
throw new IllegalArgumentException("Ungültiger Servername");
}
return server;
}
}
}
// Behoben: C++ gibt Kopien zurück
class SecureColorPalette {
private:
int colorArray[256];
std::vector<std::string> colorNames;
public:
SecureColorPalette() {
for (int i = 0; i < 256; i++) {
colorArray[i] = i;
}
}
// Behoben: Vector-Kopie zurückgeben (by value)
std::vector<int> getColors() const {
return std::vector<int>(colorArray, colorArray + 256);
}
// Behoben: Const-Referenz für schreibgeschützten Zugriff zurückgeben
const std::vector<std::string>& getColorNamesReadOnly() const {
return colorNames; // Const-Referenz
}
// Behoben: Kopie für veränderbaren Zugriff zurückgeben
std::vector<std::string> getColorNamesCopy() const {
return colorNames; // Kopie
}
// Behoben: Kontrollierte Modifikation
void addColorName(const std::string& name) {
if (validateColorName(name)) {
colorNames.push_back(name);
}
}
private:
bool validateColorName(const std::string& name) const {
return !name.empty() && name.length() < 100;
}
};
# Behoben: Defensive Kopien in Python zurückgeben
import copy
from typing import List, Set
class SecureAuditLog:
def __init__(self):
self._entries: List[str] = []
self._admins: Set[str] = {"admin"}
# Behoben: Kopie der Liste zurückgeben
def get_entries(self) -> List[str]:
return self._entries.copy() # Flache Kopie
# Behoben: Kopie des Sets zurückgeben
def get_admins(self) -> Set[str]:
return self._admins.copy()
# Behoben: Tiefe Kopie für verschachtelte Strukturen zurückgeben
def get_entries_deep(self) -> List[str]:
return copy.deepcopy(self._entries)
# Behoben: Frozen Set zurückgeben (unveränderlich)
def get_admins_frozen(self) -> frozenset:
return frozenset(self._admins)
def add_entry(self, entry: str) -> None:
if self._validate_entry(entry):
self._entries.append(entry)
def add_admin(self, admin: str, auth_token: str) -> None:
if self._verify_token(auth_token):
self._admins.add(admin)
def _validate_entry(self, entry: str) -> bool:
return bool(entry and len(entry) > 0)
def _verify_token(self, token: str) -> bool:
# Admin-Token verifizieren
return token == "valid-admin-token"
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
Referenzen
- MITRE Corporation. "CWE-495: Private Data Structure Returned From A Public Method." https://cwe.mitre.org/data/definitions/495.html
- CERT Oracle Secure Coding Standard for Java. "OBJ05-J. Do not return references to private mutable class members."
- Bloch, Joshua. "Effective Java" - Item 50: Make defensive copies when needed.