Array Declared Public, Final, and Static
Description
Array Declared Public, Final, and Static is a security vulnerability in Java where a class declares an array with the modifiers public, static, and final. While the final keyword prevents reassignment of the array reference itself, it does not protect the array's contents from modification. Because the array is publicly accessible, any code—including malicious code—can modify the values stored in the array elements. This is almost always a security bug, as the final modifier creates a false sense of immutability while the array contents remain fully mutable.
Risk
Public static final arrays expose mutable state to all code in the application and potentially to external attackers. In applet or mobile code environments, malicious code can modify the array contents to alter application behavior. Configuration arrays can be changed to bypass security settings. URL lists, permission arrays, or allowed values can be manipulated. Constant arrays intended to define fixed values become unreliable when external code modifies them. The misleading use of final creates a false assumption of immutability, leading developers to trust array contents that may have been tampered with.
Solution
Make the array private and provide a public method that returns a copy of the array or an unmodifiable view. Alternatively, use an immutable collection such as Collections.unmodifiableList() or List.of() (Java 9+). If the array must be accessible, provide only a getter that returns a defensive copy. Never expose mutable internal state through public static fields. Consider using enums for fixed sets of values. Document that returned arrays are copies if that design is chosen.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - External code can modify array contents, changing supposedly constant values and altering application behavior. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Security-related arrays like allowed URLs, permissions, or trusted hosts can be modified to bypass access controls. |
Example Code
Vulnerable Code
// Vulnerable: Public static final array
public final class VulnerableConfig extends Applet {
// Vulnerable: Array contents can be modified despite 'final'
public static final String[] ALLOWED_HOSTS = {
"trusted.example.com",
"secure.example.org"
};
// Vulnerable: URL array exposed publicly
public static final URL[] RESOURCE_URLS;
static {
try {
RESOURCE_URLS = new URL[] {
new URL("https://api.example.com"),
new URL("https://data.example.com")
};
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
// Vulnerable: Permission array
public static final Permission[] REQUIRED_PERMISSIONS = {
new FilePermission("/safe/path", "read"),
new SocketPermission("localhost:8080", "connect")
};
}
// Malicious code can modify the arrays:
public class Attacker {
public static void exploit() {
// Modify allowed hosts to include attacker's server
VulnerableConfig.ALLOWED_HOSTS[0] = "evil.attacker.com";
// Redirect resource URLs
try {
VulnerableConfig.RESOURCE_URLS[0] =
new URL("https://malicious.attacker.com/steal-data");
} catch (MalformedURLException e) {}
// Weaken permissions
VulnerableConfig.REQUIRED_PERMISSIONS[0] =
new FilePermission("/-", "read,write,delete");
}
}
// Vulnerable: Constants that aren't really constant
public class VulnerableConstants {
// Vulnerable: Looks immutable but isn't
public static final int[] MAGIC_NUMBERS = {42, 17, 256};
// Vulnerable: Configuration values
public static final String[] VALID_ROLES = {
"user", "admin", "moderator"
};
// Vulnerable: Sensitive data
public static final byte[] ENCRYPTION_IV = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
}
// Attack vector
public class ConstantAttacker {
public static void corrupt() {
// Modify "constants"
VulnerableConstants.MAGIC_NUMBERS[0] = 0;
// Add unauthorized role
VulnerableConstants.VALID_ROLES[1] = "superadmin";
// Weaken encryption
Arrays.fill(VulnerableConstants.ENCRYPTION_IV, (byte) 0);
}
}
// Vulnerable: Public mutable field in security class
public class VulnerableSecurityManager {
// Vulnerable: Trusted origins can be modified
public static final String[] TRUSTED_ORIGINS = {
"https://app.example.com",
"https://admin.example.com"
};
public boolean isOriginTrusted(String origin) {
for (String trusted : TRUSTED_ORIGINS) {
if (trusted.equals(origin)) {
return true;
}
}
return false;
}
}
Fixed Code
// Fixed: Private array with defensive copy getter
public final class SecureConfig {
// Fixed: Make the array private
private static final String[] ALLOWED_HOSTS = {
"trusted.example.com",
"secure.example.org"
};
// Fixed: Return a defensive copy
public static String[] getAllowedHosts() {
return ALLOWED_HOSTS.clone();
}
// Fixed: Or return as unmodifiable list
public static List<String> getAllowedHostsList() {
return Collections.unmodifiableList(Arrays.asList(ALLOWED_HOSTS));
}
// Fixed: Private URL array
private static final URL[] RESOURCE_URLS;
static {
try {
RESOURCE_URLS = new URL[] {
new URL("https://api.example.com"),
new URL("https://data.example.com")
};
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public static URL[] getResourceUrls() {
return RESOURCE_URLS.clone();
}
// Fixed: Check specific index without exposing array
public static URL getResourceUrl(int index) {
if (index < 0 || index >= RESOURCE_URLS.length) {
throw new IndexOutOfBoundsException();
}
return RESOURCE_URLS[index];
}
}
// Fixed: Using immutable collections (Java 9+)
public final class SecureConstants {
// Fixed: Immutable list
public static final List<String> VALID_ROLES =
List.of("user", "admin", "moderator");
// Fixed: Immutable set
public static final Set<String> ALLOWED_EXTENSIONS =
Set.of("txt", "pdf", "jpg", "png");
// Fixed: For primitive arrays, use private + getter
private static final int[] MAGIC_NUMBERS = {42, 17, 256};
public static int[] getMagicNumbers() {
return MAGIC_NUMBERS.clone();
}
// Fixed: For sensitive byte arrays
private static final byte[] ENCRYPTION_IV = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
public static byte[] getEncryptionIv() {
return ENCRYPTION_IV.clone();
}
}
// Fixed: Using enum for fixed set of values
public enum Role {
USER("user"),
ADMIN("admin"),
MODERATOR("moderator");
private final String name;
Role(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static Role fromName(String name) {
for (Role role : values()) {
if (role.name.equals(name)) {
return role;
}
}
throw new IllegalArgumentException("Unknown role: " + name);
}
}
// Fixed: Security manager with immutable trusted origins
public class SecureSecurityManager {
// Fixed: Immutable set
private static final Set<String> TRUSTED_ORIGINS =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"https://app.example.com",
"https://admin.example.com"
)));
// Java 9+ version
// private static final Set<String> TRUSTED_ORIGINS =
// Set.of("https://app.example.com", "https://admin.example.com");
public boolean isOriginTrusted(String origin) {
return TRUSTED_ORIGINS.contains(origin);
}
// Returns unmodifiable view
public Set<String> getTrustedOrigins() {
return TRUSTED_ORIGINS;
}
}
// Fixed: Configuration class with defensive copying
public class SecureConfiguration {
private final List<String> allowedHosts;
private final Map<String, String> settings;
public SecureConfiguration(List<String> hosts, Map<String, String> settings) {
// Defensive copy on construction
this.allowedHosts = new ArrayList<>(hosts);
this.settings = new HashMap<>(settings);
}
public List<String> getAllowedHosts() {
// Defensive copy on retrieval
return new ArrayList<>(allowedHosts);
}
// Or return unmodifiable view
public List<String> getAllowedHostsView() {
return Collections.unmodifiableList(allowedHosts);
}
public Map<String, String> getSettings() {
return Collections.unmodifiableMap(settings);
}
}
// Fixed: Using wrapper class for byte arrays
public final class ImmutableByteArray {
private final byte[] data;
public ImmutableByteArray(byte[] source) {
this.data = source.clone(); // Defensive copy
}
public byte[] toByteArray() {
return data.clone(); // Return copy
}
public int length() {
return data.length;
}
public byte get(int index) {
return data[index];
}
// No setter methods - truly immutable
}
// Usage
public class CryptoConfig {
public static final ImmutableByteArray DEFAULT_IV =
new ImmutableByteArray(new byte[] {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
});
}
CVE Examples
No specific CVEs are commonly attributed to this CWE directly, though the vulnerability pattern has been identified in various Java applications and applets.
References
- MITRE Corporation. "CWE-582: Array Declared Public, Final, and Static." https://cwe.mitre.org/data/definitions/582.html
- Joshua Bloch. "Effective Java" - Item 15: Minimize the accessibility of classes and members.
- CERT. "OBJ01-J. Limit accessibility of fields."