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

ImpactDetails
IntegrityScope: Integrity

Modify Application Data - External code can modify array contents, changing supposedly constant values and altering application behavior.
Access ControlScope: 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

  1. MITRE Corporation. "CWE-582: Array Declared Public, Final, and Static." https://cwe.mitre.org/data/definitions/582.html
  2. Joshua Bloch. "Effective Java" - Item 15: Minimize the accessibility of classes and members.
  3. CERT. "OBJ01-J. Limit accessibility of fields."