Public Static Field Not Marked Final
Description
Public Static Field Not Marked Final is a vulnerability where an object contains a public static field that is not marked as final, allowing it to be modified in unexpected ways. Public static variables can be accessed and altered by any code in the application without requiring accessor or mutator methods. This creates a global mutable state that can be exploited by malicious code or accidentally corrupted by well-intentioned but buggy code. The lack of the final modifier means the field's value can change at any time from anywhere in the program.
Risk
Non-final public static fields create global mutable state accessible to all code in the application. Attackers can modify configuration paths to point to malicious files, change security settings to disable protections, alter pricing or business logic values, or corrupt shared state to cause application failures. In multi-threaded environments, race conditions on such fields can lead to inconsistent behavior. In environments with shared class loaders like application servers, modifications can affect other applications. The risk is amplified because changes are not audited and can happen from anywhere in the codebase.
Solution
Mark all public static fields as final when possible. For constants, use public static final to create truly immutable values. If a field must be static but should not be globally modifiable, make it private and provide controlled access through static getter methods. For configuration that needs to change at runtime, use proper configuration management with access controls rather than public static fields. In C/C++, use the const keyword for constant values. Apply defensive programming by validating values before use even if they should be constant.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - The object could potentially be tampered with, allowing attackers to change configuration, security settings, or business logic values. |
| Confidentiality | Scope: Confidentiality Read Application Data - The object could potentially be read, exposing sensitive configuration information or application state. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Modifying static security-related fields can bypass authentication, authorization, or other security checks. |
Example Code
Vulnerable Code
// Vulnerable: Public static field without final
public class VulnerableConfiguration {
// Vulnerable: Can be modified from anywhere
public static String appPropertiesFile = "app/Application.properties";
public static String databaseUrl = "jdbc:mysql://localhost:3306/mydb";
public static String logDirectory = "/var/log/app/";
public static int maxLoginAttempts = 3;
public static boolean debugMode = false;
}
// Attacker exploits mutable static fields
public class ConfigExploit {
public void exploit() {
// Redirect config to attacker-controlled file
VulnerableConfiguration.appPropertiesFile = "/tmp/malicious.properties";
// Enable debug mode to expose sensitive info
VulnerableConfiguration.debugMode = true;
// Allow unlimited login attempts for brute force
VulnerableConfiguration.maxLoginAttempts = Integer.MAX_VALUE;
// Redirect logs to visible location
VulnerableConfiguration.logDirectory = "/var/www/html/logs/";
}
}
// Vulnerable: Security settings as non-final statics
public class VulnerableSecuritySettings {
// Vulnerable: Security controls can be disabled
public static boolean requireHttps = true;
public static boolean validateCertificates = true;
public static boolean enforceAuthentication = true;
public static String[] allowedOrigins = {"https://trusted.com"};
// Vulnerable: Encryption settings modifiable
public static String encryptionAlgorithm = "AES-256";
public static int keySize = 256;
}
// Attacker weakens security
public class SecurityExploit {
public void weaken() {
// Disable HTTPS requirement
VulnerableSecuritySettings.requireHttps = false;
// Disable certificate validation (MITM attack vector)
VulnerableSecuritySettings.validateCertificates = false;
// Disable authentication
VulnerableSecuritySettings.enforceAuthentication = false;
// Allow any origin
VulnerableSecuritySettings.allowedOrigins = new String[]{"*"};
// Weaken encryption
VulnerableSecuritySettings.encryptionAlgorithm = "DES";
VulnerableSecuritySettings.keySize = 56;
}
}
// Vulnerable: Pricing and business logic
public class VulnerablePricing {
// Vulnerable: Prices can be modified
public static double taxRate = 0.08;
public static double shippingCost = 9.99;
public static double discountMax = 0.20; // 20% max discount
public static double premiumSubscriptionPrice = 99.99;
}
// Attacker manipulates prices
public class PricingExploit {
public void exploit() {
VulnerablePricing.taxRate = 0.0; // No tax!
VulnerablePricing.shippingCost = 0.0; // Free shipping!
VulnerablePricing.discountMax = 1.0; // 100% discount!
VulnerablePricing.premiumSubscriptionPrice = 0.01; // Almost free!
}
}
// Vulnerable: C++ public static non-const
class VulnerableSettings {
public:
// Vulnerable: Modifiable from anywhere
static char* configFile;
static int maxConnections;
static bool securityEnabled;
};
// Initialize static members
char* VulnerableSettings::configFile = "/etc/app/config.cfg";
int VulnerableSettings::maxConnections = 100;
bool VulnerableSettings::securityEnabled = true;
// Exploit
void exploit() {
VulnerableSettings::configFile = "/tmp/evil.cfg";
VulnerableSettings::securityEnabled = false;
VulnerableSettings::maxConnections = 10000; // DoS
}
// Vulnerable: Applet with mutable static state
import java.applet.Applet;
public class VulnerableApplet extends Applet {
// Vulnerable: Other applets can modify these
public static String serverEndpoint = "https://api.trusted.com";
public static String apiKey = "secret-key-12345";
public static int[] allowedPorts = {443, 8443};
public void init() {
// Uses potentially modified values
connectToServer(serverEndpoint, apiKey);
}
}
// Malicious applet in same environment
public class MaliciousApplet extends Applet {
public void init() {
// Redirect traffic through attacker's server
VulnerableApplet.serverEndpoint = "https://attacker.com/proxy";
// Replace API key
VulnerableApplet.apiKey = "attacker-key";
}
}
Fixed Code
// Fixed: Use final for constants
public final class SecureConfiguration {
// Fixed: Private constructor prevents instantiation
private SecureConfiguration() {}
// Fixed: Final fields cannot be modified
public static final String APP_PROPERTIES_FILE = "app/Application.properties";
public static final String LOG_DIRECTORY = "/var/log/app/";
public static final int MAX_LOGIN_ATTEMPTS = 3;
// Fixed: For runtime configuration, use a proper configuration service
private static volatile Configuration runtimeConfig = loadConfiguration();
// Fixed: Controlled access to mutable configuration
public static Configuration getRuntimeConfig() {
return runtimeConfig; // Return immutable or copy
}
// Fixed: Controlled updates with authorization
public static void updateConfig(Configuration newConfig, AdminToken token) {
if (!token.isValid() || !token.hasPermission("UPDATE_CONFIG")) {
throw new SecurityException("Unauthorized configuration update");
}
auditLog("Configuration updated by: " + token.getAdmin());
runtimeConfig = newConfig;
}
private static Configuration loadConfiguration() {
return ConfigurationLoader.load(APP_PROPERTIES_FILE);
}
}
// Fixed: Immutable security settings
public final class SecureSecuritySettings {
private SecureSecuritySettings() {}
// Fixed: All settings final
public static final boolean REQUIRE_HTTPS = true;
public static final boolean VALIDATE_CERTIFICATES = true;
public static final boolean ENFORCE_AUTHENTICATION = true;
// Fixed: Immutable list for allowed origins
public static final List<String> ALLOWED_ORIGINS =
Collections.unmodifiableList(Arrays.asList("https://trusted.com"));
// Fixed: Final encryption settings
public static final String ENCRYPTION_ALGORITHM = "AES-256-GCM";
public static final int KEY_SIZE = 256;
// Fixed: If settings need to be configurable, use secure configuration
private static final SecurityConfig config = loadSecurityConfig();
public static SecurityConfig getConfig() {
return config; // Immutable config object
}
private static SecurityConfig loadSecurityConfig() {
// Load from secure, signed configuration
return SignedConfigLoader.loadSecurityConfig();
}
}
// Fixed: Immutable pricing configuration
public final class SecurePricing {
private SecurePricing() {}
// Fixed: Business rules as final constants
public static final double DEFAULT_TAX_RATE = 0.08;
public static final double BASE_SHIPPING_COST = 9.99;
public static final double MAX_DISCOUNT_PERCENT = 0.20;
public static final double PREMIUM_SUBSCRIPTION_BASE = 99.99;
// Fixed: Dynamic pricing through controlled service
private static final PricingService pricingService = new PricingService();
public static PricingService getPricingService() {
return pricingService;
}
// Fixed: Prices can only be updated through audited service
public static class PricingService {
private volatile PricingRules rules;
public PricingService() {
this.rules = loadFromDatabase();
}
public double getTaxRate(String region) {
return rules.getTaxRate(region);
}
public void updateRules(PricingRules newRules, AdminCredentials creds) {
if (!validateAdmin(creds)) {
throw new SecurityException("Unauthorized");
}
auditLog("Pricing updated by: " + creds.getAdminId());
this.rules = newRules;
}
}
}
// Fixed: C++ with const and controlled access
class SecureSettings {
public:
// Fixed: const static members
static const char* const CONFIG_FILE;
static const int MAX_CONNECTIONS;
static const bool SECURITY_ENABLED;
// Fixed: For mutable settings, use controlled access
static int getCurrentMaxConnections() {
return currentMaxConnections;
}
static bool setMaxConnections(int value, const AdminToken& token) {
if (!token.isValid()) {
return false;
}
currentMaxConnections = value;
return true;
}
private:
static int currentMaxConnections;
};
// Initialize const static members
const char* const SecureSettings::CONFIG_FILE = "/etc/app/config.cfg";
const int SecureSettings::MAX_CONNECTIONS = 100;
const bool SecureSettings::SECURITY_ENABLED = true;
int SecureSettings::currentMaxConnections = 100;
// Fixed: Secure applet configuration
import java.applet.Applet;
public final class SecureApplet extends Applet {
// Fixed: Final constants
private static final String DEFAULT_SERVER = "https://api.trusted.com";
// Fixed: Configuration loaded once, not modifiable
private final String serverEndpoint;
private final String apiKey;
private final int[] allowedPorts;
public SecureApplet() {
// Fixed: Load from signed configuration
SignedConfig config = SignedConfig.load();
this.serverEndpoint = config.getString("server", DEFAULT_SERVER);
this.apiKey = config.getEncrypted("apiKey");
this.allowedPorts = config.getIntArray("ports").clone();
}
@Override
public void init() {
// Fixed: Uses instance fields, not static mutable fields
connectToServer(serverEndpoint, apiKey);
}
// Fixed: Defensive copy for array access
public int[] getAllowedPorts() {
return allowedPorts.clone();
}
}
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:
- CERT Oracle Secure Coding Standard for Java (OBJ10-J)
- Java security guidelines
References
- MITRE Corporation. "CWE-500: Public Static Field Not Marked Final." https://cwe.mitre.org/data/definitions/500.html
- CERT Oracle Secure Coding Standard for Java. "OBJ10-J. Do not use public static nonfinal fields."
- Oracle. "Secure Coding Guidelines for Java SE."