Private Data Structure Returned From A Public Method
Description
Private Data Structure Returned From A Public Method is a vulnerability where a publicly declared method returns a reference to a private mutable data structure, enabling unauthorized external modification. This violates encapsulation principles by exposing internal state to callers who can then modify the data structure directly, bypassing any validation or access controls the class intended to enforce. The original private data is no longer protected because the returned reference provides direct access to it.
Risk
Returning references to private data structures creates serious integrity and security risks. Callers receive direct access to internal state and can modify it without going through validated setter methods or triggering necessary side effects. In security contexts, this can allow privilege escalation by modifying user roles, bypass validation by directly changing values that should be sanitized, or corrupt internal state leading to application failures. The vulnerability is particularly dangerous for collections of sensitive data like permissions, configurations, or audit logs that should only be modified through controlled interfaces.
Solution
Never return direct references to mutable private data structures. Instead, return defensive copies of the data or wrap collections in unmodifiable wrappers. For arrays, use clone() or Arrays.copyOf(). For collections, use Collections.unmodifiableList(), Collections.unmodifiableSet(), or similar wrappers. Consider returning immutable types or read-only views of the data. If clients need to modify data, provide explicit setter methods that can validate and control the modifications. Document whether returned data is a copy or view.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - The contents of the data structure can be modified from outside the intended scope, compromising data integrity and potentially bypassing security controls. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Callers can modify internal state directly, bypassing setter validation, access checks, or audit logging that would normally be enforced. |
Example Code
Vulnerable Code
// Vulnerable: Returning reference to private array
public class VulnerableUserManager {
private String[] allowedRoles = {"USER", "VIEWER"};
private String[] adminUsers = {"admin1", "admin2"};
// Vulnerable: Returns reference to private array
public String[] getAllowedRoles() {
return allowedRoles; // Direct reference!
}
// Vulnerable: Caller can modify admin list
public String[] getAdminUsers() {
return adminUsers; // Direct reference!
}
public boolean isAllowedRole(String role) {
for (String allowed : allowedRoles) {
if (allowed.equals(role)) {
return true;
}
}
return false;
}
}
// Attacker exploits the vulnerability
public class RoleExploit {
public void exploit(VulnerableUserManager manager) {
// Get reference to private array
String[] roles = manager.getAllowedRoles();
// Modify the private array directly!
roles[0] = "ADMIN"; // Now ADMIN is allowed
// Original private field is now corrupted
System.out.println(manager.isAllowedRole("ADMIN")); // true!
// Add ourselves to admin list
String[] admins = manager.getAdminUsers();
admins[0] = "attacker"; // We're now admin!
}
}
// Vulnerable: Returning reference to private collection
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);
}
// Vulnerable: Returns mutable list reference
public List<String> getTrustedHosts() {
return trustedHosts;
}
// Vulnerable: Returns mutable map reference
public Map<String, Integer> getPermissions() {
return permissions;
}
// Vulnerable: Returns mutable set reference
public Set<String> getBlockedIPs() {
return blockedIPs;
}
}
// Attacker modifies security config
public class ConfigExploit {
public void exploit(VulnerableSecurityConfig config) {
// Add malicious host to trusted list
config.getTrustedHosts().add("evil-hacker.com");
// Change permission levels
config.getPermissions().put("admin", 0); // Anyone is admin!
// Remove our IP from blocked list
config.getBlockedIPs().remove("attacker-ip");
}
}
// Vulnerable: C++ returning reference to private data
class VulnerableColorPalette {
private:
int colorArray[256];
std::vector<std::string> colorNames;
public:
VulnerableColorPalette() {
for (int i = 0; i < 256; i++) {
colorArray[i] = i;
}
}
// Vulnerable: Returns reference to private array
int* getColors() {
return colorArray; // Direct pointer!
}
// Vulnerable: Returns reference to private vector
std::vector<std::string>& getColorNames() {
return colorNames; // Direct reference!
}
};
// Exploit
void exploit() {
VulnerableColorPalette palette;
// Modify private array through returned pointer
int* colors = palette.getColors();
colors[0] = 0xDEADBEEF; // Corrupted!
// Modify private vector
auto& names = palette.getColorNames();
names.clear(); // Deleted all names!
}
# Vulnerable: Returning reference to private list
class VulnerableAuditLog:
def __init__(self):
self._entries = [] # "Private" by convention
self._admins = ["admin"]
# Vulnerable: Returns reference to private list
def get_entries(self):
return self._entries # Direct reference!
def get_admins(self):
return self._admins
def add_entry(self, entry):
# This validation can be bypassed
if self._validate_entry(entry):
self._entries.append(entry)
def _validate_entry(self, entry):
return len(entry) > 0
# Exploit
def exploit():
log = VulnerableAuditLog()
# Get reference and modify directly
entries = log.get_entries()
entries.clear() # Delete audit trail!
entries.append("Fake entry") # Add fake entries!
# Add ourselves as admin
admins = log.get_admins()
admins.append("attacker") # Now we're admin!
Fixed Code
// Fixed: Return defensive copies
public class SecureUserManager {
private String[] allowedRoles = {"USER", "VIEWER"};
private String[] adminUsers = {"admin1", "admin2"};
// Fixed: Return copy of array
public String[] getAllowedRoles() {
return allowedRoles.clone(); // Returns copy
}
// Fixed: Return copy using Arrays.copyOf
public String[] getAdminUsers() {
return Arrays.copyOf(adminUsers, adminUsers.length);
}
// Fixed: Provide immutable view as alternative
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;
}
// Fixed: Modification only through controlled method
public void addAllowedRole(String role, AdminCredential cred) {
if (!cred.hasPermission("MANAGE_ROLES")) {
throw new SecurityException("Not authorized");
}
// Validate and add
if (isValidRole(role)) {
allowedRoles = Arrays.copyOf(allowedRoles, allowedRoles.length + 1);
allowedRoles[allowedRoles.length - 1] = role;
auditLog("Role added: " + role);
}
}
}
// Fixed: Return unmodifiable collections
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<>();
}
// Fixed: Returns unmodifiable view
public List<String> getTrustedHosts() {
return trustedHosts; // Already unmodifiable
}
// Fixed: Returns unmodifiable map
public Map<String, Integer> getPermissions() {
return permissions; // Already unmodifiable
}
// Fixed: Returns defensive copy
public Set<String> getBlockedIPs() {
return new HashSet<>(blockedIPs); // Copy
}
// Fixed: Controlled modification methods
public void addBlockedIP(String ip, AdminContext ctx) {
if (!ctx.isAuthorized("MANAGE_BLOCKED_IPS")) {
throw new SecurityException("Unauthorized");
}
blockedIPs.add(ip);
auditLog("Blocked IP: " + ip);
}
}
// Fixed: Using immutable types
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);
}
// Fixed: Immutable collections are safe to return
public ImmutableList<String> getServers() {
return servers; // Cannot be modified
}
public ImmutableMap<String, String> getSettings() {
return settings; // Cannot be modified
}
public ImmutableSet<String> getFeatures() {
return features; // Cannot be modified
}
// Fixed: Builder for controlled construction
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("Invalid server name");
}
return server;
}
}
}
// Fixed: C++ returning copies
class SecureColorPalette {
private:
int colorArray[256];
std::vector<std::string> colorNames;
public:
SecureColorPalette() {
for (int i = 0; i < 256; i++) {
colorArray[i] = i;
}
}
// Fixed: Return vector copy (by value)
std::vector<int> getColors() const {
return std::vector<int>(colorArray, colorArray + 256);
}
// Fixed: Return const reference for read-only access
const std::vector<std::string>& getColorNamesReadOnly() const {
return colorNames; // Const reference
}
// Fixed: Return copy for modifiable access
std::vector<std::string> getColorNamesCopy() const {
return colorNames; // Copy
}
// Fixed: Controlled modification
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;
}
};
# Fixed: Return defensive copies in Python
import copy
from typing import List, Set
class SecureAuditLog:
def __init__(self):
self._entries: List[str] = []
self._admins: Set[str] = {"admin"}
# Fixed: Return copy of list
def get_entries(self) -> List[str]:
return self._entries.copy() # Shallow copy
# Fixed: Return copy of set
def get_admins(self) -> Set[str]:
return self._admins.copy()
# Fixed: Return deep copy for nested structures
def get_entries_deep(self) -> List[str]:
return copy.deepcopy(self._entries)
# Fixed: Return frozen set (immutable)
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:
# Verify admin token
return token == "valid-admin-token"
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:
- CERT Secure Coding Standards
- Java security guidelines
References
- 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.