Public Data Assigned to Private Array-Typed Field
Description
Public Data Assigned to Private Array-Typed Field is a vulnerability where a class assigns publicly provided data directly to a private array field without making a defensive copy. This provides the caller equivalent access to that array as if it were public, since they retain a reference to the same array object. Any modifications the caller makes to their original array will be reflected in the private field, and vice versa, completely undermining the intended encapsulation and data protection.
Risk
Direct assignment of external arrays to private fields creates serious integrity and security vulnerabilities. Callers retain the ability to modify supposedly private data at any time after the assignment. In security contexts, this allows attackers to modify permission arrays, role lists, or configuration data after passing validation. The timing aspect makes this particularly dangerous - data may be valid at assignment time but modified later to bypass security checks. Collections of sensitive data like allowed operations, trusted hosts, or user permissions become vulnerable to unauthorized modification.
Solution
Never assign external array references directly to private fields. Always create defensive copies of arrays received from external sources. Use clone() or Arrays.copyOf() to create independent copies. For collections, use copy constructors or Collections.copy(). Validate the array contents before storing. Consider using immutable data structures when modification is not needed. Apply the same defensive copying when returning private arrays (related to CWE-495). Document the copying behavior in API documentation.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - The contents of the private array can be modified from outside the intended scope, compromising data integrity since external code retains a reference to the internal data. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Security checks performed at assignment time can be bypassed by modifying the array contents after assignment. |
Example Code
Vulnerable Code
// Vulnerable: Direct assignment of external array to private field
public class VulnerableUserProfile {
private String[] permissions;
private String[] trustedDomains;
private byte[] encryptionKey;
// Vulnerable: Direct assignment
public void setPermissions(String[] permissions) {
this.permissions = permissions; // Caller retains reference!
}
// Vulnerable: Direct assignment
public void setTrustedDomains(String[] domains) {
this.trustedDomains = domains; // External reference stored!
}
// Vulnerable: Direct assignment of sensitive data
public void setEncryptionKey(byte[] key) {
this.encryptionKey = key; // Key can be modified externally!
}
public boolean hasPermission(String perm) {
for (String p : permissions) {
if (p.equals(perm)) {
return true;
}
}
return false;
}
}
// Attacker exploits the vulnerability
public class PermissionExploit {
public void exploit() {
VulnerableUserProfile profile = new VulnerableUserProfile();
// Pass array and keep reference
String[] perms = {"READ"};
profile.setPermissions(perms);
// Validation passes - user only has READ
System.out.println(profile.hasPermission("ADMIN")); // false
// Later: modify the array we still hold
perms[0] = "ADMIN";
// Now the private field is corrupted!
System.out.println(profile.hasPermission("ADMIN")); // true!
}
public void exploitEncryptionKey() {
VulnerableUserProfile profile = new VulnerableUserProfile();
byte[] key = generateValidKey();
profile.setEncryptionKey(key);
// Later: corrupt the key
Arrays.fill(key, (byte) 0); // Key is now all zeros!
// Profile's encryption is broken
}
}
// Vulnerable: Constructor accepting arrays
public class VulnerableConfig {
private String[] allowedHosts;
private int[] portNumbers;
private Object[] handlers;
// Vulnerable: Constructor stores references directly
public VulnerableConfig(String[] hosts, int[] ports, Object[] handlers) {
this.allowedHosts = hosts; // Vulnerable
this.portNumbers = ports; // Vulnerable
this.handlers = handlers; // Vulnerable
}
public boolean isAllowedHost(String host) {
return Arrays.asList(allowedHosts).contains(host);
}
}
// Exploit through constructor
public class ConfigExploit {
public void exploit() {
String[] hosts = {"trusted.com"};
int[] ports = {443};
Object[] handlers = {new SafeHandler()};
VulnerableConfig config = new VulnerableConfig(hosts, ports, handlers);
// Modify after construction
hosts[0] = "evil.com"; // Add malicious host
ports[0] = 22; // Change to SSH port
handlers[0] = new MaliciousHandler(); // Replace handler
// Config is now compromised
config.isAllowedHost("evil.com"); // true!
}
}
// Vulnerable: C++ storing external array reference
class VulnerableSettings {
private:
int* allowedPorts;
size_t portCount;
char* secretKey;
public:
// Vulnerable: Stores pointer to external array
void setAllowedPorts(int* ports, size_t count) {
this->allowedPorts = ports; // External pointer stored!
this->portCount = count;
}
// Vulnerable: Stores pointer to external data
void setSecretKey(char* key) {
this->secretKey = key; // External pointer stored!
}
bool isPortAllowed(int port) {
for (size_t i = 0; i < portCount; i++) {
if (allowedPorts[i] == port) {
return true;
}
}
return false;
}
};
// Exploit
void exploit() {
VulnerableSettings settings;
int ports[] = {80, 443};
settings.setAllowedPorts(ports, 2);
// Modify the external array
ports[0] = 22; // Now SSH is allowed!
char key[] = "secret123";
settings.setSecretKey(key);
// Corrupt the key
memset(key, 0, sizeof(key)); // Key is now empty!
}
Fixed Code
// Fixed: Defensive copying on assignment
public class SecureUserProfile {
private String[] permissions;
private String[] trustedDomains;
private byte[] encryptionKey;
// Fixed: Create defensive copy
public void setPermissions(String[] permissions) {
if (permissions == null) {
this.permissions = new String[0];
} else {
// Fixed: Clone the array
this.permissions = permissions.clone();
}
}
// Fixed: Defensive copy with validation
public void setTrustedDomains(String[] domains) {
if (domains == null) {
this.trustedDomains = new String[0];
return;
}
// Fixed: Copy and validate each element
this.trustedDomains = new String[domains.length];
for (int i = 0; i < domains.length; i++) {
if (!isValidDomain(domains[i])) {
throw new IllegalArgumentException("Invalid domain: " + domains[i]);
}
this.trustedDomains[i] = domains[i];
}
}
// Fixed: Defensive copy and clear source for sensitive data
public void setEncryptionKey(byte[] key) {
if (key == null || key.length < 16) {
throw new IllegalArgumentException("Invalid key");
}
// Fixed: Copy the key
this.encryptionKey = key.clone();
// Clear the source array to prevent external access
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]$");
}
}
// Fixed: Constructor with defensive copying
public class SecureConfig {
private final String[] allowedHosts;
private final int[] portNumbers;
private final Object[] handlers;
// Fixed: Constructor creates defensive copies
public SecureConfig(String[] hosts, int[] ports, Object[] handlers) {
// Fixed: Clone arrays
this.allowedHosts = hosts != null ? hosts.clone() : new String[0];
this.portNumbers = ports != null ? ports.clone() : new int[0];
// Fixed: Deep copy for mutable objects
if (handlers != null) {
this.handlers = new Object[handlers.length];
for (int i = 0; i < handlers.length; i++) {
// Validate handler type before storing
if (handlers[i] instanceof Handler) {
this.handlers[i] = handlers[i]; // Immutable handler reference
} else {
throw new IllegalArgumentException("Invalid handler type");
}
}
} else {
this.handlers = new Object[0];
}
}
public boolean isAllowedHost(String host) {
return Arrays.asList(allowedHosts).contains(host);
}
// Fixed: Also return defensive copy
public String[] getAllowedHosts() {
return allowedHosts.clone();
}
}
// Fixed: Using Collections instead of arrays
public class SecureConfigWithCollections {
private final List<String> allowedHosts;
private final Set<Integer> allowedPorts;
public SecureConfigWithCollections(List<String> hosts, Set<Integer> ports) {
// Fixed: Create unmodifiable copies
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);
}
}
// Fixed: C++ with defensive copying
class SecureSettings {
private:
std::vector<int> allowedPorts;
std::string secretKey;
public:
// Fixed: Copy data into internal storage
void setAllowedPorts(const int* ports, size_t count) {
allowedPorts.clear();
if (ports != nullptr && count > 0) {
// Fixed: Copy elements into vector
allowedPorts.assign(ports, ports + count);
}
}
// Fixed: Accept by const reference, store copy
void setAllowedPorts(const std::vector<int>& ports) {
allowedPorts = ports; // Copy assignment
}
// Fixed: Copy string data
void setSecretKey(const char* key) {
if (key != nullptr) {
secretKey = std::string(key); // Creates copy
} else {
secretKey.clear();
}
}
// Fixed: Accept string by value (caller can move if they want)
void setSecretKey(std::string key) {
secretKey = std::move(key); // Take ownership
}
bool isPortAllowed(int port) const {
return std::find(allowedPorts.begin(), allowedPorts.end(), port)
!= allowedPorts.end();
}
};
// Fixed: Using smart pointers and RAII
class SecureConfig {
private:
std::unique_ptr<std::vector<std::string>> hosts;
public:
// Fixed: Create owned copy
void setHosts(const std::vector<std::string>& newHosts) {
hosts = std::make_unique<std::vector<std::string>>(newHosts);
}
// Fixed: Const access only
const std::vector<std::string>& getHosts() const {
static const std::vector<std::string> empty;
return hosts ? *hosts : empty;
}
};
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 on encapsulation
References
- 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.